Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make cleanup of OpenGL objects explicit in GLMakie #4699

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5b99033
pull tweaks from #4689
ffreyer Jan 2, 2025
fed962b
pull context validation from #4689
ffreyer Jan 2, 2025
442b271
handle texture atlas explicitly + fix errors
ffreyer Jan 2, 2025
1310186
explicitly clean up shaders & programs
ffreyer Jan 2, 2025
fb4bf07
handle GLBuffer, GLVertexArray, Postprocessors
ffreyer Jan 2, 2025
e8bf0fe
handle Texture explicitly
ffreyer Jan 2, 2025
d12c511
fix SSAO error
ffreyer Jan 2, 2025
affbfa9
test cleanup
ffreyer Jan 2, 2025
18fb15f
add some sanity checks
ffreyer Jan 2, 2025
437ac9f
fix tests
ffreyer Jan 2, 2025
662c971
cleanup finalizers, free(), unsafe_free()
ffreyer Jan 2, 2025
ca120a9
skip GLFW initialized assert for CI
ffreyer Jan 2, 2025
3eb56a1
update changelog
ffreyer Jan 2, 2025
4722f58
fix freeing of unused uniforms & cleanup unused pattern_sections
ffreyer Jan 2, 2025
39c7357
count and check failed OpenGL cleanup
ffreyer Jan 2, 2025
dbbf3f7
try to handle GLFW CI failure
ffreyer Jan 2, 2025
a500572
Merge branch 'master' into ff/safe-context
ffreyer Jan 2, 2025
e94363f
try fix CI
ffreyer Jan 2, 2025
a47ed68
require context for Texture and GLBuffer in most cases
ffreyer Jan 2, 2025
4b74544
fix incorrect uniform cleanup
ffreyer Jan 2, 2025
c096311
CI please
ffreyer Jan 2, 2025
4a1d278
fix typo
ffreyer Jan 2, 2025
e638450
try handling GLFW init errors differently
ffreyer Jan 2, 2025
3b46514
maybe just destroy the window if the context dies?
ffreyer Jan 2, 2025
c7285ca
fix typos
ffreyer Jan 2, 2025
0296205
maybe also cleanup dead screens when trying to reopen?
ffreyer Jan 2, 2025
b819240
tweak require_context to maybe catch error earlier
ffreyer Jan 2, 2025
6c64137
switch before requiring
ffreyer Jan 2, 2025
07e6d2e
avoid requiring context for get_texture and thus robj cleanup
ffreyer Jan 3, 2025
6f6eb32
treat scene finalizer
ffreyer Jan 3, 2025
54f1c1b
Treat scene finalizer in texture atlas too
ffreyer Jan 3, 2025
73f78c2
add missing passthrough
ffreyer Jan 3, 2025
c3f2131
move object deletion test to end
ffreyer Jan 3, 2025
86d4b0b
warn on dead context
ffreyer Jan 3, 2025
e54c2e2
avoid the last few current_context() calls
ffreyer Jan 3, 2025
c31bac8
fix scatter indices
ffreyer Jan 3, 2025
abe3d91
put finalizers behind debug constant
ffreyer Jan 4, 2025
bab465e
don't skip observable cleanup in free
ffreyer Jan 4, 2025
fa61c24
cleanup require_context
ffreyer Jan 4, 2025
8c58f93
fix typo
ffreyer Jan 4, 2025
667094d
fix test errors
ffreyer Jan 4, 2025
41351c7
clean up
SimonDanisch Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Fixed gaps in corners of `poly(Rect2(...))` stroke [#4664](https://github.com/MakieOrg/Makie.jl/pull/4664)
- Fixed an issue where `reinterpret`ed arrays of line points were not handled correctly in CairoMakie [#4668](https://github.com/MakieOrg/Makie.jl/pull/4668).
- Fixed various issues with `markerspace = :data`, `transform_marker = true` and `rotation` for scatter in CairoMakie (incorrect marker transformations, ignored transformations, Cairo state corruption) [#4663](https://github.com/MakieOrg/Makie.jl/pull/4663)
- Refactored OpenGL cleanup to run immediately rather than on GC [#4699](https://github.com/MakieOrg/Makie.jl/pull/4699)

## [0.21.18] - 2024-12-12

Expand Down
4 changes: 2 additions & 2 deletions GLMakie/src/GLAbstraction/AbstractGPUArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ gpu_setindex!(t) = error("gpu_setindex! not implemented for: $(typeof(t)). This
max_dim(t) = error("max_dim not implemented for: $(typeof(t)). This happens, when you call setindex! on an array, without implementing the GPUArray interface")


function (::Type{GPUArrayType})(data::Observable; kw...) where GPUArrayType <: GPUArray
gpu_mem = GPUArrayType(data[]; kw...)
function (::Type{GPUArrayType})(context, data::Observable; kw...) where GPUArrayType <: GPUArray
gpu_mem = GPUArrayType(context, data[]; kw...)
# TODO merge these and handle update tracking during construction
obs2 = on(new_data -> update!(gpu_mem, new_data), data)
if GPUArrayType <: TextureBuffer
Expand Down
16 changes: 15 additions & 1 deletion GLMakie/src/GLAbstraction/GLAbstraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ import FixedPointNumbers: N0f8, N0f16, N0f8, Normed

import Base: merge, resize!, similar, length, getindex, setindex!

# Debug tools
const GLMAKIE_DEBUG = Ref(false)

# implemented in GLMakie/glwindow.jl
function require_context_no_error(args...) end

function require_context(ctx, current = ShaderAbstractions.current_context())
msg = require_context_no_error(ctx, current)
isnothing(msg) && return nothing
error(msg)
end

export require_context

include("AbstractGPUArray.jl")

#Methods which get overloaded by GLExtendedFunctions.jl:
Expand All @@ -41,9 +55,9 @@ import ModernGL.glGetShaderiv
import ModernGL.glViewport
import ModernGL.glScissor

include("shaderabstraction.jl")
include("GLUtils.jl")

include("shaderabstraction.jl")
include("GLTypes.jl")
export GLProgram # Shader/program object
export Texture # Texture object, basically a 1/2/3D OpenGL data array
Expand Down
54 changes: 29 additions & 25 deletions GLMakie/src/GLAbstraction/GLBuffer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ mutable struct GLBuffer{T} <: GPUArray{T, 1}
# TODO maybe also delay upload to when render happens?
observers::Vector{Observables.ObserverFunction}

function GLBuffer{T}(ptr::Ptr{T}, buff_length::Int, buffertype::GLenum, usage::GLenum) where T
function GLBuffer{T}(context, ptr::Ptr{T}, buff_length::Int, buffertype::GLenum, usage::GLenum) where T
require_context(context)
id = glGenBuffers()
glBindBuffer(buffertype, id)
# size of 0 can segfault it seems
Expand All @@ -16,9 +17,9 @@ mutable struct GLBuffer{T} <: GPUArray{T, 1}
glBindBuffer(buffertype, 0)

obj = new(
id, (buff_length,), buffertype, usage, current_context(),
id, (buff_length,), buffertype, usage, context,
Observables.ObserverFunction[])
finalizer(free, obj)
GLMAKIE_DEBUG[] && finalizer(verify_free, obj)
obj
end
end
Expand All @@ -34,35 +35,35 @@ end
bind(buffer::GLBuffer, other_target) = glBindBuffer(buffer.buffertype, other_target)

function similar(x::GLBuffer{T}, buff_length::Int) where T
GLBuffer{T}(Ptr{T}(C_NULL), buff_length, x.buffertype, x.usage)
return GLBuffer{T}(x.context, Ptr{T}(C_NULL), buff_length, x.buffertype, x.usage)
end

cardinality(::GLBuffer{T}) where {T} = cardinality(T)

#Function to deal with any Immutable type with Real as Subtype
function GLBuffer(
buffer::Union{Base.ReinterpretArray{T, 1}, DenseVector{T}};
context, buffer::Union{Base.ReinterpretArray{T, 1}, DenseVector{T}};
buffertype::GLenum = GL_ARRAY_BUFFER, usage::GLenum = GL_STATIC_DRAW
) where T <: GLArrayEltypes
GC.@preserve buffer begin
return GLBuffer{T}(pointer(buffer), length(buffer), buffertype, usage)
return GLBuffer{T}(context, pointer(buffer), length(buffer), buffertype, usage)
end
end

function GLBuffer(
buffer::DenseVector{T};
context, buffer::DenseVector{T};
buffertype::GLenum = GL_ARRAY_BUFFER, usage::GLenum = GL_STATIC_DRAW
) where T <: GLArrayEltypes
GC.@preserve buffer begin
return GLBuffer{T}(pointer(buffer), length(buffer), buffertype, usage)
return GLBuffer{T}(context, pointer(buffer), length(buffer), buffertype, usage)
end
end

function GLBuffer(
buffer::ShaderAbstractions.Buffer{T};
context, buffer::ShaderAbstractions.Buffer{T};
buffertype::GLenum = GL_ARRAY_BUFFER, usage::GLenum = GL_STATIC_DRAW
) where T <: GLArrayEltypes
b = GLBuffer(ShaderAbstractions.data(buffer); buffertype=buffertype, usage=usage)
b = GLBuffer(context, ShaderAbstractions.data(buffer); buffertype=buffertype, usage=usage)
au = ShaderAbstractions.updater(buffer)
obsfunc = on(au.update) do (f, args)
f(b, args...) # forward setindex! etc
Expand All @@ -76,36 +77,30 @@ end
GLBuffer(buffer::GLBuffer) = buffer
GLBuffer{T}(buffer::GLBuffer{T}) where {T} = buffer

function GLBuffer(
buffer::AbstractVector{T};
kw_args...
) where T <: GLArrayEltypes
GLBuffer(collect(buffer); kw_args...)
function GLBuffer(context, buffer::AbstractVector{T}; kw_args...) where T <: GLArrayEltypes
return GLBuffer(context, collect(buffer); kw_args...)
end

function GLBuffer{T}(
buffer::AbstractVector;
kw_args...
) where T <: GLArrayEltypes
GLBuffer(convert(Vector{T}, buffer); kw_args...)
function GLBuffer{T}(context, buffer::AbstractVector; kw_args...) where T <: GLArrayEltypes
return GLBuffer(context, convert(Vector{T}, buffer); kw_args...)
end

function GLBuffer(
::Type{T}, len::Int;
context, ::Type{T}, len::Int;
buffertype::GLenum = GL_ARRAY_BUFFER, usage::GLenum = GL_STATIC_DRAW
) where T <: GLArrayEltypes
GLBuffer{T}(Ptr{T}(C_NULL), len, buffertype, usage)
return GLBuffer{T}(context, Ptr{T}(C_NULL), len, buffertype, usage)
end


function indexbuffer(
buffer::VectorTypes{T};
usage::GLenum = GL_STATIC_DRAW
context, buffer::VectorTypes{T}; usage::GLenum = GL_STATIC_DRAW
) where T <: GLArrayEltypes
GLBuffer(buffer, buffertype = GL_ELEMENT_ARRAY_BUFFER, usage=usage)
return GLBuffer(context, buffer, buffertype = GL_ELEMENT_ARRAY_BUFFER, usage=usage)
end
# GPUArray interface
function gpu_data(b::GLBuffer{T}) where T
require_context(b.context)
data = Vector{T}(undef, length(b))
bind(b)
glGetBufferSubData(b.buffertype, 0, sizeof(data), data)
Expand All @@ -116,6 +111,7 @@ end

# Resize buffer
function gpu_resize!(buffer::GLBuffer{T}, newdims::NTuple{1, Int}) where T
require_context(buffer.context)
#TODO make this safe!
newlength = newdims[1]
oldlen = length(buffer)
Expand All @@ -139,13 +135,15 @@ function gpu_resize!(buffer::GLBuffer{T}, newdims::NTuple{1, Int}) where T
end

function gpu_setindex!(b::GLBuffer{T}, value::Vector{T}, offset::Integer) where T
require_context(b.context)
multiplicator = sizeof(T)
bind(b)
glBufferSubData(b.buffertype, multiplicator*(offset-1), sizeof(value), value)
bind(b, 0)
end

function gpu_setindex!(b::GLBuffer{T}, value::Vector{T}, offset::UnitRange{Int}) where T
require_context(b.context)
multiplicator = sizeof(T)
bind(b)
glBufferSubData(b.buffertype, multiplicator*(first(offset)-1), sizeof(value), value)
Expand All @@ -156,6 +154,8 @@ end
# copy between two buffers
# could be a setindex! operation, with subarrays for buffers
function unsafe_copy!(a::GLBuffer{T}, readoffset::Int, b::GLBuffer{T}, writeoffset::Int, len::Int) where T
require_context(a.context)
@assert a.context == b.context
multiplicator = sizeof(T)
@assert a.id != 0 & b.id != 0
glBindBuffer(GL_COPY_READ_BUFFER, a.id)
Expand All @@ -178,6 +178,7 @@ end

#copy inside one buffer
function unsafe_copy!(buffer::GLBuffer{T}, readoffset::Int, writeoffset::Int, len::Int) where T
require_context(buffer.context)
len <= 0 && return nothing
bind(buffer)
ptr = Ptr{T}(glMapBuffer(buffer.buffertype, GL_READ_WRITE))
Expand All @@ -190,6 +191,7 @@ function unsafe_copy!(buffer::GLBuffer{T}, readoffset::Int, writeoffset::Int, le
end

function unsafe_copy!(a::Vector{T}, readoffset::Int, b::GLBuffer{T}, writeoffset::Int, len::Int) where T
require_context(b.context)
bind(b)
ptr = Ptr{T}(glMapBuffer(b.buffertype, GL_WRITE_ONLY))
for i=1:len
Expand All @@ -200,6 +202,7 @@ function unsafe_copy!(a::Vector{T}, readoffset::Int, b::GLBuffer{T}, writeoffset
end

function unsafe_copy!(a::GLBuffer{T}, readoffset::Int, b::Vector{T}, writeoffset::Int, len::Int) where T
require_context(a.context)
bind(a)
ptr = Ptr{T}(glMapBuffer(a.buffertype, GL_READ_ONLY))
for i in 1:len
Expand All @@ -210,6 +213,7 @@ function unsafe_copy!(a::GLBuffer{T}, readoffset::Int, b::Vector{T}, writeoffset
end

function gpu_getindex(b::GLBuffer{T}, range::UnitRange) where T
require_context(b.context)
multiplicator = sizeof(T)
offset = first(range)-1
value = Vector{T}(undef, length(range))
Expand Down
8 changes: 8 additions & 0 deletions GLMakie/src/GLAbstraction/GLInfo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,11 @@ function getProgramInfo(p::GLProgram)
@show info = glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_BUFFER_MODE)
@show info = glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS)
end

const FAILED_FREE_COUNTER = Ref(0)
function verify_free(obj::T, name = T) where T
if obj.id != 0
FAILED_FREE_COUNTER[] = FAILED_FREE_COUNTER[] + 1
Threads.@spawn println(stderr, "Error: $name has not been freed.")
end
end
1 change: 1 addition & 0 deletions GLMakie/src/GLAbstraction/GLRender.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function setup_clip_planes(N::Integer)
end
end

# Note: context required in renderloop, not per renderobject here

"""
When rendering a specialised list of Renderables, we can do some optimizations
Expand Down
35 changes: 25 additions & 10 deletions GLMakie/src/GLAbstraction/GLShader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ function ShaderCache(context)
)
end

function free(cache::ShaderCache)
ShaderAbstractions.switch_context!(cache.context)
for (k, v) in cache.shader_cache
for (k2, shader) in v
free(shader)
end
end
for program in values(cache.program_cache)
free(program)
end
require_context_no_error(cache.context) # just so we don't need try .. catch at call site
return
end

abstract type AbstractLazyShader end

struct LazyShader <: AbstractLazyShader
Expand All @@ -116,9 +130,9 @@ struct LazyShader <: AbstractLazyShader
end
end

gl_convert(shader::GLProgram, data) = shader
gl_convert(::GLContext, shader::GLProgram, data) = shader

function compile_shader(source::ShaderSource, template_src::String)
function compile_shader(context, source::ShaderSource, template_src::String)
name = source.name
shaderid = createshader(source.typ)
glShaderSource(shaderid, template_src)
Expand All @@ -127,7 +141,7 @@ function compile_shader(source::ShaderSource, template_src::String)
GLAbstraction.print_with_lines(template_src)
@warn("shader $(name) didn't compile. \n$(GLAbstraction.getinfolog(shaderid))")
end
return Shader(name, Vector{UInt8}(template_src), source.typ, shaderid)
return Shader(context, name, Vector{UInt8}(template_src), source.typ, shaderid)
end


Expand All @@ -137,14 +151,14 @@ function get_shader!(cache::ShaderCache, src::ShaderSource, template_replacement
return get!(shader_dict, template_replacement) do
templated_source = mustache_replace(template_replacement, src.source)
ShaderAbstractions.switch_context!(cache.context)
return compile_shader(src, templated_source)
return compile_shader(cache.context, src, templated_source)
end::Shader
end

function get_template!(cache::ShaderCache, src::ShaderSource, view, attributes)
return get!(cache.template_cache, src.name) do
templated_source, replacements = template2source(src.source, view, attributes)
shader = compile_shader(src, templated_source)
shader = compile_shader(cache.context, src, templated_source)
template_keys = collect(keys(replacements))
template_replacements = collect(values(replacements))
# can't yet be in here, since we didn't even have template keys
Expand Down Expand Up @@ -179,7 +193,7 @@ function compile_program(shaders::Vector{Shader}, fragdatalocation)
# generate the link locations
nametypedict = uniform_name_type(program)
uniformlocationdict = uniformlocations(nametypedict, program)
GLProgram(program, shaders, nametypedict, uniformlocationdict)
return GLProgram(program, shaders, nametypedict, uniformlocationdict)
end

function get_view(kw_dict)
Expand All @@ -190,12 +204,13 @@ function get_view(kw_dict)
_view
end

gl_convert(lazyshader::AbstractLazyShader, data) = error("gl_convert shader")
function gl_convert(lazyshader::LazyShader, data)
gl_convert(lazyshader.shader_cache, lazyshader, data)
gl_convert(::GLContext, lazyshader::AbstractLazyShader, data) = error("gl_convert shader")
function gl_convert(ctx::GLContext, lazyshader::LazyShader, data)
gl_convert(ctx, lazyshader.shader_cache, lazyshader, data)
end

function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data)
function gl_convert(ctx::GLContext, cache::ShaderCache, lazyshader::AbstractLazyShader, data)
require_context(cache.context, ctx)
kw_dict = lazyshader.kw_args
paths = lazyshader.paths

Expand Down
Loading
Loading