From 1a140f90dfcbb9eb64d7cf8fc9dc69ef0f737497 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 16 Aug 2024 20:27:21 +0200 Subject: [PATCH 01/26] prototype scene tree, new render loop --- GLMakie/src/GLScene.jl | 215 +++++++++++++++++++++ GLMakie/src/drawing_primitives.jl | 16 +- GLMakie/src/gl_backend.jl | 1 + GLMakie/src/postprocessing.jl | 57 ++---- GLMakie/src/rendering.jl | 302 ++++++++++++++++++++++++------ GLMakie/src/screen.jl | 144 +++++--------- 6 files changed, 529 insertions(+), 206 deletions(-) create mode 100644 GLMakie/src/GLScene.jl diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl new file mode 100644 index 00000000000..4f7ee97d744 --- /dev/null +++ b/GLMakie/src/GLScene.jl @@ -0,0 +1,215 @@ +struct GLScene + scene::WeakRef # TODO phase this out? + + # TODO: do these need to be reactive for render on demand? + viewport::Observable{Rect2f} + clear::Observable{Bool} + backgroundcolor::Observable{RGBAf} + visible::Observable{Bool} + # clear_depth::Bool + # stecil/scene_based_occlusion + + # TODO: + # - move postprocessors here + # - make them controllable by Makie + # - move SSAO settings into postprocessor controls + ssao::Makie.SSAO + # postprocessors + + renderobjects::Vector{RenderObject} + + # TODO: WeakRef or not? + plot2robj::Dict{UInt64, RenderObject} + robj2plot::Dict{UInt32, Plot} +end + +function GLScene(scene::Scene) + return GLScene( + WeakRef(scene), + + scene.viewport, + scene.clear, + scene.backgroundcolor, + scene.visible, + scene.ssao, + + RenderObject[], + Dict{UInt64, RenderObject}(), + Dict{UInt32, Plot}() + ) +end + +function Base.show(io::IO, glscene::GLScene) + println(io, + "GLScene(", + glscene === nothing ? "nothing" : "scene", ", ", + "viewport = ", glscene.viewport[], ", ", + "clear = ", glscene.clear[], ", ", + "backgroundcolor = ", glscene.backgroundcolor[], ", ", + "visible = ", glscene.visible[], ", ", + "ssao = ", glscene.ssao, ", ", + length(glscene.renderobjects), " RenderObjects", + ")" + ) +end + +function delete_plot!(glscene::GLScene, plot::AbstractPlot) + for atomic in Makie.collect_atomic_plots(plot) + if haskey(glscene.plot2robj, objectid(atomic)) + robj = pop!(plot2robj, objectid(atomic)) + + filter!(x -> x !== robj, glscene.renderobjects) + delete!(glscene.robj2plot, robj.id) + delete!(glscene.plot2robj, objectid(atomic)) + + destroy!(robj) + else + # TODO: Should hard-error? + @error "Cannot delete plot which is not part of the glscene. $atomic" + end + end +end + + + +# TODO: name? should we keep this separate from Screen? +struct GLSceneTree + scene2index::Dict{WeakRef, Int} + + # flattened scene tree + # Order: + # Scene 1 + # Scene 2 + # Scene 3 + # Scene 4 + # Scene 5 + # Scene 6 + # Scene 7 + # Scene 8 + # Scene 9 + scenes::Vector{GLScene} + depth::Vector{Int} +end + +GLSceneTree() = GLSceneTree(Dict{WeakRef, Int}(), GLScene[], Int[]) + +function gc_cleanup(tree::GLSceneTree) + # TODO: do we need this? Can this create orphaned child scenes? + for k in copy(keys(tree.scene2index)) + if k === nothing + # Remove scene from map + index = pop!(tree.scene2index, WeakRef(scene)) + + # Clean up RenderObjects (maps should get deleted with GLScene struct) + foreach(destroy!, tree.scenes[index]) + + # Remove GLScene (TODO: rendering parameters should not need cleanup? ) + deleteat!(tree.scenes, index) + deleteat!(tree.depth, index) + + # Update mapping + for (k, v) in tree.scene2index + if v > index + tree.scene2index[k] -= 1 + end + end + end + end +end + +Base.haskey(tree::GLSceneTree, scene::Scene) = haskey(tree.scene2index, WeakRef(scene)) +Base.getindex(tree::GLSceneTree, scene::Scene) = tree.scenes[tree.scene2index[WeakRef(scene)]] + +function Base.show(io::IO, tree::GLSceneTree) + for i in eachindex(tree.scenes) + println(io, " " ^ tree.depth[i], "GLScene(", length(tree.scenes[i].renderobjects), ")") + end +end + +function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Nothing, index::Integer) + # a root scene can only be added if the screen does not already have a root scene + + if isempty(tree.scenes) + tree.scene2index[WeakRef(scene)] = 1 + push!(tree.scenes, GLScene(scene)) + push!(tree.depth, 1) + else + error("Cannot insert a root scene into a tree that already contains one.") + end + + return +end + +function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::Integer) + if isempty(tree.scenes) # allow non-root scenes to act as root scenes + tree.scene2index[WeakRef(scene)] = 1 + push!(tree.scenes, GLScene(scene)) + push!(tree.depth, 1) + return + elseif !haskey(tree.scene2index, WeakRef(parent)) + error("Cannot add a scene whose parent is not part of the displayed scene tree.") + end + + @assert !isempty(tree.scenes) "An empty scene tree should not be reachable here." + + parent_index = tree.scene2index[WeakRef(parent)] + index = parent_index + index + tree.scene2index[WeakRef(scene)] = index + insert!(tree.scenes, index, GLScene(scene)) + insert!(tree.depth, index, tree.depth[parent_index] + 1) + + return +end + +function delete_scene!(tree::GLSceneTree, scene::Scene) + if haskey(tree.scene2index, WeakRef(scene)) + # Delete all child scenes + for child in scene.children + delete_scene!(tree, child) + end + + # Remove scene from map + index = pop!(tree.scene2index, WeakRef(scene)) + + # Clean up RenderObjects (maps should get deleted with GLScene struct) + glscene = tree.scenes[index] + foreach(destroy!, glscene.renderobjects) + + # Remove GLScene (TODO: rendering parameters should not need cleanup? ) + deleteat!(tree.scenes, index) + deleteat!(tree.depth, index) + + # Update mapping + for (k, v) in tree.scene2index + if v > index + tree.scene2index[k] -= 1 + end + end + else + error("Cannot delete scene from tree - does not exist in tree.") + end + return +end + +# TODO: +# How integrate + +# function add_renderobject!(tree::GLSceneTree, scene::Scene, robj::RenderObject) +# if haskey(tree.scene2index, WeakRef(scene)) +# glscene = tree.scenes[tree.scene2index[WeakRef(scene)]] +# push!(glscene.renderobjects, robj) +# glscene.plot2robj[]::Dict{UInt64, RenderObject} # TODO: +# glscene.robj2plot[]::Dict{UInt32, Plot} # TODO: +# else +# error("Cannot insert renderobject if it's parent scene is not part of the rendered scene tree.") +# end +# end + +function delete_plot!(tree::GLSceneTree, scene::Scene, plot::AbstractPlot) + if haskey(tree, scene) + delete_plot!(tree[scene], plot) + else + error("Cannot delete plot if its parent scene is not being shown.") + end + return +end diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 65550461d71..7df47c0da73 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -232,7 +232,12 @@ const EXCLUDE_KEYS = Set([:transformation, :tickranges, :ticklabels, :raw, :SSAO function cached_robj!(robj_func, screen, scene, plot::AbstractPlot) # poll inside functions to make wait on compile less prominent pollevents(screen, Makie.BackendTick) - robj = get!(screen.cache, objectid(plot)) do + + @assert haskey(screen.scene_tree, scene) + glscene = screen.scene_tree[scene] + + + robj = get!(glscene.plot2robj, objectid(plot)) do filtered = filter(plot.attributes) do (k, v) return !in(k, EXCLUDE_KEYS) @@ -337,10 +342,12 @@ function cached_robj!(robj_func, screen, scene, plot::AbstractPlot) robj = robj_func(gl_attributes) get!(gl_attributes, :ssao, Observable(false)) - screen.cache2plot[robj.id] = plot + glscene.robj2plot[robj.id] = plot return robj end - push!(screen, scene, robj) + + push!(glscene.renderobjects, robj) + return robj end @@ -348,11 +355,12 @@ Base.insert!(::GLMakie.Screen, ::Scene, ::Makie.PlotList) = nothing function Base.insert!(screen::Screen, scene::Scene, @nospecialize(x::Plot)) ShaderAbstractions.switch_context!(screen.glscreen) - add_scene!(screen, scene) + add_scene!(screen, scene) # TODO: redundant? # poll inside functions to make wait on compile less prominent pollevents(screen, Makie.BackendTick) if isempty(x.plots) # if no plots inserted, this truly is an atomic draw_atomic(screen, scene, x) + else foreach(x.plots) do x # poll inside functions to make wait on compile less prominent diff --git a/GLMakie/src/gl_backend.jl b/GLMakie/src/gl_backend.jl index ee4bffe3402..ff83ed0e9bb 100644 --- a/GLMakie/src/gl_backend.jl +++ b/GLMakie/src/gl_backend.jl @@ -62,6 +62,7 @@ end include("glwindow.jl") include("postprocessing.jl") +include("GLScene.jl") include("screen.jl") include("glshaders/visualize_interface.jl") include("glshaders/lines.jl") diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 60c978e07cf..53630937962 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -25,7 +25,7 @@ struct PostProcessor{F} end function empty_postprocessor(args...; kwargs...) - PostProcessor(RenderObject[], screen -> nothing, empty_postprocessor) + PostProcessor(RenderObject[], (args...) -> nothing, empty_postprocessor) end @@ -63,12 +63,8 @@ function OIT_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin - fb = screen.framebuffer - w, h = size(fb) - # Blend transparent onto opaque glDrawBuffer(color_id) - glViewport(0, 0, w, h) GLAbstraction.render(pass) end @@ -154,44 +150,16 @@ function ssao_postprocessor(framebuffer, shader_cache) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) color_id = framebuffer[:color][1] - full_render = screen -> begin - fb = screen.framebuffer - w, h = size(fb) - - # Setup rendering - # SSAO - calculate occlusion + full_render = (screen, settings, projection) -> begin glDrawBuffer(normal_occ_id) # occlusion buffer - glViewport(0, 0, w, h) - glEnable(GL_SCISSOR_TEST) - ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) - - for (screenid, scene) in screen.screens - # Select the area of one leaf scene - # This should be per scene because projection may vary between - # scenes. It should be a leaf scene to avoid repeatedly shading - # the same region (though this is not guaranteed...) - isempty(scene.children) || continue - a = viewport(scene)[] - glScissor(ppu(minimum(a))..., ppu(widths(a))...) - # update uniforms - data1[:projection] = scene.camera.projection[] - data1[:bias] = scene.ssao.bias[] - data1[:radius] = scene.ssao.radius[] - GLAbstraction.render(pass1) - end + data1[:projection] = projection + data1[:bias] = ssao.bias[] + data1[:radius] = ssao.radius[] + GLAbstraction.render(pass1) - # SSAO - blur occlusion and apply to color glDrawBuffer(color_id) # color buffer - for (screenid, scene) in screen.screens - # Select the area of one leaf scene - isempty(scene.children) || continue - a = viewport(scene)[] - glScissor(ppu(minimum(a))..., ppu(widths(a))...) - # update uniforms - data2[:blur_range] = scene.ssao.blur - GLAbstraction.render(pass2) - end - glDisable(GL_SCISSOR_TEST) + data2[:blur_range] = ssao.blur[] + GLAbstraction.render(pass2) end PostProcessor(RenderObject[pass1, pass2], full_render, ssao_postprocessor) @@ -244,12 +212,8 @@ function fxaa_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin - fb = screen.framebuffer - w, h = size(fb) - # FXAA - calculate LUMA glDrawBuffer(luma_id) - glViewport(0, 0, w, h) # necessary with negative SSAO bias... glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) @@ -285,12 +249,13 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi pass = RenderObject(data, shader, PostprocessPrerender(), nothing) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - full_render = screen -> begin + full_render = (screen, x, y, w, h) -> begin # transfer everything to the screen default_id = isnothing(screen_fb_id) ? 0 : screen_fb_id[] # GLFW uses 0, Gtk uses a value that we have to probe at the beginning of rendering glBindFramebuffer(GL_FRAMEBUFFER, default_id) - glViewport(0, 0, framebuffer_size(screen.glscreen)...) + glViewport(x, y, w, h) + glScissor(x, y, w, h) glClear(GL_COLOR_BUFFER_BIT) GLAbstraction.render(pass) # copy postprocess end diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index dda559f5ec3..4dcc8ab1dcb 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -1,91 +1,151 @@ -function setup!(screen::Screen) - glEnable(GL_SCISSOR_TEST) - if isopen(screen) && !isnothing(screen.root_scene) - ppu = screen.px_per_unit[] - glScissor(0, 0, round.(Int, size(screen.root_scene) .* ppu)...) - glClearColor(1, 1, 1, 1) - glClear(GL_COLOR_BUFFER_BIT) - for (id, scene) in screen.screens - if scene.visible[] - a = viewport(scene)[] - rt = (round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) - glViewport(rt...) - if scene.clear[] - c = scene.backgroundcolor[] - glScissor(rt...) - glClearColor(red(c), green(c), blue(c), alpha(c)) - glClear(GL_COLOR_BUFFER_BIT) - end - end - end - end - glDisable(GL_SCISSOR_TEST) - return -end - """ Renders a single frame of a `window` """ function render_frame(screen::Screen; resize_buffers=true) + render_frame(screen, screen.scene_tree.scenes, resize_buffers) +end + +function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers=true) nw = to_native(screen) ShaderAbstractions.switch_context!(nw) - function sortby(x) - robj = x[3] - plot = screen.cache2plot[robj.id] - # TODO, use actual boundingbox - return Makie.zvalue2d(plot) - end - zvals = sortby.(screen.renderlist) - permute!(screen.renderlist, sortperm(zvals)) - - # NOTE - # The transparent color buffer is reused by SSAO and FXAA. Changing the - # render order here may introduce artifacts because of that. + # Resize buffers fb = screen.framebuffer if resize_buffers && !isnothing(screen.root_scene) ppu = screen.px_per_unit[] resize!(fb, round.(Int, ppu .* size(screen.root_scene))...) end + + # Clear final output + # TODO: Could be skipped if glscenes[1] clears + OUTPUT_FRAMEBUFFER_ID = 0 + glBindFramebuffer(GL_FRAMEBUFFER, OUTPUT_FRAMEBUFFER_ID) + wh = framebuffer_size(nw) + glViewport(0, 0, wh[1], wh[2]) - # prepare stencil (for sub-scenes) + glClearColor(1,0,0,1) + glClear(GL_COLOR_BUFFER_BIT) + + # Reset all intermediate buffers + # TODO: really just need color, objectid here? (additionals clear per scene) glBindFramebuffer(GL_FRAMEBUFFER, fb.id) glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) glClearColor(0, 0, 0, 0) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - glDrawBuffer(fb.render_buffer_ids[1]) - setup!(screen) - glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) + # Draw scene by scene + glEnable(GL_SCISSOR_TEST) + for glscene in glscenes + render_frame(screen, glscene) + end + glDisable(GL_SCISSOR_TEST) + + # TODO: copy accumulation buffer to screen buffer + # screen.postprocessors[4].render(screen) +end + + +function render_frame(screen::Screen, glscene::GLScene) + clear = glscene.clear[]::Bool + renderlist = glscene.renderobjects::Vector{RenderObject} + # if the scene doesn't have a visual impact we skip + if !glscene.visible[] || (isempty(renderlist) && !clear) + return + end + + # TODO: Not like this + if glscene.scene === nothing + return + end + + nw = to_native(screen) + fb = screen.framebuffer # required + + # z-sorting + function sortby(robj) + plot = glscene.robj2plot[robj.id] + # TODO, use actual boundingbox + return Makie.zvalue2d(plot) + end + zvals = sortby.(glscene.renderobjects) + permute!(glscene.renderobjects, sortperm(zvals)) + + # Redundant? + glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + + # Set/Restrict draw area + mini = screen.px_per_unit[] .* minimum(glscene.viewport[]) + wh = screen.px_per_unit[] .* widths(glscene.viewport[]) + @inbounds glViewport(mini[1], mini[2], wh[1], wh[2]) + @inbounds glScissor(mini[1], mini[2], wh[1], wh[2]) + + # TODO: better solution + # render buffers are (color, objectid, maybe position, maybe normals) + # color, objectid should only be cleared if glscene.clear == true + # position, normals are only relevant for SSAO but should be cleared + # TODO: make DEPTH clear optional? + # TODO: unpadded clears may create edge-artifacts with SSAO, FXAA + glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + glDrawBuffers(length(fb.render_buffer_ids)-2, fb.render_buffer_ids[2:end]) + glClearColor(0, 0, 0, 0) + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + + if glscene.clear[] + # Draw background color + glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer + c = glscene.backgroundcolor[]::RGBAf + glClearColor(red(c), green(c), blue(c), alpha(c)) + glClear(GL_COLOR_BUFFER_BIT) + + # If previous picked plots are no longer visible they should not be pickable + if alpha(c) == 1.0 + glDrawBuffer(fb.render_buffer_ids[2]) # objectid, i.e. picking + glClearColor(0, 0, 0, 0) + glClear(GL_COLOR_BUFFER_BIT) + end + else + # TODO: maybe SSAO needs this cleared if we run it per scene...? + glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer + glClearColor(0, 0, 1, 0) + glClear(GL_COLOR_BUFFER_BIT) + end + + # NOTE + # The transparent color buffer is reused by SSAO and FXAA. Changing the + # render order here may introduce artifacts because of that. + + # TODO: clear SSAO buffers here? # render with SSAO + glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) # activate all render outputs glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) - GLAbstraction.render(screen) do robj + GLAbstraction.render(glscene) do robj return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) end - # SSAO - screen.postprocessors[1].render(screen) + # SSAO postprocessor + # TODO: when moving postprocessors to Scene the postprocessor should not need any extra inputs + screen.postprocessors[1].render(screen, glscene.ssao, glscene.scene.value.camera.projection[]) # render no SSAO - glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) + glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) # color, objectid # render all non ssao - GLAbstraction.render(screen) do robj + GLAbstraction.render(glscene) do robj return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) end # TRANSPARENT RENDER # clear sums to 0 - glDrawBuffer(GL_COLOR_ATTACHMENT2) + glDrawBuffer(GL_COLOR_ATTACHMENT2) # HDR color (i.e. 16 Bit precision) glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) # clear alpha product to 1 - glDrawBuffer(GL_COLOR_ATTACHMENT3) + glDrawBuffer(GL_COLOR_ATTACHMENT3) # OIT weight buffer glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) # draw - glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) + glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) # HDR color, objectid, OIT weight # Render only transparent objects - GLAbstraction.render(screen) do robj + GLAbstraction.render(glscene) do robj return Bool(robj[:transparency][]) end @@ -96,11 +156,115 @@ function render_frame(screen::Screen; resize_buffers=true) screen.postprocessors[3].render(screen) # transfer everything to the screen - screen.postprocessors[4].render(screen) + # TODO: accumulation buffer would avoid viewport/scissor reset + screen.postprocessors[4].render(screen, mini[1], mini[2], wh[1], wh[2]) return end +# function setup!(screen::Screen) +# glEnable(GL_SCISSOR_TEST) +# if isopen(screen) && !isnothing(screen.root_scene) +# ppu = screen.px_per_unit[] +# glScissor(0, 0, round.(Int, size(screen.root_scene) .* ppu)...) +# glClearColor(1, 1, 1, 1) +# glClear(GL_COLOR_BUFFER_BIT) +# for (id, scene) in screen.screens +# if scene.visible[] +# a = viewport(scene)[] +# rt = (round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) +# glViewport(rt...) +# if scene.clear[] +# c = scene.backgroundcolor[] +# glScissor(rt...) +# glClearColor(red(c), green(c), blue(c), alpha(c)) +# glClear(GL_COLOR_BUFFER_BIT) +# end +# end +# end +# end +# glDisable(GL_SCISSOR_TEST) +# return +# end + +# """ +# Renders a single frame of a `window` +# """ +# function render_frame(screen::Screen; resize_buffers=true) +# nw = to_native(screen) +# ShaderAbstractions.switch_context!(nw) +# function sortby(x) +# robj = x[3] +# plot = screen.cache2plot[robj.id] +# # TODO, use actual boundingbox +# return Makie.zvalue2d(plot) +# end +# zvals = sortby.(screen.renderlist) +# permute!(screen.renderlist, sortperm(zvals)) + +# # NOTE +# # The transparent color buffer is reused by SSAO and FXAA. Changing the +# # render order here may introduce artifacts because of that. + +# fb = screen.framebuffer +# if resize_buffers && !isnothing(screen.root_scene) +# ppu = screen.px_per_unit[] +# resize!(fb, round.(Int, ppu .* size(screen.root_scene))...) +# end + +# # prepare stencil (for sub-scenes) +# glBindFramebuffer(GL_FRAMEBUFFER, fb.id) +# glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) +# glClearColor(0, 0, 0, 0) +# glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + +# glDrawBuffer(fb.render_buffer_ids[1]) +# setup!(screen) +# glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) + +# # render with SSAO +# glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) +# GLAbstraction.render(screen) do robj +# return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) +# end +# # SSAO +# screen.postprocessors[1].render(screen) + +# # render no SSAO +# glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) +# # render all non ssao +# GLAbstraction.render(screen) do robj +# return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) +# end + +# # TRANSPARENT RENDER +# # clear sums to 0 +# glDrawBuffer(GL_COLOR_ATTACHMENT2) +# glClearColor(0, 0, 0, 0) +# glClear(GL_COLOR_BUFFER_BIT) +# # clear alpha product to 1 +# glDrawBuffer(GL_COLOR_ATTACHMENT3) +# glClearColor(1, 1, 1, 1) +# glClear(GL_COLOR_BUFFER_BIT) +# # draw +# glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) +# # Render only transparent objects +# GLAbstraction.render(screen) do robj +# return Bool(robj[:transparency][]) +# end + +# # TRANSPARENT BLEND +# screen.postprocessors[2].render(screen) + +# # FXAA +# screen.postprocessors[3].render(screen) + +# # transfer everything to the screen +# screen.postprocessors[4].render(screen) + +# return +# end + function id2scene(screen, id1) # TODO maybe we should use a different data structure for (id2, scene) in screen.screens @@ -109,19 +273,33 @@ function id2scene(screen, id1) return false, nothing end -function GLAbstraction.render(filter_elem_func, screen::Screen) +# function GLAbstraction.render(filter_elem_func, screen::Screen) +# # Somehow errors in here get ignored silently!? +# try +# for (zindex, screenid, elem) in screen.renderlist +# filter_elem_func(elem)::Bool || continue + +# found, scene = id2scene(screen, screenid) +# found || continue +# scene.visible[] || continue +# ppu = screen.px_per_unit[] +# a = viewport(scene)[] +# glViewport(round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) +# render(elem) +# end +# catch e +# @error "Error while rendering!" exception = e +# rethrow(e) +# end +# return +# end + +function GLAbstraction.render(filter_elem_func, glscene::GLScene) # Somehow errors in here get ignored silently!? try - for (zindex, screenid, elem) in screen.renderlist - filter_elem_func(elem)::Bool || continue - - found, scene = id2scene(screen, screenid) - found || continue - scene.visible[] || continue - ppu = screen.px_per_unit[] - a = viewport(scene)[] - glViewport(round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) - render(elem) + for robj in glscene.renderobjects + filter_elem_func(robj)::Bool || continue + render(robj) end catch e @error "Error while rendering!" exception = e diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index bd01bbdedc3..3e567ceafaa 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -169,12 +169,14 @@ mutable struct Screen{GLWindow} <: MakieScreen timer::BudgetedTimer px_per_unit::Observable{Float32} - screen2scene::Dict{WeakRef, ScreenID} - screens::Vector{ScreenArea} - renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}} + # screen2scene::Dict{WeakRef, ScreenID} + # screens::Vector{ScreenArea} + # renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}} + # cache::Dict{UInt64, RenderObject} + # cache2plot::Dict{UInt32, AbstractPlot} + scene_tree::GLSceneTree postprocessors::Vector{PostProcessor} - cache::Dict{UInt64, RenderObject} - cache2plot::Dict{UInt32, AbstractPlot} + framecache::Matrix{RGB{N0f8}} render_tick::Observable{Makie.TickState} # listeners must not Consume(true) window_open::Observable{Bool} @@ -195,21 +197,21 @@ mutable struct Screen{GLWindow} <: MakieScreen stop_renderloop::Bool, rendertask::Union{Nothing, Task}, - screen2scene::Dict{WeakRef, ScreenID}, - screens::Vector{ScreenArea}, - renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}}, + # screen2scene::Dict{WeakRef, ScreenID}, + # screens::Vector{ScreenArea}, + # renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}}, postprocessors::Vector{PostProcessor}, - cache::Dict{UInt64, RenderObject}, - cache2plot::Dict{UInt32, AbstractPlot}, + # cache::Dict{UInt64, RenderObject}, + # cache2plot::Dict{UInt32, AbstractPlot}, reuse::Bool ) where {GLWindow} s = size(framebuffer) screen = new{GLWindow}( glscreen, owns_glscreen, shader_cache, framebuffer, - config, stop_renderloop, rendertask, BudgetedTimer(1.0 / 30.0), - Observable(0f0), screen2scene, - screens, renderlist, postprocessors, cache, cache2plot, + config, stop_renderloop, rendertask, + BudgetedTimer(1.0 / 30.0), Observable(0f0), + GLSceneTree(), postprocessors, Matrix{RGB{N0f8}}(undef, s), Observable(Makie.UnknownTickState), Observable(true), Observable(0f0), nothing, reuse, true, false ) @@ -289,12 +291,7 @@ function empty_screen(debugging::Bool; reuse=true, window=nothing) window, owns_glscreen, shader_cache, fb, nothing, false, nothing, - Dict{WeakRef, ScreenID}(), - ScreenArea[], - Tuple{ZIndex, ScreenID, RenderObject}[], postprocessors, - Dict{UInt64, RenderObject}(), - Dict{UInt32, AbstractPlot}(), reuse, ) @@ -494,16 +491,24 @@ Base.isopen(x::Screen) = isopen(x.glscreen) Base.size(x::Screen) = size(x.framebuffer) function add_scene!(screen::Screen, scene::Scene) - get!(screen.screen2scene, WeakRef(scene)) do - id = length(screen.screens) + 1 - push!(screen.screens, (id, scene)) + if !haskey(screen.scene_tree, scene) + # TODO: shortcut for now + if isnothing(scene.parent) + idx = 1 + else + idx = findfirst(==(scene), scene.parent.children) + if !haskey(screen.scene_tree, scene.parent) + add_scene!(screen, scene.parent) + end + end + insert_scene!(screen.scene_tree, scene, scene.parent, idx) + screen.requires_update = true onany((args...) -> screen.requires_update = true, scene, scene.visible, scene.backgroundcolor, scene.clear, scene.ssao.bias, scene.ssao.blur, scene.ssao.radius, scene.camera.projectionview, scene.camera.resolution) - return id end return end @@ -520,41 +525,7 @@ function Makie.insertplots!(screen::Screen, scene::Scene) end function Base.delete!(screen::Screen, scene::Scene) - for child in scene.children - delete!(screen, child) - end - for plot in scene.plots - delete!(screen, scene, plot) - end - filter!(x -> x !== screen, scene.current_screens) - if haskey(screen.screen2scene, WeakRef(scene)) - deleted_id = pop!(screen.screen2scene, WeakRef(scene)) - # TODO: this should always find something but sometimes doesn't... - i = findfirst(id_scene -> id_scene[1] == deleted_id, screen.screens) - i !== nothing && deleteat!(screen.screens, i) - - # Remap scene IDs to a continuous range by replacing the largest ID - # with the one that got removed - if deleted_id - 1 != length(screen.screens) - key, max_id = first(screen.screen2scene) - for p in screen.screen2scene - if p[2] > max_id - key, max_id = p - end - end - - i = findfirst(id_scene -> id_scene[1] == max_id, screen.screens)::Int - screen.screens[i] = (deleted_id, screen.screens[i][2]) - - screen.screen2scene[key] = deleted_id - - for (i, (z, id, robj)) in enumerate(screen.renderlist) - if id == max_id - screen.renderlist[i] = (z, deleted_id, robj) - end - end - end - end + delete_scene!(screen.scene_tree, scene) return end @@ -583,22 +554,7 @@ function destroy!(rob::RenderObject) end function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) - if !isempty(plot.plots) - # this plot consists of children, so we flatten it and delete the children instead - for cplot in Makie.collect_atomic_plots(plot) - delete!(screen, scene, cplot) - end - else - # I think we can double delete renderobjects, so this may be ok - # TODO, is it? - renderobject = get(screen.cache, objectid(plot), nothing) - if !isnothing(renderobject) - destroy!(renderobject) - filter!(x-> x[3] !== renderobject, screen.renderlist) - delete!(screen.cache2plot, renderobject.id) - end - delete!(screen.cache, objectid(plot)) - end + delete_plot!(screen.scene_tree, scene, plot) screen.requires_update = true return end @@ -608,9 +564,9 @@ function Base.empty!(screen::Screen) # we should never just "empty" an already destroyed screen @assert !was_destroyed(screen.glscreen) - for plot in collect(values(screen.cache2plot)) - delete!(screen, Makie.rootparent(plot), plot) - end + # for plot in collect(values(screen.cache2plot)) + # delete!(screen, Makie.rootparent(plot), plot) + # end if !isnothing(screen.root_scene) Makie.disconnect_screen(screen.root_scene, screen) @@ -618,12 +574,12 @@ function Base.empty!(screen::Screen) screen.root_scene = nothing end - @assert isempty(screen.renderlist) - @assert isempty(screen.cache2plot) - @assert isempty(screen.cache) + # @assert isempty(screen.renderlist) + # @assert isempty(screen.cache2plot) + # @assert isempty(screen.cache) - empty!(screen.screen2scene) - empty!(screen.screens) + # empty!(screen.screen2scene) + # empty!(screen.screens) Observables.clear(screen.px_per_unit) Observables.clear(screen.scalefactor) Observables.clear(screen.render_tick) @@ -792,19 +748,19 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma end end -function Base.push!(screen::Screen, scene::Scene, robj) - # filter out gc'ed elements - filter!(screen.screen2scene) do (k, v) - k.value !== nothing - end - screenid = get!(screen.screen2scene, WeakRef(scene)) do - id = length(screen.screens) + 1 - push!(screen.screens, (id, scene)) - return id - end - push!(screen.renderlist, (0, screenid, robj)) - return robj -end +# function Base.push!(screen::Screen, scene::Scene, robj) +# # filter out gc'ed elements +# filter!(screen.screen2scene) do (k, v) +# k.value !== nothing +# end +# screenid = get!(screen.screen2scene, WeakRef(scene)) do +# id = length(screen.screens) + 1 +# push!(screen.screens, (id, scene)) +# return id +# end +# push!(screen.renderlist, (0, screenid, robj)) +# return robj +# end Makie.to_native(x::Screen) = x.glscreen From c906145dca60d7041c9c6b28fc5883b04aa06639 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 16 Aug 2024 22:09:30 +0200 Subject: [PATCH 02/26] update scene pushing infrastructure --- GLMakie/src/GLScene.jl | 36 ++++++++++++++++++---- GLMakie/src/drawing_primitives.jl | 1 - GLMakie/src/screen.jl | 50 ++++++++++++++++++------------- src/scenes.jl | 24 +++++++++++++-- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl index 4f7ee97d744..bc51df25a5b 100644 --- a/GLMakie/src/GLScene.jl +++ b/GLMakie/src/GLScene.jl @@ -47,7 +47,7 @@ function Base.show(io::IO, glscene::GLScene) "clear = ", glscene.clear[], ", ", "backgroundcolor = ", glscene.backgroundcolor[], ", ", "visible = ", glscene.visible[], ", ", - "ssao = ", glscene.ssao, ", ", + "ssao = ..., ", length(glscene.renderobjects), " RenderObjects", ")" ) @@ -152,11 +152,37 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In @assert !isempty(tree.scenes) "An empty scene tree should not be reachable here." + # Figure out where the scene should be inserted parent_index = tree.scene2index[WeakRef(parent)] - index = parent_index + index - tree.scene2index[WeakRef(scene)] = index - insert!(tree.scenes, index, GLScene(scene)) - insert!(tree.depth, index, tree.depth[parent_index] + 1) + insert_index = parent_index + @info insert_index + while (index > 0) && (insert_index < length(tree.scenes)) + @info "loop" + insert_index += 1 + if tree.depth[insert_index] == tree.depth[parent_index] + 1 + # found a child of parent + index -= 1 + elseif tree.depth[insert_index] == tree.depth[parent_index] + # found a sibling of parent + # we can insert here but no further down + if index != 1 + error("Cannot insert scene because other children of its parent are missing.") + end + index -= 1 + break + end + end + @info insert_index, tree.depth[insert_index], tree.depth[parent_index] + if index == 1 && insert_index == length(tree.scenes) + insert_index += 1 + elseif index != 0 + error("Failed to find scene insertion index.") + end + @info insert_index + + tree.scene2index[WeakRef(scene)] = insert_index + insert!(tree.scenes, insert_index, GLScene(scene)) + insert!(tree.depth, insert_index, tree.depth[parent_index] + 1) return end diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 7df47c0da73..50c47fe5919 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -355,7 +355,6 @@ Base.insert!(::GLMakie.Screen, ::Scene, ::Makie.PlotList) = nothing function Base.insert!(screen::Screen, scene::Scene, @nospecialize(x::Plot)) ShaderAbstractions.switch_context!(screen.glscreen) - add_scene!(screen, scene) # TODO: redundant? # poll inside functions to make wait on compile less prominent pollevents(screen, Makie.BackendTick) if isempty(x.plots) # if no plots inserted, this truly is an atomic diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 3e567ceafaa..c3dd0ce0257 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -436,10 +436,16 @@ end function display_scene!(screen::Screen, scene::Scene) @debug("display scene on screen") resize!(screen, size(scene)...) + + # insert scene content + @assert isempty(screen.scene_tree.scenes) "There should be no scenes in the tree when inserting the root" + screen.root_scene = scene + insert_scene!(screen.scene_tree, scene, nothing, 0) insertplots!(screen, scene) + Makie.push_screen!(scene, screen) connect_screen(scene, screen) - screen.root_scene = scene + return end @@ -490,32 +496,34 @@ Base.show(io::IO, screen::Screen) = print(io, "GLMakie.Screen(...)") Base.isopen(x::Screen) = isopen(x.glscreen) Base.size(x::Screen) = size(x.framebuffer) -function add_scene!(screen::Screen, scene::Scene) +function Base.insert!(screen::Screen, scene::Scene, parent::Scene, idx::Integer) if !haskey(screen.scene_tree, scene) - # TODO: shortcut for now - if isnothing(scene.parent) - idx = 1 - else - idx = findfirst(==(scene), scene.parent.children) - if !haskey(screen.scene_tree, scene.parent) - add_scene!(screen, scene.parent) - end - end - insert_scene!(screen.scene_tree, scene, scene.parent, idx) - - screen.requires_update = true - onany((args...) -> screen.requires_update = true, - scene, - scene.visible, scene.backgroundcolor, scene.clear, - scene.ssao.bias, scene.ssao.blur, scene.ssao.radius, scene.camera.projectionview, - scene.camera.resolution) + insert_scene!(screen, scene, parent, idx) end return end +function insert_scene!(screen::Screen, scene::Scene, parent::Union{Scene, Nothing}, idx::Integer) + insert_scene!(screen.scene_tree, scene, parent, idx) + + screen.requires_update = true + onany((args...) -> screen.requires_update = true, + scene, + scene.visible, scene.backgroundcolor, scene.clear, + scene.ssao.bias, scene.ssao.blur, scene.ssao.radius, scene.camera.projectionview, + scene.camera.resolution) +end + function Makie.insertplots!(screen::Screen, scene::Scene) ShaderAbstractions.switch_context!(screen.glscreen) - add_scene!(screen, scene) + + # TODO: organize this better? + # typemin(Int) to force boundserror if scene is not root_scene + if !haskey(screen.scene_tree, scene) + idx = isnothing(scene.parent) ? typemin(Int) : findfirst(==(scene), scene.parent.children) + insert!(screen, scene, scene.parent, idx) + end + for elem in scene.plots insert!(screen, scene, elem) end @@ -570,7 +578,7 @@ function Base.empty!(screen::Screen) if !isnothing(screen.root_scene) Makie.disconnect_screen(screen.root_scene, screen) - delete!(screen, screen.root_scene) + delete!(screen, screen.root_scene) # this should wipe all scenes and plots screen.root_scene = nothing end diff --git a/src/scenes.jl b/src/scenes.jl index 34aa7f6e9f8..524f3782a08 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -322,7 +322,7 @@ function Scene( error("viewport must be an Observable{Rect2} or a Rect2") end end - push!(parent.children, child) + push!(parent, child) child.parent = parent return child end @@ -419,13 +419,20 @@ function free(scene::Scene) end function Base.empty!(scene::Scene; free=false) + # clear all child scenes foreach(empty!, copy(scene.children)) + # clear plots of this scene for plot in copy(scene.plots) delete!(scene, plot) end - # clear all child scenes + # delete scene in backend + for screen in scene.current_screens + delete!(screen, scene) + end + + # remove from parent if !isnothing(scene.parent) filter!(x-> x !== scene, scene.parent.children) end @@ -449,6 +456,15 @@ function Base.empty!(scene::Scene; free=false) return nothing end +function Base.push!(parent::Scene, child::Scene) + @assert isempty(child.children) "Adding a scene with children to a parent not yet implemented" + push!(parent.children, child) + idx = length(parent.children) + for screen in parent.current_screens + Base.invokelatest(insert!, screen, child, parent, idx) + end +end + function Base.push!(plot::Plot, subplot) MakieCore.validate_attribute_keys(subplot) subplot.parent = plot @@ -463,6 +479,10 @@ function Base.push!(scene::Scene, @nospecialize(plot::Plot)) end end +function Base.insert!(screen::MakieScreen, scene::Scene, parent::Scene, idx::Integer) + @debug "Inserting scenes not implemented for backend $(typeof(screen))" +end + function Base.delete!(screen::MakieScreen, ::Scene, ::AbstractPlot) @debug "Deleting plots not implemented for backend: $(typeof(screen))" end From c5272e7c8f8bbdb55eb1772819e7134bd05253fe Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 01:49:23 +0200 Subject: [PATCH 03/26] fix px_per_unit error --- GLMakie/src/rendering.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 4dcc8ab1dcb..194b5ac6da2 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -75,8 +75,9 @@ function render_frame(screen::Screen, glscene::GLScene) glBindFramebuffer(GL_FRAMEBUFFER, fb.id) # Set/Restrict draw area - mini = screen.px_per_unit[] .* minimum(glscene.viewport[]) - wh = screen.px_per_unit[] .* widths(glscene.viewport[]) + # TODO: move to glscreen? + mini = round.(Int, screen.px_per_unit[] .* minimum(glscene.viewport[])) + wh = round.(Int, screen.px_per_unit[] .* widths(glscene.viewport[])) @inbounds glViewport(mini[1], mini[2], wh[1], wh[2]) @inbounds glScissor(mini[1], mini[2], wh[1], wh[2]) From d8380b8dbdfaf5c42db33d64129a6674fb4730d4 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 01:54:12 +0200 Subject: [PATCH 04/26] fix to_screen not showing anything --- GLMakie/src/postprocessing.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 53630937962..a1bdf7e1b60 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -254,9 +254,9 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi default_id = isnothing(screen_fb_id) ? 0 : screen_fb_id[] # GLFW uses 0, Gtk uses a value that we have to probe at the beginning of rendering glBindFramebuffer(GL_FRAMEBUFFER, default_id) - glViewport(x, y, w, h) + wh = framebuffer_size(to_native(screen)) + glViewport(0, 0, wh[1], wh[2]) glScissor(x, y, w, h) - glClear(GL_COLOR_BUFFER_BIT) GLAbstraction.render(pass) # copy postprocess end From 1a1d1b4228c8f6d09e43ce62fdadc0aeabaf8880 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 02:24:28 +0200 Subject: [PATCH 05/26] fix clear=false scenes rendering solid colors --- GLMakie/assets/shader/postprocessing/copy.frag | 4 +--- GLMakie/src/GLAbstraction/GLRender.jl | 3 ++- GLMakie/src/postprocessing.jl | 8 +++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/GLMakie/assets/shader/postprocessing/copy.frag b/GLMakie/assets/shader/postprocessing/copy.frag index 9ec63ec90af..b5095968ce3 100644 --- a/GLMakie/assets/shader/postprocessing/copy.frag +++ b/GLMakie/assets/shader/postprocessing/copy.frag @@ -6,7 +6,5 @@ out vec4 fragment_color; void main(void) { - vec4 color = texture(color_texture, frag_uv); - fragment_color.rgb = color.rgb; - fragment_color.a = 1.0; + fragment_color = texture(color_texture, frag_uv); } diff --git a/GLMakie/src/GLAbstraction/GLRender.jl b/GLMakie/src/GLAbstraction/GLRender.jl index b1dbeb967d4..3fc0a65be5b 100644 --- a/GLMakie/src/GLAbstraction/GLRender.jl +++ b/GLMakie/src/GLAbstraction/GLRender.jl @@ -179,6 +179,7 @@ function enabletransparency() # target.rgb = source.a * source.rgb + (1 - source.a) * target.rgb # target.a = 0 * source.a + 1 * target.a # the latter is required to keep target.a = 1 for the OIT pass - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) + # glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) return end diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index a1bdf7e1b60..d5d651ad149 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -246,7 +246,13 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi data = Dict{Symbol, Any}( :color_texture => framebuffer[:color][2] ) - pass = RenderObject(data, shader, PostprocessPrerender(), nothing) + pass = RenderObject(data, shader, () -> begin + glDepthMask(GL_TRUE) + glDisable(GL_DEPTH_TEST) + glDisable(GL_CULL_FACE) + glEnable(GL_BLEND) + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) + end, nothing) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) full_render = (screen, x, y, w, h) -> begin From 10c25e4d9b258e100889e60b5d3f6f5f4109ab2b Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 11:33:49 +0200 Subject: [PATCH 06/26] fix fxaa undoing clearing --- GLMakie/assets/shader/postprocessing/fxaa.frag | 1 + GLMakie/src/rendering.jl | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/GLMakie/assets/shader/postprocessing/fxaa.frag b/GLMakie/assets/shader/postprocessing/fxaa.frag index 56d4eb678b9..816831105eb 100644 --- a/GLMakie/assets/shader/postprocessing/fxaa.frag +++ b/GLMakie/assets/shader/postprocessing/fxaa.frag @@ -1047,4 +1047,5 @@ void main(void) 0.0f, // FxaaFloat fxaaConsoleEdgeThresholdMin, FxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f) // FxaaFloat fxaaConsole360ConstDir, ).rgb; + fragment_color.a = 1.0; } diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 194b5ac6da2..e60c65a6ce9 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -46,16 +46,18 @@ end function render_frame(screen::Screen, glscene::GLScene) + # TODO: Not like this + if glscene.scene === nothing + @warn "Parent scene unavailable" + return + end + clear = glscene.clear[]::Bool renderlist = glscene.renderobjects::Vector{RenderObject} # if the scene doesn't have a visual impact we skip if !glscene.visible[] || (isempty(renderlist) && !clear) - return - end - - # TODO: Not like this - if glscene.scene === nothing + @info "Skipped due to visible = $(glscene.visible[]) or empty renderlist $(isempty(renderlist)) && clear = $clear" return end From f5a2a6f5b90251e51aced29a2746162887f21154 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 12:14:53 +0200 Subject: [PATCH 07/26] disconnect screen from scene when removing scene from screen --- GLMakie/src/GLScene.jl | 5 ++++- GLMakie/src/screen.jl | 2 +- src/scenes.jl | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl index bc51df25a5b..1579e98a33a 100644 --- a/GLMakie/src/GLScene.jl +++ b/GLMakie/src/GLScene.jl @@ -191,7 +191,7 @@ function delete_scene!(tree::GLSceneTree, scene::Scene) if haskey(tree.scene2index, WeakRef(scene)) # Delete all child scenes for child in scene.children - delete_scene!(tree, child) + delete_scene!(screen, tree, child) end # Remove scene from map @@ -211,6 +211,9 @@ function delete_scene!(tree::GLSceneTree, scene::Scene) tree.scene2index[k] -= 1 end end + + # Remove screen from scene to avoid double-deletion + filter!(x -> x !== screen, scene.current_screens) else error("Cannot delete scene from tree - does not exist in tree.") end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index c3dd0ce0257..3cb7bac6e7d 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -533,7 +533,7 @@ function Makie.insertplots!(screen::Screen, scene::Scene) end function Base.delete!(screen::Screen, scene::Scene) - delete_scene!(screen.scene_tree, scene) + delete_scene!(screen, screen.scene_tree, scene) return end diff --git a/src/scenes.jl b/src/scenes.jl index 524f3782a08..a12941560c3 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -410,9 +410,9 @@ function free(scene::Scene) for field in [:backgroundcolor, :viewport, :visible] Observables.clear(getfield(scene, field)) end - for screen in copy(scene.current_screens) - delete!(screen, scene) - end + # for screen in copy(scene.current_screens) + # delete!(screen, scene) + # end empty!(scene.current_screens) scene.parent = nothing return From 5ea88b7d363454fe4d3edd7700280ae8814a1cd4 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 12:20:00 +0200 Subject: [PATCH 08/26] fix shrinking of image when running draw_fullscreen --- GLMakie/src/postprocessing.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index d5d651ad149..e41296131dd 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -64,6 +64,8 @@ function OIT_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin # Blend transparent onto opaque + wh = framebuffer_size(to_native(screen)) + glViewport(0, 0, wh[1], wh[2]) glDrawBuffer(color_id) GLAbstraction.render(pass) end @@ -151,6 +153,9 @@ function ssao_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = (screen, settings, projection) -> begin + wh = framebuffer_size(to_native(screen)) + glViewport(0, 0, wh[1], wh[2]) + glDrawBuffer(normal_occ_id) # occlusion buffer data1[:projection] = projection data1[:bias] = ssao.bias[] @@ -212,6 +217,10 @@ function fxaa_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin + # TODO: make scissor explicit? + wh = framebuffer_size(to_native(screen)) + glViewport(0, 0, wh[1], wh[2]) + # FXAA - calculate LUMA glDrawBuffer(luma_id) # necessary with negative SSAO bias... From 09f7878b14d75e5cb88a61fd6ab9fb96e79e69a2 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 14:46:49 +0200 Subject: [PATCH 09/26] rework blending for transparent buffers --- .../shader/postprocessing/OIT_blend.frag | 8 +++--- GLMakie/src/GLAbstraction/GLRender.jl | 15 +++++++++- GLMakie/src/postprocessing.jl | 28 +++++++++++++------ GLMakie/src/rendering.jl | 12 +++++--- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/GLMakie/assets/shader/postprocessing/OIT_blend.frag b/GLMakie/assets/shader/postprocessing/OIT_blend.frag index 63d4343fb0c..4916d4a2af0 100644 --- a/GLMakie/assets/shader/postprocessing/OIT_blend.frag +++ b/GLMakie/assets/shader/postprocessing/OIT_blend.frag @@ -8,18 +8,18 @@ in vec2 frag_uv; // contains sum_i C_i * weight(depth_i, alpha_i) uniform sampler2D sum_color; // contains pod_i (1 - alpha_i) -uniform sampler2D prod_alpha; +uniform sampler2D transmittance; out vec4 fragment_color; void main(void) { vec4 summed_color_weight = texture(sum_color, frag_uv); - float transmittance = texture(prod_alpha, frag_uv).r; + float tr = texture(transmittance, frag_uv).r; vec3 weighted_transparent = summed_color_weight.rgb / max(summed_color_weight.a, 0.00001); - vec3 full_weighted_transparent = weighted_transparent * (1 - transmittance); + vec3 full_weighted_transparent = weighted_transparent * (1 - tr); fragment_color.rgb = full_weighted_transparent; - fragment_color.a = transmittance; + fragment_color.a = tr; } diff --git a/GLMakie/src/GLAbstraction/GLRender.jl b/GLMakie/src/GLAbstraction/GLRender.jl index 3fc0a65be5b..3938a587843 100644 --- a/GLMakie/src/GLAbstraction/GLRender.jl +++ b/GLMakie/src/GLAbstraction/GLRender.jl @@ -175,11 +175,24 @@ end function enabletransparency() glEnablei(GL_BLEND, 0) glDisablei(GL_BLEND, 1) + # We render to a potentially transparent scene here. The final blending + # may look like this: + # (s)creen <- (b)ackground <- (r)ender + # with the screen being opaque. This should blend as: + # r.a * r.rgb + (1-r.a) * (b.a * b.rgb + (1-b.a) * s.rgb), 1 + # = r.a * r.rgb + (1-r.a) * b.a * b.rgb + (1-r.a) * (1-b.a) * s.rgb, 1 + # If we want to run the screen blend at a later stage we can: + # - precalculate p = b.a * b.rgb, 1 - b.a in the background buffer + # - track (1 - r.a) * p.a as alpha in the buffer + # which gives us the following blending rule: + # r.a * r.rgb + (1-r.a) * p.rgb, 0 * r.a + (1-r.a) * p.a + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA) + # This does: # target.rgb = source.a * source.rgb + (1 - source.a) * target.rgb # target.a = 0 * source.a + 1 * target.a # the latter is required to keep target.a = 1 for the OIT pass # glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) return end diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index e41296131dd..33dca4db684 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -40,7 +40,7 @@ function OIT_postprocessor(framebuffer, shader_cache) data = Dict{Symbol, Any}( # :opaque_color => framebuffer[:color][2], :sum_color => framebuffer[:HDR_color][2], - :prod_alpha => framebuffer[:OIT_weight][2], + :transmittance => framebuffer[:OIT_weight][2], ) pass = RenderObject( data, shader, @@ -49,13 +49,21 @@ function OIT_postprocessor(framebuffer, shader_cache) glDisable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) glEnable(GL_BLEND) - # shader computes: - # src.rgb = sum_color / sum_weight * (1 - prod_alpha) - # src.a = prod_alpha - # blending: (assumes opaque.a = 1) - # opaque.rgb = 1 * src.rgb + src.a * opaque.rgb - # opaque.a = 0 * src.a + 1 * opaque.a - glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_ONE) + # prepare: + # sum_color = 0 + # transmittance = 1 + # main render + blend: (w/o clamping) + # weight = alpha * (factor) * (1 - z)^3 + # sum_color += (weight * rgb, weight) + # transmittance *= (1 - alpha) + # postprocessor shader: + # out = (sum_color.rgb / sum_color.w * (1 - transmittance), tranmittance) + # blending (here) + # src = ^ = (pre-multiplied color, transmittance) + # dst = (pre-multiplied color, transmittance) (from non-OIT draw) + # combines to another (pre-multiplied color, transmittance) as: + # src.rgb + dst.rgb, src.a * dst.a + glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA) end, nothing ) @@ -260,7 +268,9 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi glDisable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) glEnable(GL_BLEND) - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) + # glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) + # see GLRender.jl enabletransparency() + glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_ONE) end, nothing) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index e60c65a6ce9..becebf081c7 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -95,22 +95,26 @@ function render_frame(screen::Screen, glscene::GLScene) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) if glscene.clear[] + @info "clearing" # Draw background color glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer c = glscene.backgroundcolor[]::RGBAf - glClearColor(red(c), green(c), blue(c), alpha(c)) + a = alpha(c) + @info a * red(c), a * green(c), a * blue(c), 1f0 - a + glClearColor(a * red(c), a * green(c), a * blue(c), 1f0 - a) glClear(GL_COLOR_BUFFER_BIT) # If previous picked plots are no longer visible they should not be pickable - if alpha(c) == 1.0 + if alpha(c) == 1f0 glDrawBuffer(fb.render_buffer_ids[2]) # objectid, i.e. picking glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) end else + @info "no clear" # TODO: maybe SSAO needs this cleared if we run it per scene...? glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer - glClearColor(0, 0, 1, 0) + glClearColor(0, 0, 0, 1) glClear(GL_COLOR_BUFFER_BIT) end @@ -156,7 +160,7 @@ function render_frame(screen::Screen, glscene::GLScene) screen.postprocessors[2].render(screen) # FXAA - screen.postprocessors[3].render(screen) + # screen.postprocessors[3].render(screen) # transfer everything to the screen # TODO: accumulation buffer would avoid viewport/scissor reset From 34c67f4489b790e53d07beb1f09fc19b861b2c38 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 14:55:30 +0200 Subject: [PATCH 10/26] fix var name, tweak comment --- GLMakie/src/postprocessing.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 33dca4db684..64d8ad557c1 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -59,9 +59,8 @@ function OIT_postprocessor(framebuffer, shader_cache) # postprocessor shader: # out = (sum_color.rgb / sum_color.w * (1 - transmittance), tranmittance) # blending (here) - # src = ^ = (pre-multiplied color, transmittance) + # src = out = (pre-multiplied color, transmittance) # dst = (pre-multiplied color, transmittance) (from non-OIT draw) - # combines to another (pre-multiplied color, transmittance) as: # src.rgb + dst.rgb, src.a * dst.a glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA) end, @@ -160,7 +159,7 @@ function ssao_postprocessor(framebuffer, shader_cache) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) color_id = framebuffer[:color][1] - full_render = (screen, settings, projection) -> begin + full_render = (screen, ssao, projection) -> begin wh = framebuffer_size(to_native(screen)) glViewport(0, 0, wh[1], wh[2]) From a7eb6811f5a06776f81bf2f18b7a3c56267c7067 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 17:01:08 +0200 Subject: [PATCH 11/26] add global robj2plot for picking --- GLMakie/src/GLScene.jl | 88 ++++++++++++++++++------------- GLMakie/src/drawing_primitives.jl | 7 ++- GLMakie/src/picking.jl | 37 ++++++------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl index 1579e98a33a..dbd94676333 100644 --- a/GLMakie/src/GLScene.jl +++ b/GLMakie/src/GLScene.jl @@ -39,6 +39,18 @@ function GLScene(scene::Scene) ) end +# A bit dodgy... +Base.haskey(glscene::GLScene, robj::RenderObject) = haskey(glscene, robj.id) +Base.haskey(glscene::GLScene, plot::AbstractPlot) = haskey(glscene, objectid(plot)) +Base.haskey(glscene::GLScene, robj_id::UInt32) = haskey(glscene.robj2plot, robj_id) +Base.haskey(glscene::GLScene, plot_id::UInt64) = haskey(glscene.plot2robj, plot_id) + +Base.getindex(glscene::GLScene, robj::RenderObject) = getindex(glscene, robj.id) +Base.getindex(glscene::GLScene, plot::AbstractPlot) = getindex(glscene, objectid(plot)) +Base.getindex(glscene::GLScene, robj_id::UInt32) = getindex(glscene.robj2plot, robj_id) +Base.getindex(glscene::GLScene, plot_id::UInt64) = getindex(glscene.plot2robj, plot_id) + + function Base.show(io::IO, glscene::GLScene) println(io, "GLScene(", @@ -54,19 +66,23 @@ function Base.show(io::IO, glscene::GLScene) end function delete_plot!(glscene::GLScene, plot::AbstractPlot) - for atomic in Makie.collect_atomic_plots(plot) - if haskey(glscene.plot2robj, objectid(atomic)) - robj = pop!(plot2robj, objectid(atomic)) - - filter!(x -> x !== robj, glscene.renderobjects) - delete!(glscene.robj2plot, robj.id) - delete!(glscene.plot2robj, objectid(atomic)) - - destroy!(robj) - else - # TODO: Should hard-error? - @error "Cannot delete plot which is not part of the glscene. $atomic" - end + @info "deleting plot $(objectid(plot))" + @assert isempty(plot.plots) "Plot must be atomic" + if haskey(glscene.plot2robj, objectid(atomic)) + @info "deleting atomic $(objectid(atomic))" + robj = pop!(glscene.plot2robj, objectid(atomic)) + + filter!(x -> x !== robj, glscene.renderobjects) + delete!(glscene.robj2plot, robj.id) + delete!(glscene.plot2robj, objectid(atomic)) + + destroy!(robj) + + return robj.id + else + # TODO: Should hard-error? + @debug("Cannot delete plot which is not part of the glscene. $atomic") + return typemax(UInt32) end end @@ -75,6 +91,7 @@ end # TODO: name? should we keep this separate from Screen? struct GLSceneTree scene2index::Dict{WeakRef, Int} + robj2plot::Dict{UInt32, AbstractPlot} # for picking # flattened scene tree # Order: @@ -91,7 +108,7 @@ struct GLSceneTree depth::Vector{Int} end -GLSceneTree() = GLSceneTree(Dict{WeakRef, Int}(), GLScene[], Int[]) +GLSceneTree() = GLSceneTree(Dict{WeakRef, Int}(), Dict{UInt32, AbstractPlot}(), GLScene[], Int[]) function gc_cleanup(tree::GLSceneTree) # TODO: do we need this? Can this create orphaned child scenes? @@ -119,6 +136,7 @@ end Base.haskey(tree::GLSceneTree, scene::Scene) = haskey(tree.scene2index, WeakRef(scene)) Base.getindex(tree::GLSceneTree, scene::Scene) = tree.scenes[tree.scene2index[WeakRef(scene)]] +Base.isempty(tree::GLSceneTree) = isempty(tree.scenes) function Base.show(io::IO, tree::GLSceneTree) for i in eachindex(tree.scenes) @@ -128,7 +146,7 @@ end function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Nothing, index::Integer) # a root scene can only be added if the screen does not already have a root scene - + @info "Inserting root scene $(objectid(scene))" if isempty(tree.scenes) tree.scene2index[WeakRef(scene)] = 1 push!(tree.scenes, GLScene(scene)) @@ -141,6 +159,7 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Nothing, index:: end function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::Integer) + @info "Inserting scene $(objectid(scene))" if isempty(tree.scenes) # allow non-root scenes to act as root scenes tree.scene2index[WeakRef(scene)] = 1 push!(tree.scenes, GLScene(scene)) @@ -155,9 +174,9 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In # Figure out where the scene should be inserted parent_index = tree.scene2index[WeakRef(parent)] insert_index = parent_index - @info insert_index + # @info insert_index while (index > 0) && (insert_index < length(tree.scenes)) - @info "loop" + # @info "loop" insert_index += 1 if tree.depth[insert_index] == tree.depth[parent_index] + 1 # found a child of parent @@ -172,13 +191,13 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In break end end - @info insert_index, tree.depth[insert_index], tree.depth[parent_index] + # @info insert_index, tree.depth[insert_index], tree.depth[parent_index] if index == 1 && insert_index == length(tree.scenes) insert_index += 1 elseif index != 0 error("Failed to find scene insertion index.") end - @info insert_index + # @info insert_index tree.scene2index[WeakRef(scene)] = insert_index insert!(tree.scenes, insert_index, GLScene(scene)) @@ -187,7 +206,8 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In return end -function delete_scene!(tree::GLSceneTree, scene::Scene) +function delete_scene!(screen, tree::GLSceneTree, scene::Scene) + @info "Deleting scene $(objectid(scene))" if haskey(tree.scene2index, WeakRef(scene)) # Delete all child scenes for child in scene.children @@ -215,30 +235,24 @@ function delete_scene!(tree::GLSceneTree, scene::Scene) # Remove screen from scene to avoid double-deletion filter!(x -> x !== screen, scene.current_screens) else - error("Cannot delete scene from tree - does not exist in tree.") + @debug("Cannot delete scene from tree - does not exist in tree.") end return end -# TODO: -# How integrate - -# function add_renderobject!(tree::GLSceneTree, scene::Scene, robj::RenderObject) -# if haskey(tree.scene2index, WeakRef(scene)) -# glscene = tree.scenes[tree.scene2index[WeakRef(scene)]] -# push!(glscene.renderobjects, robj) -# glscene.plot2robj[]::Dict{UInt64, RenderObject} # TODO: -# glscene.robj2plot[]::Dict{UInt32, Plot} # TODO: -# else -# error("Cannot insert renderobject if it's parent scene is not part of the rendered scene tree.") -# end -# end - function delete_plot!(tree::GLSceneTree, scene::Scene, plot::AbstractPlot) + @debug("Deleting plot $(objectid(plot))") + if haskey(tree, scene) - delete_plot!(tree[scene], plot) + glscene = tree[scene] + for atomic in Makie.collect_atomic_plots(plot) + robj_id = delete_plot!(glscene, atomic) + if robj_id != typemax(UInt32) + delete!(tree.robj2plot, robj_id) + end + end else - error("Cannot delete plot if its parent scene is not being shown.") + @debug("Cannot delete plot if its parent scene is not being shown.") end return end diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 50c47fe5919..7f009ace7a3 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -342,11 +342,15 @@ function cached_robj!(robj_func, screen, scene, plot::AbstractPlot) robj = robj_func(gl_attributes) get!(gl_attributes, :ssao, Observable(false)) + glscene.robj2plot[robj.id] = plot + screen.scene_tree.robj2plot[robj.id] = plot + return robj end push!(glscene.renderobjects, robj) + @info "Inserting robj $(robj.id) / atomic $(objectid(plot))" return robj end @@ -359,8 +363,9 @@ function Base.insert!(screen::Screen, scene::Scene, @nospecialize(x::Plot)) pollevents(screen, Makie.BackendTick) if isempty(x.plots) # if no plots inserted, this truly is an atomic draw_atomic(screen, scene, x) - + else + @info "Inserting plot $(objectid(x))" foreach(x.plots) do x # poll inside functions to make wait on compile less prominent pollevents(screen, Makie.BackendTick) diff --git a/GLMakie/src/picking.jl b/GLMakie/src/picking.jl index ab718ba7bcc..cceff3d1b2b 100644 --- a/GLMakie/src/picking.jl +++ b/GLMakie/src/picking.jl @@ -43,30 +43,29 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}) return SelectionID{Int}(0, 0) end -function Makie.pick(scene::Scene, screen::Screen, xy::Vec{2, Float64}) - sid = pick_native(screen, xy) - if haskey(screen.cache2plot, sid.id) - plot = screen.cache2plot[sid.id] - return (plot, sid.index) +function process_pick_id(screen::Screen, sid::SelectionID) + robj_id = UInt32(sid.id) + if haskey(screen.scene_tree.robj2plot, robj_id) + return (screen.scene_tree.robj2plot[robj_id], sid.index) else return (nothing, 0) end end +function Makie.pick(scene::Scene, screen::Screen, xy::Vec{2, Float64}) + return process_pick_id(screen, pick_native(screen, xy)) +end + function Makie.pick(scene::Scene, screen::Screen, rect::Rect2i) - map(pick_native(screen, rect)) do sid - if haskey(screen.cache2plot, sid.id) - (screen.cache2plot[sid.id], sid.index) - else - (nothing, sid.index) - end - end + return map(sid -> process_pick_id(screen, sid), pick_native(screen, rect)) end # Skips one set of allocations function Makie.pick_closest(scene::Scene, screen::Screen, xy, range) isopen(screen) || return (nothing, 0) + robj2plot = screen.scene_tree.robj2plot + w, h = size(scene) ((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) || return (nothing, 0) @@ -81,22 +80,20 @@ function Makie.pick_closest(scene::Scene, screen::Screen, xy, range) for i in 1:dx, j in 1:dy d = (x-i)^2 + (y-j)^2 sid = sids[i, j] - if (d < min_dist) && (sid.id > 0) && haskey(screen.cache2plot, sid.id) + if (d < min_dist) && (sid.id > 0) && haskey(robj2plot, sid.id) min_dist = d id = convert(SelectionID{Int}, sid) end end - if haskey(screen.cache2plot, id.id) - return (screen.cache2plot[id.id], id.index) - else - return (nothing, 0) - end + return process_pick_id(screen, id) end # Skips some allocations function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) isopen(screen) || return (nothing, 0) + robj2plot = screen.scene_tree.robj2plot + w, h = size(scene) if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) return Tuple{AbstractPlot, Int}[] @@ -107,7 +104,7 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) picks = pick_native(screen, Rect2i(x0, y0, dx, dy)) - selected = filter(x -> x.id > 0 && haskey(screen.cache2plot, x.id), unique(vec(picks))) + selected = filter(x -> x.id > 0 && haskey(robj2plot, x.id), unique(vec(picks))) distances = Float32[range^2 for _ in selected] x, y = xy .+ 1 .- Vec2f(x0, y0) for i in 1:dx, j in 1:dy @@ -124,5 +121,5 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) idxs = sortperm(distances) permute!(selected, idxs) - return map(id -> (screen.cache2plot[id.id], id.index), selected) + return map(id -> process_pick_id(screen, id), selected) end From 37c8b8d7ccea51ad33920ab82f9d289b9142cb47 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 17:25:06 +0200 Subject: [PATCH 12/26] fix double-empty, typo --- GLMakie/src/GLScene.jl | 6 +++--- GLMakie/src/screen.jl | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl index dbd94676333..8ba3bc150bf 100644 --- a/GLMakie/src/GLScene.jl +++ b/GLMakie/src/GLScene.jl @@ -65,9 +65,9 @@ function Base.show(io::IO, glscene::GLScene) ) end -function delete_plot!(glscene::GLScene, plot::AbstractPlot) - @info "deleting plot $(objectid(plot))" - @assert isempty(plot.plots) "Plot must be atomic" +function delete_plot!(glscene::GLScene, atomic::AbstractPlot) + @info "deleting plot $(objectid(atomic))" + @assert isempty(atomic.plots) "Plot must be atomic" if haskey(glscene.plot2robj, objectid(atomic)) @info "deleting atomic $(objectid(atomic))" robj = pop!(glscene.plot2robj, objectid(atomic)) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 3cb7bac6e7d..91ddd8e92d9 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -534,10 +534,12 @@ end function Base.delete!(screen::Screen, scene::Scene) delete_scene!(screen, screen.scene_tree, scene) + screen.requires_update = true return end function destroy!(rob::RenderObject) + @info "Destroying rob $(rob.id)" # These need explicit clean up because (some of) the source observables # remain when the plot is deleted. GLAbstraction.switch_context!(rob.context) @@ -580,8 +582,14 @@ function Base.empty!(screen::Screen) Makie.disconnect_screen(screen.root_scene, screen) delete!(screen, screen.root_scene) # this should wipe all scenes and plots screen.root_scene = nothing + elseif !isempty(screen.scene_tree.scenes) + @error "Root scene not defined but scenes are still connected" + else + @warn "Root scene already undefined" end + @assert isempty(screen.scene_tree) "Scenes did not get deleted from screen" + # @assert isempty(screen.renderlist) # @assert isempty(screen.cache2plot) # @assert isempty(screen.cache) @@ -849,7 +857,6 @@ function stop_renderloop!(screen::Screen; close_after_renderloop=screen.close_af c = screen.close_after_renderloop screen.close_after_renderloop = close_after_renderloop screen.stop_renderloop = true - screen.close_after_renderloop = c # stop_renderloop! may be called inside renderloop as part of close # in which case we should not wait for the task to finish (deadlock) @@ -858,6 +865,9 @@ function stop_renderloop!(screen::Screen; close_after_renderloop=screen.close_af # after done, we can set the task to nothing screen.rendertask = nothing end + + screen.close_after_renderloop = c + # else, we can't do that much in the rendertask itself return end From 0cbc022f7142f6bdca2779a3288b7d9cdadece52 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 18:10:54 +0200 Subject: [PATCH 13/26] try tooltip on overlay scene --- src/interaction/inspector.jl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/interaction/inspector.jl b/src/interaction/inspector.jl index 396a29c00e8..9c61cf05a58 100644 --- a/src/interaction/inspector.jl +++ b/src/interaction/inspector.jl @@ -181,7 +181,7 @@ end # TODO destructor? mutable struct DataInspector - root::Scene + scene::Scene attributes::Attributes temp_plots::Vector{AbstractPlot} @@ -202,7 +202,7 @@ end function cleanup(inspector::DataInspector) off.(inspector.obsfuncs) empty!(inspector.obsfuncs) - delete!(inspector.root, inspector.plot) + delete!(inspector.scene, inspector.plot) clear_temporary_plots!(inspector, inspector.selection) inspector end @@ -248,6 +248,8 @@ end function DataInspector(scene::Scene; priority = 100, kwargs...) parent = root(scene) @assert origin(viewport(parent)[]) == Vec2f(0) + scene = Scene(parent, viewport = parent.viewport, clear = false) + campixel!(scene) attrib_dict = Dict(kwargs) base_attrib = Attributes( @@ -271,13 +273,13 @@ function DataInspector(scene::Scene; priority = 100, kwargs...) _color = RGBAf(0,0,0,0), ) - plot = tooltip!(parent, Observable(Point2f(0)), text = Observable(""); visible=false, attrib_dict...) + plot = tooltip!(scene, Observable(Point2f(0)), text = Observable(""); visible=false, attrib_dict...) on(z -> translate!(plot, 0, 0, z), base_attrib.depth) notify(base_attrib.depth) - inspector = DataInspector(parent, plot, base_attrib) + inspector = DataInspector(scene, plot, base_attrib) - e = events(parent) + e = events(scene) f1 = on(_ -> on_hover(inspector), e.mouseposition, priority = priority) f2 = on(_ -> on_hover(inspector), e.scroll, priority = priority) @@ -297,7 +299,7 @@ end DataInspector(; kwargs...) = DataInspector(current_figure(); kwargs...) function on_hover(inspector) - parent = inspector.root + parent = inspector.scene lock(inspector.lock) do (inspector.attributes.enabled[] && is_mouseinside(parent)) || return Consume(false) @@ -405,7 +407,7 @@ end function update_tooltip_alignment!(inspector, proj_pos) inspector.plot[1][] = proj_pos - wx, wy = widths(viewport(inspector.root)[]) + wx, wy = widths(viewport(inspector.scene)[]) px, py = proj_pos placement = py < 0.75wy ? (:above) : (:below) @@ -549,7 +551,7 @@ function show_data(inspector::DataInspector, plot::Mesh, idx) scene = parent_scene(plot) bbox = boundingbox(plot) - proj_pos = Point2f(mouseposition_px(inspector.root)) + proj_pos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, proj_pos) if a.enable_indicators[] @@ -597,7 +599,7 @@ function show_data(inspector::DataInspector, plot::Surface, idx) a = inspector.attributes tt = inspector.plot - proj_pos = Point2f(mouseposition_px(inspector.root)) + proj_pos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, proj_pos) pos = position_on_plot(plot, idx, apply_transform = false) @@ -672,7 +674,7 @@ function show_imagelike(inspector, plot, name, edge_based) z end - proj_pos = Point2f(mouseposition_px(inspector.root)) + proj_pos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, proj_pos) if a.enable_indicators[] @@ -815,7 +817,7 @@ function show_data(inspector::DataInspector, plot::BarPlot, idx) tt = inspector.plot scene = parent_scene(plot) - proj_pos = Point2f(mouseposition_px(inspector.root)) + proj_pos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, proj_pos) if a.enable_indicators[] @@ -864,7 +866,7 @@ function show_data(inspector::DataInspector, plot::Arrows, idx, source) tt = inspector.plot pos = plot[1][][idx] - mpos = Point2f(mouseposition_px(inspector.root)) + mpos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, mpos) p = vec2string(pos) @@ -889,7 +891,7 @@ function show_data(inspector::DataInspector, plot::Contourf, idx, source::Mesh) idx = show_poly(inspector, plot, plot.plots[1], idx, source) level = plot.plots[1].color[][idx] - mpos = Point2f(mouseposition_px(inspector.root)) + mpos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, mpos) tt[1][] = mpos if to_value(get(plot, :inspector_label, automatic)) == automatic @@ -960,7 +962,7 @@ function show_data(inspector::DataInspector, plot::VolumeSlices, idx, child::Hea i, j, val = _pixelated_getindex(child[1][], child[2][], child[3][], pos, true) - proj_pos = Point2f(mouseposition_px(inspector.root)) + proj_pos = Point2f(mouseposition_px(inspector.scene)) update_tooltip_alignment!(inspector, proj_pos) tt[1][] = proj_pos @@ -1028,7 +1030,7 @@ function show_data(inspector::DataInspector, plot::Band, idx::Integer, mesh::Mes end # Update tooltip - update_tooltip_alignment!(inspector, mouseposition_px(inspector.root)) + update_tooltip_alignment!(inspector, mouseposition_px(inspector.scene)) if to_value(get(plot, :inspector_label, automatic)) == automatic P1 = apply_transform_and_model(mesh, P1, Point2f) From 2658f5efafae5b0df1da93911945c52b9225e27f Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 18:11:13 +0200 Subject: [PATCH 14/26] cleanup old code --- GLMakie/src/rendering.jl | 140 ++------------------------------------- 1 file changed, 6 insertions(+), 134 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index becebf081c7..559e304ebe7 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -35,7 +35,8 @@ function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers= # Draw scene by scene glEnable(GL_SCISSOR_TEST) - for glscene in glscenes + for (i, glscene) in enumerate(glscenes) + @info "Rendering scene $i" render_frame(screen, glscene) end glDisable(GL_SCISSOR_TEST) @@ -82,6 +83,7 @@ function render_frame(screen::Screen, glscene::GLScene) wh = round.(Int, screen.px_per_unit[] .* widths(glscene.viewport[])) @inbounds glViewport(mini[1], mini[2], wh[1], wh[2]) @inbounds glScissor(mini[1], mini[2], wh[1], wh[2]) + @info mini, wh # TODO: better solution # render buffers are (color, objectid, maybe position, maybe normals) @@ -100,7 +102,7 @@ function render_frame(screen::Screen, glscene::GLScene) glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer c = glscene.backgroundcolor[]::RGBAf a = alpha(c) - @info a * red(c), a * green(c), a * blue(c), 1f0 - a + # @info a * red(c), a * green(c), a * blue(c), 1f0 - a glClearColor(a * red(c), a * green(c), a * blue(c), 1f0 - a) glClear(GL_COLOR_BUFFER_BIT) @@ -169,143 +171,13 @@ function render_frame(screen::Screen, glscene::GLScene) return end -# function setup!(screen::Screen) -# glEnable(GL_SCISSOR_TEST) -# if isopen(screen) && !isnothing(screen.root_scene) -# ppu = screen.px_per_unit[] -# glScissor(0, 0, round.(Int, size(screen.root_scene) .* ppu)...) -# glClearColor(1, 1, 1, 1) -# glClear(GL_COLOR_BUFFER_BIT) -# for (id, scene) in screen.screens -# if scene.visible[] -# a = viewport(scene)[] -# rt = (round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) -# glViewport(rt...) -# if scene.clear[] -# c = scene.backgroundcolor[] -# glScissor(rt...) -# glClearColor(red(c), green(c), blue(c), alpha(c)) -# glClear(GL_COLOR_BUFFER_BIT) -# end -# end -# end -# end -# glDisable(GL_SCISSOR_TEST) -# return -# end - -# """ -# Renders a single frame of a `window` -# """ -# function render_frame(screen::Screen; resize_buffers=true) -# nw = to_native(screen) -# ShaderAbstractions.switch_context!(nw) -# function sortby(x) -# robj = x[3] -# plot = screen.cache2plot[robj.id] -# # TODO, use actual boundingbox -# return Makie.zvalue2d(plot) -# end -# zvals = sortby.(screen.renderlist) -# permute!(screen.renderlist, sortperm(zvals)) - -# # NOTE -# # The transparent color buffer is reused by SSAO and FXAA. Changing the -# # render order here may introduce artifacts because of that. - -# fb = screen.framebuffer -# if resize_buffers && !isnothing(screen.root_scene) -# ppu = screen.px_per_unit[] -# resize!(fb, round.(Int, ppu .* size(screen.root_scene))...) -# end - -# # prepare stencil (for sub-scenes) -# glBindFramebuffer(GL_FRAMEBUFFER, fb.id) -# glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) -# glClearColor(0, 0, 0, 0) -# glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - -# glDrawBuffer(fb.render_buffer_ids[1]) -# setup!(screen) -# glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) - -# # render with SSAO -# glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) -# GLAbstraction.render(screen) do robj -# return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) -# end -# # SSAO -# screen.postprocessors[1].render(screen) - -# # render no SSAO -# glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) -# # render all non ssao -# GLAbstraction.render(screen) do robj -# return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) -# end - -# # TRANSPARENT RENDER -# # clear sums to 0 -# glDrawBuffer(GL_COLOR_ATTACHMENT2) -# glClearColor(0, 0, 0, 0) -# glClear(GL_COLOR_BUFFER_BIT) -# # clear alpha product to 1 -# glDrawBuffer(GL_COLOR_ATTACHMENT3) -# glClearColor(1, 1, 1, 1) -# glClear(GL_COLOR_BUFFER_BIT) -# # draw -# glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) -# # Render only transparent objects -# GLAbstraction.render(screen) do robj -# return Bool(robj[:transparency][]) -# end - -# # TRANSPARENT BLEND -# screen.postprocessors[2].render(screen) - -# # FXAA -# screen.postprocessors[3].render(screen) - -# # transfer everything to the screen -# screen.postprocessors[4].render(screen) - -# return -# end - -function id2scene(screen, id1) - # TODO maybe we should use a different data structure - for (id2, scene) in screen.screens - id1 == id2 && return true, scene - end - return false, nothing -end - -# function GLAbstraction.render(filter_elem_func, screen::Screen) -# # Somehow errors in here get ignored silently!? -# try -# for (zindex, screenid, elem) in screen.renderlist -# filter_elem_func(elem)::Bool || continue - -# found, scene = id2scene(screen, screenid) -# found || continue -# scene.visible[] || continue -# ppu = screen.px_per_unit[] -# a = viewport(scene)[] -# glViewport(round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...) -# render(elem) -# end -# catch e -# @error "Error while rendering!" exception = e -# rethrow(e) -# end -# return -# end - function GLAbstraction.render(filter_elem_func, glscene::GLScene) # Somehow errors in here get ignored silently!? try for robj in glscene.renderobjects filter_elem_func(robj)::Bool || continue + robj.visible || continue + @info "Rendering robj $(robj.id)" render(robj) end catch e From d8752b75bae6f8c0146863954422efee73e9cc80 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Aug 2024 20:50:57 +0200 Subject: [PATCH 15/26] reenable fxaa --- GLMakie/assets/shader/postprocessing/fxaa.frag | 2 +- .../shader/postprocessing/postprocess.frag | 8 +++----- GLMakie/src/postprocessing.jl | 18 +++++++++++++++++- GLMakie/src/rendering.jl | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/GLMakie/assets/shader/postprocessing/fxaa.frag b/GLMakie/assets/shader/postprocessing/fxaa.frag index 816831105eb..6eb2335fa6a 100644 --- a/GLMakie/assets/shader/postprocessing/fxaa.frag +++ b/GLMakie/assets/shader/postprocessing/fxaa.frag @@ -1047,5 +1047,5 @@ void main(void) 0.0f, // FxaaFloat fxaaConsoleEdgeThresholdMin, FxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f) // FxaaFloat fxaaConsole360ConstDir, ).rgb; - fragment_color.a = 1.0; + fragment_color.a = 0.0; } diff --git a/GLMakie/assets/shader/postprocessing/postprocess.frag b/GLMakie/assets/shader/postprocessing/postprocess.frag index ff5e2bde304..a3807db2d94 100644 --- a/GLMakie/assets/shader/postprocessing/postprocess.frag +++ b/GLMakie/assets/shader/postprocessing/postprocess.frag @@ -21,17 +21,15 @@ bool unpack_bool(uint id) { void main(void) { + // color.a is transmittance, i.e. 1 - alpha vec4 color = texture(color_texture, frag_uv).rgba; - if(color.a <= 0){ - discard; - } - uint id = texture(object_ids, frag_uv).x; + // do tonemappings //opaque = linear_tone_mapping(color.rgb, 1.8); // linear color output fragment_color.rgb = color.rgb; // we store fxaa = true/false in highbit of the object id - if (unpack_bool(id)) { + if (unpack_bool(id) && (color.a < 1.0)) { fragment_color.a = dot(color.rgb, vec3(0.299, 0.587, 0.114)); // compute luma } else { // we disable fxaa by setting luma to 1 diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 64d8ad557c1..01510da16f5 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -30,6 +30,8 @@ end function OIT_postprocessor(framebuffer, shader_cache) + @info "Creating OIT postprocessor" + # Based on https://jcgt.org/published/0002/02/09/, see #1390 # OIT setup shader = LazyShader( @@ -84,6 +86,8 @@ end function ssao_postprocessor(framebuffer, shader_cache) + @info "Creating SSAO postprocessor" + # Add missing buffers if !haskey(framebuffer, :position) glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) @@ -183,6 +187,8 @@ end Returns a PostProcessor that handles fxaa. """ function fxaa_postprocessor(framebuffer, shader_cache) + @info "Creating FXAA postprocessor" + # Add missing buffers if !haskey(framebuffer, :color_luma) if !haskey(framebuffer, :HDR_color) @@ -219,7 +225,14 @@ function fxaa_postprocessor(framebuffer, shader_cache) :color_texture => getfallback(framebuffer, :color_luma, :HDR_color)[2], :RCPFrame => lift(rcpframe, framebuffer.resolution), ) - pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) + pass2 = RenderObject(data2, shader2, () -> begin + glDepthMask(GL_TRUE) + glDisable(GL_DEPTH_TEST) + glDisable(GL_CULL_FACE) + glEnable(GL_BLEND) + # keep transmittance from dst + glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ONE) + end, nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) color_id = framebuffer[:color][1] @@ -253,6 +266,8 @@ to pass in a reference to the framebuffer ID of the screen. If `nothing` is used (the default), 0 is used. """ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothing) + @info "Creating to screen postprocessor" + # draw color buffer shader = LazyShader( shader_cache, @@ -288,6 +303,7 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi end function destroy!(pp::PostProcessor) + @info "Destroying postprocessor" while !isempty(pp.robjs) destroy!(pop!(pp.robjs)) end diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 559e304ebe7..6846ecadfcd 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -162,7 +162,7 @@ function render_frame(screen::Screen, glscene::GLScene) screen.postprocessors[2].render(screen) # FXAA - # screen.postprocessors[3].render(screen) + screen.postprocessors[3].render(screen) # transfer everything to the screen # TODO: accumulation buffer would avoid viewport/scissor reset From 7d2af55f799b43b7326791e8ca6c9beeaf0793b5 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 18 Aug 2024 19:29:30 +0200 Subject: [PATCH 16/26] remove render_frame prints to avoid crash --- GLMakie/src/rendering.jl | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 6846ecadfcd..5e6687d0ae8 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -6,6 +6,8 @@ function render_frame(screen::Screen; resize_buffers=true) end function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers=true) + # WARNING: Prints\Task switches from within render_frame may crash Julia. + nw = to_native(screen) ShaderAbstractions.switch_context!(nw) @@ -33,10 +35,10 @@ function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers= glClearColor(0, 0, 0, 0) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + # TODO: Does this require a lock to avoid changes to the glscenes array? # Draw scene by scene glEnable(GL_SCISSOR_TEST) for (i, glscene) in enumerate(glscenes) - @info "Rendering scene $i" render_frame(screen, glscene) end glDisable(GL_SCISSOR_TEST) @@ -49,7 +51,6 @@ end function render_frame(screen::Screen, glscene::GLScene) # TODO: Not like this if glscene.scene === nothing - @warn "Parent scene unavailable" return end @@ -58,7 +59,6 @@ function render_frame(screen::Screen, glscene::GLScene) # if the scene doesn't have a visual impact we skip if !glscene.visible[] || (isempty(renderlist) && !clear) - @info "Skipped due to visible = $(glscene.visible[]) or empty renderlist $(isempty(renderlist)) && clear = $clear" return end @@ -83,7 +83,6 @@ function render_frame(screen::Screen, glscene::GLScene) wh = round.(Int, screen.px_per_unit[] .* widths(glscene.viewport[])) @inbounds glViewport(mini[1], mini[2], wh[1], wh[2]) @inbounds glScissor(mini[1], mini[2], wh[1], wh[2]) - @info mini, wh # TODO: better solution # render buffers are (color, objectid, maybe position, maybe normals) @@ -97,12 +96,10 @@ function render_frame(screen::Screen, glscene::GLScene) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) if glscene.clear[] - @info "clearing" # Draw background color glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer c = glscene.backgroundcolor[]::RGBAf a = alpha(c) - # @info a * red(c), a * green(c), a * blue(c), 1f0 - a glClearColor(a * red(c), a * green(c), a * blue(c), 1f0 - a) glClear(GL_COLOR_BUFFER_BIT) @@ -113,7 +110,6 @@ function render_frame(screen::Screen, glscene::GLScene) glClear(GL_COLOR_BUFFER_BIT) end else - @info "no clear" # TODO: maybe SSAO needs this cleared if we run it per scene...? glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer glClearColor(0, 0, 0, 1) @@ -177,7 +173,6 @@ function GLAbstraction.render(filter_elem_func, glscene::GLScene) for robj in glscene.renderobjects filter_elem_func(robj)::Bool || continue robj.visible || continue - @info "Rendering robj $(robj.id)" render(robj) end catch e From 253d9187de1cb6af1761ea66e89522dc683a2972 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 18 Aug 2024 23:30:30 +0200 Subject: [PATCH 17/26] skip post-processing for empty scenes --- GLMakie/src/rendering.jl | 126 ++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 5e6687d0ae8..af78890ef06 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -51,7 +51,8 @@ end function render_frame(screen::Screen, glscene::GLScene) # TODO: Not like this if glscene.scene === nothing - return + error("Does this actually happen?") + # return end clear = glscene.clear[]::Bool @@ -65,15 +66,6 @@ function render_frame(screen::Screen, glscene::GLScene) nw = to_native(screen) fb = screen.framebuffer # required - # z-sorting - function sortby(robj) - plot = glscene.robj2plot[robj.id] - # TODO, use actual boundingbox - return Makie.zvalue2d(plot) - end - zvals = sortby.(glscene.renderobjects) - permute!(glscene.renderobjects, sortperm(zvals)) - # Redundant? glBindFramebuffer(GL_FRAMEBUFFER, fb.id) @@ -84,17 +76,6 @@ function render_frame(screen::Screen, glscene::GLScene) @inbounds glViewport(mini[1], mini[2], wh[1], wh[2]) @inbounds glScissor(mini[1], mini[2], wh[1], wh[2]) - # TODO: better solution - # render buffers are (color, objectid, maybe position, maybe normals) - # color, objectid should only be cleared if glscene.clear == true - # position, normals are only relevant for SSAO but should be cleared - # TODO: make DEPTH clear optional? - # TODO: unpadded clears may create edge-artifacts with SSAO, FXAA - glBindFramebuffer(GL_FRAMEBUFFER, fb.id) - glDrawBuffers(length(fb.render_buffer_ids)-2, fb.render_buffer_ids[2:end]) - glClearColor(0, 0, 0, 0) - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - if glscene.clear[] # Draw background color glDrawBuffer(fb.render_buffer_ids[1]) # accumulation color buffer @@ -116,49 +97,72 @@ function render_frame(screen::Screen, glscene::GLScene) glClear(GL_COLOR_BUFFER_BIT) end - # NOTE - # The transparent color buffer is reused by SSAO and FXAA. Changing the - # render order here may introduce artifacts because of that. - - # TODO: clear SSAO buffers here? - # render with SSAO - glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) # activate all render outputs - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) - GLAbstraction.render(glscene) do robj - return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) - end - # SSAO postprocessor - # TODO: when moving postprocessors to Scene the postprocessor should not need any extra inputs - screen.postprocessors[1].render(screen, glscene.ssao, glscene.scene.value.camera.projection[]) - - # render no SSAO - glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) # color, objectid - # render all non ssao - GLAbstraction.render(glscene) do robj - return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) - end + # No need to render and run post processors if there are no render objects + if !isempty(glscene.renderobjects) + # TODO: better solution + # render buffers are (color, objectid, maybe position, maybe normals) + # color, objectid should only be cleared if glscene.clear == true + # position, normals are only relevant for SSAO but should be cleared + # TODO: make DEPTH clear optional? + # TODO: unpadded clears may create edge-artifacts with SSAO, FXAA + glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + glDrawBuffers(length(fb.render_buffer_ids)-2, fb.render_buffer_ids[3:end]) + glClearColor(0, 0, 0, 0) + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + + # z-sorting + function sortby(robj) + plot = glscene.robj2plot[robj.id] + # TODO, use actual boundingbox + return Makie.zvalue2d(plot) + end + zvals = sortby.(glscene.renderobjects) + permute!(glscene.renderobjects, sortperm(zvals)) + + # NOTE + # The transparent color buffer is reused by SSAO and FXAA. Changing the + # render order here may introduce artifacts because of that. + + # TODO: clear SSAO buffers here? + # render with SSAO + glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) # activate all render outputs + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) + GLAbstraction.render(glscene) do robj + return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) + end + # SSAO postprocessor + # TODO: when moving postprocessors to Scene the postprocessor should not need any extra inputs + screen.postprocessors[1].render(screen, glscene.ssao, glscene.scene.value.camera.projection[]) + + # render no SSAO + glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) # color, objectid + # render all non ssao + GLAbstraction.render(glscene) do robj + return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) + end - # TRANSPARENT RENDER - # clear sums to 0 - glDrawBuffer(GL_COLOR_ATTACHMENT2) # HDR color (i.e. 16 Bit precision) - glClearColor(0, 0, 0, 0) - glClear(GL_COLOR_BUFFER_BIT) - # clear alpha product to 1 - glDrawBuffer(GL_COLOR_ATTACHMENT3) # OIT weight buffer - glClearColor(1, 1, 1, 1) - glClear(GL_COLOR_BUFFER_BIT) - # draw - glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) # HDR color, objectid, OIT weight - # Render only transparent objects - GLAbstraction.render(glscene) do robj - return Bool(robj[:transparency][]) - end + # TRANSPARENT RENDER + # clear sums to 0 + glDrawBuffer(GL_COLOR_ATTACHMENT2) # HDR color (i.e. 16 Bit precision) + glClearColor(0, 0, 0, 0) + glClear(GL_COLOR_BUFFER_BIT) + # clear alpha product to 1 + glDrawBuffer(GL_COLOR_ATTACHMENT3) # OIT weight buffer + glClearColor(1, 1, 1, 1) + glClear(GL_COLOR_BUFFER_BIT) + # draw + glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) # HDR color, objectid, OIT weight + # Render only transparent objects + GLAbstraction.render(glscene) do robj + return Bool(robj[:transparency][]) + end - # TRANSPARENT BLEND - screen.postprocessors[2].render(screen) + # TRANSPARENT BLEND + screen.postprocessors[2].render(screen) - # FXAA - screen.postprocessors[3].render(screen) + # FXAA + screen.postprocessors[3].render(screen) + end # transfer everything to the screen # TODO: accumulation buffer would avoid viewport/scissor reset From f02954029993ba01904bbb2af4cb5976775c2b66 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 18 Aug 2024 23:35:07 +0200 Subject: [PATCH 18/26] don't detach scene on empty! --- src/scenes.jl | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/scenes.jl b/src/scenes.jl index a12941560c3..f338a992732 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -400,6 +400,7 @@ function getindex(scene::Scene, ::Type{OldAxis}) end function delete_scene!(scene::Scene) + # TODO: un-deprecate this and make this detach the scene from tree/backend? @warn "deprecated in favor of empty!(scene)" empty!(scene) return nothing @@ -410,9 +411,9 @@ function free(scene::Scene) for field in [:backgroundcolor, :viewport, :visible] Observables.clear(getfield(scene, field)) end - # for screen in copy(scene.current_screens) - # delete!(screen, scene) - # end + for screen in copy(scene.current_screens) + delete!(screen, scene) + end empty!(scene.current_screens) scene.parent = nothing return @@ -427,11 +428,6 @@ function Base.empty!(scene::Scene; free=false) delete!(scene, plot) end - # delete scene in backend - for screen in scene.current_screens - delete!(screen, scene) - end - # remove from parent if !isnothing(scene.parent) filter!(x-> x !== scene, scene.parent.children) From 81b5fd94952f12a9ea85dab7b41e65feb7cdcbc8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 01:04:57 +0200 Subject: [PATCH 19/26] update tests, + fixes + hide prints --- GLMakie/src/GLScene.jl | 54 ++++++++++-------------- GLMakie/src/drawing_primitives.jl | 4 +- GLMakie/src/postprocessing.jl | 10 ++--- GLMakie/src/screen.jl | 11 ++--- GLMakie/test/unit_tests.jl | 69 ++++++++++++++++++++----------- src/scenes.jl | 13 +++--- 6 files changed, 84 insertions(+), 77 deletions(-) diff --git a/GLMakie/src/GLScene.jl b/GLMakie/src/GLScene.jl index 8ba3bc150bf..284df1a2bf0 100644 --- a/GLMakie/src/GLScene.jl +++ b/GLMakie/src/GLScene.jl @@ -65,28 +65,6 @@ function Base.show(io::IO, glscene::GLScene) ) end -function delete_plot!(glscene::GLScene, atomic::AbstractPlot) - @info "deleting plot $(objectid(atomic))" - @assert isempty(atomic.plots) "Plot must be atomic" - if haskey(glscene.plot2robj, objectid(atomic)) - @info "deleting atomic $(objectid(atomic))" - robj = pop!(glscene.plot2robj, objectid(atomic)) - - filter!(x -> x !== robj, glscene.renderobjects) - delete!(glscene.robj2plot, robj.id) - delete!(glscene.plot2robj, objectid(atomic)) - - destroy!(robj) - - return robj.id - else - # TODO: Should hard-error? - @debug("Cannot delete plot which is not part of the glscene. $atomic") - return typemax(UInt32) - end -end - - # TODO: name? should we keep this separate from Screen? struct GLSceneTree @@ -111,6 +89,7 @@ end GLSceneTree() = GLSceneTree(Dict{WeakRef, Int}(), Dict{UInt32, AbstractPlot}(), GLScene[], Int[]) function gc_cleanup(tree::GLSceneTree) + @debug "WeakRef cleanup" # TODO: do we need this? Can this create orphaned child scenes? for k in copy(keys(tree.scene2index)) if k === nothing @@ -146,7 +125,7 @@ end function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Nothing, index::Integer) # a root scene can only be added if the screen does not already have a root scene - @info "Inserting root scene $(objectid(scene))" + @debug "Inserting root scene $(objectid(scene))" if isempty(tree.scenes) tree.scene2index[WeakRef(scene)] = 1 push!(tree.scenes, GLScene(scene)) @@ -159,7 +138,7 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Nothing, index:: end function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::Integer) - @info "Inserting scene $(objectid(scene))" + @debug "Inserting scene $(objectid(scene))" if isempty(tree.scenes) # allow non-root scenes to act as root scenes tree.scene2index[WeakRef(scene)] = 1 push!(tree.scenes, GLScene(scene)) @@ -174,9 +153,9 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In # Figure out where the scene should be inserted parent_index = tree.scene2index[WeakRef(parent)] insert_index = parent_index - # @info insert_index + # @debug insert_index while (index > 0) && (insert_index < length(tree.scenes)) - # @info "loop" + # @debug "loop" insert_index += 1 if tree.depth[insert_index] == tree.depth[parent_index] + 1 # found a child of parent @@ -191,13 +170,13 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In break end end - # @info insert_index, tree.depth[insert_index], tree.depth[parent_index] + # @debug insert_index, tree.depth[insert_index], tree.depth[parent_index] if index == 1 && insert_index == length(tree.scenes) insert_index += 1 elseif index != 0 error("Failed to find scene insertion index.") end - # @info insert_index + # @debug insert_index tree.scene2index[WeakRef(scene)] = insert_index insert!(tree.scenes, insert_index, GLScene(scene)) @@ -207,7 +186,7 @@ function insert_scene!(tree::GLSceneTree, scene::Scene, parent::Scene, index::In end function delete_scene!(screen, tree::GLSceneTree, scene::Scene) - @info "Deleting scene $(objectid(scene))" + @debug "Deleting scene $(objectid(scene))" if haskey(tree.scene2index, WeakRef(scene)) # Delete all child scenes for child in scene.children @@ -219,7 +198,10 @@ function delete_scene!(screen, tree::GLSceneTree, scene::Scene) # Clean up RenderObjects (maps should get deleted with GLScene struct) glscene = tree.scenes[index] - foreach(destroy!, glscene.renderobjects) + foreach(glscene.renderobjects) do robj + delete!(tree.robj2plot, robj.id) + destroy!(robj) + end # Remove GLScene (TODO: rendering parameters should not need cleanup? ) deleteat!(tree.scenes, index) @@ -246,9 +228,15 @@ function delete_plot!(tree::GLSceneTree, scene::Scene, plot::AbstractPlot) if haskey(tree, scene) glscene = tree[scene] for atomic in Makie.collect_atomic_plots(plot) - robj_id = delete_plot!(glscene, atomic) - if robj_id != typemax(UInt32) - delete!(tree.robj2plot, robj_id) + if haskey(glscene.plot2robj, objectid(atomic)) + @debug "deleting atomic $(objectid(atomic))" + robj = pop!(glscene.plot2robj, objectid(atomic)) + + delete!(tree.robj2plot, robj.id) + delete!(glscene.robj2plot, robj.id) + + filter!(x -> x !== robj, glscene.renderobjects) + destroy!(robj) end end else diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 7f009ace7a3..826970700c5 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -350,7 +350,7 @@ function cached_robj!(robj_func, screen, scene, plot::AbstractPlot) end push!(glscene.renderobjects, robj) - @info "Inserting robj $(robj.id) / atomic $(objectid(plot))" + @debug "Inserting robj $(robj.id) / atomic $(objectid(plot))" return robj end @@ -365,7 +365,7 @@ function Base.insert!(screen::Screen, scene::Scene, @nospecialize(x::Plot)) draw_atomic(screen, scene, x) else - @info "Inserting plot $(objectid(x))" + @debug "Inserting plot $(objectid(x))" foreach(x.plots) do x # poll inside functions to make wait on compile less prominent pollevents(screen, Makie.BackendTick) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 01510da16f5..e15272d4730 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -30,7 +30,7 @@ end function OIT_postprocessor(framebuffer, shader_cache) - @info "Creating OIT postprocessor" + @debug "Creating OIT postprocessor" # Based on https://jcgt.org/published/0002/02/09/, see #1390 # OIT setup @@ -86,7 +86,7 @@ end function ssao_postprocessor(framebuffer, shader_cache) - @info "Creating SSAO postprocessor" + @debug "Creating SSAO postprocessor" # Add missing buffers if !haskey(framebuffer, :position) @@ -187,7 +187,7 @@ end Returns a PostProcessor that handles fxaa. """ function fxaa_postprocessor(framebuffer, shader_cache) - @info "Creating FXAA postprocessor" + @debug "Creating FXAA postprocessor" # Add missing buffers if !haskey(framebuffer, :color_luma) @@ -266,7 +266,7 @@ to pass in a reference to the framebuffer ID of the screen. If `nothing` is used (the default), 0 is used. """ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothing) - @info "Creating to screen postprocessor" + @debug "Creating to screen postprocessor" # draw color buffer shader = LazyShader( @@ -303,7 +303,7 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi end function destroy!(pp::PostProcessor) - @info "Destroying postprocessor" + @debug "Destroying postprocessor" while !isempty(pp.robjs) destroy!(pop!(pp.robjs)) end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 91ddd8e92d9..18f14934e5f 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -539,7 +539,7 @@ function Base.delete!(screen::Screen, scene::Scene) end function destroy!(rob::RenderObject) - @info "Destroying rob $(rob.id)" + @debug "Destroying rob $(rob.id)" # These need explicit clean up because (some of) the source observables # remain when the plot is deleted. GLAbstraction.switch_context!(rob.context) @@ -588,11 +588,12 @@ function Base.empty!(screen::Screen) @warn "Root scene already undefined" end - @assert isempty(screen.scene_tree) "Scenes did not get deleted from screen" + # @assert isempty(screen.scene_tree) "Scenes did not get deleted from screen" - # @assert isempty(screen.renderlist) - # @assert isempty(screen.cache2plot) - # @assert isempty(screen.cache) + @assert isempty(screen.scene_tree.scenes) "Scenes did not get cleaned up" + @assert isempty(screen.scene_tree.depth) "Scene tree depth did not get cleaned up" + @assert isempty(screen.scene_tree.scene2index) "Makie.Scene map did not get cleaned up" + @assert isempty(screen.scene_tree.robj2plot) "global robj2plot did not get cleaned up." # empty!(screen.screen2scene) # empty!(screen.screens) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 331c813a996..53b37305607 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -145,13 +145,15 @@ end @test screen in fig.scene.current_screens @test length(fig.scene.current_screens) == 1 @testset "all got freed" begin - for (_, _, robj) in screen.renderlist - for (k, v) in robj.uniforms - if v isa GLMakie.GPUArray - @test v.id == 0 + for glscene in screen.scene_tree.scenes + for robj in glscene.renderobjects + for (k, v) in robj.uniforms + if v isa GLMakie.GPUArray + @test v.id == 0 + end end + @test robj.vertexarray.id == 0 end - @test robj.vertexarray.id == 0 end end ax = Axis(fig[1,1]) @@ -159,17 +161,22 @@ end lines!(ax, 1:5, rand(5); linewidth=3) text!(ax, [Point2f(2)], text=["hi"]) @testset "no freed object after replotting" begin - for (_, _, robj) in screen.renderlist - for (k, v) in robj.uniforms - if v isa GLMakie.GPUArray - @test v.id != 0 + for glscene in screen.scene_tree.scenes + for robj in glscene.renderobjects + for (k, v) in robj.uniforms + if v isa GLMakie.GPUArray + @test v.id != 0 + end end + @test robj.vertexarray.id != 0 end - @test robj.vertexarray.id != 0 end end close(screen) - @test isempty(screen.renderlist) + @test isempty(screen.scene_tree.scenes) + @test isempty(screen.scene_tree.depth) + @test isempty(screen.scene_tree.scene2index) + @test isempty(screen.scene_tree.robj2plot) end @testset "empty!(ax)" begin @@ -183,7 +190,14 @@ end @test ax.scene.plots == [hmp, lp, tp] - robjs = map(x-> screen.cache[objectid(x)], [hmp, lp, tp.plots...]) + robjs = map([hmp, lp, tp.plots...]) do plot + for glscene in screen.scene_tree.scenes + if haskey(glscene, plot) + return glscene[plot] # TODO: do we keep this syntax? + end + end + error("Failed to retrieve robj") + end empty!(ax) @@ -201,17 +215,22 @@ end lines!(ax, 1:5, rand(5); linewidth=3) text!(ax, [Point2f(2)], text=["hi"]) @testset "no freed object after replotting" begin - for (_, _, robj) in screen.renderlist - for (k, v) in robj.uniforms - if v isa GLMakie.GPUArray - @test v.id != 0 + for glscene in screen.scene_tree.scenes + for robj in glscene.renderobjects + for (k, v) in robj.uniforms + if v isa GLMakie.GPUArray + @test v.id != 0 + end end + @test robj.vertexarray.id != 0 end - @test robj.vertexarray.id != 0 end end close(screen) - @test isempty(screen.renderlist) + @test isempty(screen.scene_tree.scenes) + @test isempty(screen.scene_tree.depth) + @test isempty(screen.scene_tree.scene2index) + @test isempty(screen.scene_tree.robj2plot) end @testset "closing" begin @@ -277,11 +296,10 @@ end for screen in screens @test !isopen(screen) - @test isempty(screen.screen2scene) - @test isempty(screen.screens) - @test isempty(screen.renderlist) - @test isempty(screen.cache) - @test isempty(screen.cache2plot) + @test isempty(screen.scene_tree.scene2index) + @test isempty(screen.scene_tree.robj2plot) + @test isempty(screen.scene_tree.scenes) + @test isempty(screen.scene_tree.depth) @test isempty(screen.window_open.listeners) @test isempty(screen.render_tick.listeners) @@ -451,13 +469,14 @@ end lines!(ax,sin.(0.0:0.1:2pi)) text!(ax,10.0,0.0,text="sine wave") empty!(ax) - ids = [robj.id for (_, _, robj) in screen.renderlist] + ids = [robj.id for glscene in screen.scene_tree.scenes for robj in glscene.renderobjects] lines!(ax, sin.(0.0:0.1:2pi)) text!(ax,10.0,0.0,text="sine wave") resize!(current_figure(), 800, 800) - robj = filter(x -> !(x.id in ids), last.(screen.renderlist))[1] + all_robjs = [robj for glscene in screen.scene_tree.scenes for robj in glscene.renderobjects] + robj = filter(x -> !(x.id in ids), all_robjs)[1] cam = ax.scene.camera @test robj.uniforms[:resolution][] == screen.px_per_unit[] * cam.resolution[] diff --git a/src/scenes.jl b/src/scenes.jl index f338a992732..2b2e749972e 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -414,25 +414,24 @@ function free(scene::Scene) for screen in copy(scene.current_screens) delete!(screen, scene) end + # remove from parent + if !isnothing(scene.parent) + filter!(x-> x !== scene, scene.parent.children) + end empty!(scene.current_screens) scene.parent = nothing return end function Base.empty!(scene::Scene; free=false) - # clear all child scenes - foreach(empty!, copy(scene.children)) + # since we clear our children all of our children need to get fully detached + foreach(Makie.free, copy(scene.children)) # clear plots of this scene for plot in copy(scene.plots) delete!(scene, plot) end - # remove from parent - if !isnothing(scene.parent) - filter!(x-> x !== scene, scene.parent.children) - end - empty!(scene.children) empty!(scene.plots) empty!(scene.theme) From a7ce6d48d0cc1cb9682e1a6d1b899492ce3fef85 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 17:36:04 +0200 Subject: [PATCH 20/26] add composition buffer to fix colorbuffer and allow alpha --- GLMakie/src/glwindow.jl | 35 ++++++++++----- GLMakie/src/postprocessing.jl | 81 +++++++++++++++++++++++++++++------ GLMakie/src/rendering.jl | 31 +++++--------- GLMakie/src/screen.jl | 44 +++++++++++-------- 4 files changed, 127 insertions(+), 64 deletions(-) diff --git a/GLMakie/src/glwindow.jl b/GLMakie/src/glwindow.jl index 7901b34c074..267ac105187 100644 --- a/GLMakie/src/glwindow.jl +++ b/GLMakie/src/glwindow.jl @@ -92,8 +92,8 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) glBindFramebuffer(GL_FRAMEBUFFER, frambuffer_id) # Buffers we always need - # Holds the image that eventually gets displayed - color_buffer = Texture( + # Holds the combined image of all plots which eventually gets displayed + composition_buffer = Texture( RGBA{N0f8}, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge ) # Holds a (plot id, element id) for point picking @@ -107,6 +107,11 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) internalformat = GL_DEPTH24_STENCIL8, format = GL_DEPTH_STENCIL ) + + # Holds the (temporary) render of a scene + color_buffer = Texture( + RGBA{N0f8}, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge + ) # Order Independent Transparency HDR_color_buffer = Texture( RGBA{Float16}, fb_size, minfilter = :linear, x_repeat = :clamp_to_edge @@ -115,10 +120,14 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) N0f8, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge ) - attach_framebuffer(color_buffer, GL_COLOR_ATTACHMENT0) + # Global Buffers + attach_framebuffer(composition_buffer, GL_COLOR_ATTACHMENT0) attach_framebuffer(objectid_buffer, GL_COLOR_ATTACHMENT1) - attach_framebuffer(HDR_color_buffer, GL_COLOR_ATTACHMENT2) - attach_framebuffer(OIT_weight_buffer, GL_COLOR_ATTACHMENT3) + + # Scene local buffers + attach_framebuffer(color_buffer, GL_COLOR_ATTACHMENT2) + attach_framebuffer(HDR_color_buffer, GL_COLOR_ATTACHMENT3) + attach_framebuffer(OIT_weight_buffer, GL_COLOR_ATTACHMENT4) attach_framebuffer(depth_buffer, GL_DEPTH_ATTACHMENT) attach_framebuffer(depth_buffer, GL_STENCIL_ATTACHMENT) @@ -130,26 +139,30 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) # track of the buffer ids that are already in use. We may also want to reuse # buffers so we give them names for easy fetching. buffer_ids = Dict{Symbol,GLuint}( - :color => GL_COLOR_ATTACHMENT0, + :composition => GL_COLOR_ATTACHMENT0, :objectid => GL_COLOR_ATTACHMENT1, - :HDR_color => GL_COLOR_ATTACHMENT2, - :OIT_weight => GL_COLOR_ATTACHMENT3, + + :color => GL_COLOR_ATTACHMENT2, + :HDR_color => GL_COLOR_ATTACHMENT3, + :OIT_weight => GL_COLOR_ATTACHMENT4, :depth => GL_DEPTH_ATTACHMENT, :stencil => GL_STENCIL_ATTACHMENT, ) buffers = Dict{Symbol, Texture}( - :color => color_buffer, + :composition => composition_buffer, :objectid => objectid_buffer, + + :color => color_buffer, :HDR_color => HDR_color_buffer, :OIT_weight => OIT_weight_buffer, :depth => depth_buffer, - :stencil => depth_buffer + :stencil => depth_buffer, ) return GLFramebuffer( fb_size_node, frambuffer_id, buffer_ids, buffers, - [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1] + [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1] )::GLFramebuffer end diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index e15272d4730..bb045c85bd4 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -63,8 +63,8 @@ function OIT_postprocessor(framebuffer, shader_cache) # blending (here) # src = out = (pre-multiplied color, transmittance) # dst = (pre-multiplied color, transmittance) (from non-OIT draw) - # src.rgb + dst.rgb, src.a * dst.a - glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA) + # src.rgb + src.a * dst.rgb, src.a * dst.a + glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA) end, nothing ) @@ -257,13 +257,55 @@ function fxaa_postprocessor(framebuffer, shader_cache) end +""" + compose_postprocessor(framebuffer, shader_cache, default_id = nothing) + +Creates a Postprocessor for merging the finished render of a scene with the +composition buffer which will eventually include all scenes. +""" +function compose_postprocessor(framebuffer, shader_cache) + @debug "Creating compose postprocessor" + + # draw color buffer + shader = LazyShader( + shader_cache, + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/copy.frag") + ) + data = Dict{Symbol, Any}( + :color_texture => framebuffer[:color][2] + ) + pass = RenderObject(data, shader, () -> begin + glDepthMask(GL_TRUE) + glDisable(GL_DEPTH_TEST) + glDisable(GL_CULL_FACE) + glEnable(GL_BLEND) + # src format = (a1 * rgb1, 1-a1) + # dst format = (a2 * rgb2, 1-a2) + # blended = (a1 * rgb1 + (1-a1) * a2 * rgb2, (1-a1) * (1-a2)) + glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA) + end, nothing) + pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) + + composition_buffer_id = framebuffer[:composition][1] + full_render = (screen, x, y, w, h) -> begin + glDrawBuffer(composition_buffer_id) + wh = framebuffer_size(to_native(screen)) + glViewport(0, 0, wh[1], wh[2]) # :color buffer is ful screen + glScissor(x, y, w, h) # but we only care about the active section + GLAbstraction.render(pass) # copy postprocess + end + + PostProcessor(RenderObject[pass], full_render, to_screen_postprocessor) +end + """ to_screen_postprocessor(framebuffer, shader_cache, default_id = nothing) -Sets up a Postprocessor which copies the color buffer to the screen. Used as a -final step for displaying the screen. The argument `screen_fb_id` can be used -to pass in a reference to the framebuffer ID of the screen. If `nothing` is -used (the default), 0 is used. +Creates a Postprocessor which maps the composed render of all scenes to the +screen. Used as a final step for displaying the screen. The argument +`screen_fb_id` can be used to pass in a reference to the framebuffer ID of the +screen. If `nothing` is used (the default), 0 is used. """ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothing) @debug "Creating to screen postprocessor" @@ -275,27 +317,38 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi loadshader("postprocessing/copy.frag") ) data = Dict{Symbol, Any}( - :color_texture => framebuffer[:color][2] + :color_texture => framebuffer[:composition][2] ) pass = RenderObject(data, shader, () -> begin glDepthMask(GL_TRUE) glDisable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) glEnable(GL_BLEND) - # glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE) - # see GLRender.jl enabletransparency() + # incoming texture is in format src = (a * c, 1-a) + # destination is in format dst = (bg, 1) + # blends to (src.rgb + src.a * dst.rgb, 1 * dst.a) = (a * c + (1-a) * bg, 1) glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_ONE) end, nothing) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - full_render = (screen, x, y, w, h) -> begin - # transfer everything to the screen - default_id = isnothing(screen_fb_id) ? 0 : screen_fb_id[] + full_render = (screen) -> begin + # TODO: Is this an observable? Can this be static? # GLFW uses 0, Gtk uses a value that we have to probe at the beginning of rendering - glBindFramebuffer(GL_FRAMEBUFFER, default_id) + OUTPUT_FRAMEBUFFER_ID = isnothing(screen_fb_id) ? 0 : screen_fb_id[] + + # Set target + glBindFramebuffer(GL_FRAMEBUFFER, OUTPUT_FRAMEBUFFER_ID) wh = framebuffer_size(to_native(screen)) glViewport(0, 0, wh[1], wh[2]) - glScissor(x, y, w, h) + glScissor(0, 0, wh[1], wh[2]) + + # clear target + # TODO: Could be skipped if glscenes[1] clears to opaque (maybe use dedicated shader?) + # TODO: Should this be cleared if we don't own the target? + glClearColor(1,0,0,1) # DEBUG color + glClear(GL_COLOR_BUFFER_BIT) + + # transfer everything to the screen GLAbstraction.render(pass) # copy postprocess end diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index af78890ef06..b0e2fb535ae 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -18,23 +18,12 @@ function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers= resize!(fb, round.(Int, ppu .* size(screen.root_scene))...) end - # Clear final output - # TODO: Could be skipped if glscenes[1] clears - OUTPUT_FRAMEBUFFER_ID = 0 - glBindFramebuffer(GL_FRAMEBUFFER, OUTPUT_FRAMEBUFFER_ID) - wh = framebuffer_size(nw) - glViewport(0, 0, wh[1], wh[2]) - - glClearColor(1,0,0,1) - glClear(GL_COLOR_BUFFER_BIT) - - # Reset all intermediate buffers - # TODO: really just need color, objectid here? (additionals clear per scene) + # Clear global buffers (color composition, objectid) glBindFramebuffer(GL_FRAMEBUFFER, fb.id) - glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) + glDrawBuffers(2, [fb[:composition][1], fb[:objectid][1]]) # TODO: avoid alloc? glClearColor(0, 0, 0, 0) - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - + glClear(GL_COLOR_BUFFER_BIT) + # TODO: Does this require a lock to avoid changes to the glscenes array? # Draw scene by scene glEnable(GL_SCISSOR_TEST) @@ -43,8 +32,8 @@ function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers= end glDisable(GL_SCISSOR_TEST) - # TODO: copy accumulation buffer to screen buffer - # screen.postprocessors[4].render(screen) + # Clear final output and draw composited scene + screen.postprocessors[end].render(screen) end @@ -135,7 +124,7 @@ function render_frame(screen::Screen, glscene::GLScene) screen.postprocessors[1].render(screen, glscene.ssao, glscene.scene.value.camera.projection[]) # render no SSAO - glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) # color, objectid + glDrawBuffers(2, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1]) # color, objectid # render all non ssao GLAbstraction.render(glscene) do robj return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) @@ -143,15 +132,15 @@ function render_frame(screen::Screen, glscene::GLScene) # TRANSPARENT RENDER # clear sums to 0 - glDrawBuffer(GL_COLOR_ATTACHMENT2) # HDR color (i.e. 16 Bit precision) + glDrawBuffer(GL_COLOR_ATTACHMENT3) # HDR color (i.e. 16 Bit precision) glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) # clear alpha product to 1 - glDrawBuffer(GL_COLOR_ATTACHMENT3) # OIT weight buffer + glDrawBuffer(GL_COLOR_ATTACHMENT4) # OIT weight buffer glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) # draw - glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) # HDR color, objectid, OIT weight + glDrawBuffers(3, [GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT4]) # HDR color, objectid, OIT weight # Render only transparent objects GLAbstraction.render(glscene) do robj return Bool(robj[:transparency][]) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 18f14934e5f..547f0e3d9e2 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -177,7 +177,7 @@ mutable struct Screen{GLWindow} <: MakieScreen scene_tree::GLSceneTree postprocessors::Vector{PostProcessor} - framecache::Matrix{RGB{N0f8}} + framecache::Matrix{RGBA{N0f8}} render_tick::Observable{Makie.TickState} # listeners must not Consume(true) window_open::Observable{Bool} scalefactor::Observable{Float32} @@ -284,6 +284,7 @@ function empty_screen(debugging::Bool; reuse=true, window=nothing) empty_postprocessor(), empty_postprocessor(), empty_postprocessor(), + compose_postprocessor(fb, shader_cache), to_screen_postprocessor(fb, shader_cache) ] @@ -699,10 +700,18 @@ function Base.resize!(screen::Screen, w::Int, h::Int) return nothing end -function fast_color_data!(dest::Array{RGB{N0f8}, 2}, source::Texture{T, 2}) where T +# function fast_color_data!(dest::Array{RGB{N0f8}, 2}, source::Texture{T, 2}) where T +# GLAbstraction.bind(source) +# glPixelStorei(GL_PACK_ALIGNMENT, 1) +# glGetTexImage(source.texturetype, 0, GL_RGB, GL_UNSIGNED_BYTE, dest) +# GLAbstraction.bind(source, 0) +# return +# end + +function fast_color_data!(dest::Array{RGBA{N0f8}, 2}, source::Texture{T, 2}) where T GLAbstraction.bind(source) glPixelStorei(GL_PACK_ALIGNMENT, 1) - glGetTexImage(source.texturetype, 0, GL_RGB, GL_UNSIGNED_BYTE, dest) + glGetTexImage(source.texturetype, 0, GL_RGBA, GL_UNSIGNED_BYTE, dest) GLAbstraction.bind(source, 0) return end @@ -740,7 +749,7 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma error("Screen not open!") end ShaderAbstractions.switch_context!(screen.glscreen) - ctex = screen.framebuffer.buffers[:color] + ctex = screen.framebuffer.buffers[:composition] # polling may change window size, when its bigger than monitor! # we still need to poll though, to get all the newest events! pollevents(screen, Makie.BackendTick) @@ -757,6 +766,18 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma screen.framecache = Matrix{RGB{N0f8}}(undef, size(ctex)) end fast_color_data!(screen.framecache, ctex) + + # remap (a * rgb, 1-a) format to standard (rgb, a) + @inbounds for i in eachindex(screen.framecache) + c = RGBA{Float32}(screen.framecache[i]) + a = 1 - alpha(c) + inv_a = 1 / a + r = clamp(inv_a * red(c), 0.0, 1.0) + g = clamp(inv_a * green(c), 0.0, 1.0) + b = clamp(inv_a * blue(c), 0.0, 1.0) + screen.framecache[i] = RGBA{N0f8}(r, g, b, a) + end + if format == Makie.GLNative return screen.framecache elseif format == Makie.JuliaNative @@ -765,20 +786,6 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma end end -# function Base.push!(screen::Screen, scene::Scene, robj) -# # filter out gc'ed elements -# filter!(screen.screen2scene) do (k, v) -# k.value !== nothing -# end -# screenid = get!(screen.screen2scene, WeakRef(scene)) do -# id = length(screen.screens) + 1 -# push!(screen.screens, (id, scene)) -# return id -# end -# push!(screen.renderlist, (0, screenid, robj)) -# return robj -# end - Makie.to_native(x::Screen) = x.glscreen """ @@ -805,6 +812,7 @@ function get_loading_image(resolution) return img end +# TODO: This is not in the right alpha-format anymore function display_loading_image(screen::Screen) fb = screen.framebuffer fbsize = size(fb) From efd35d64e97fd379d50041df48e2579fc58be604 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 17:53:18 +0200 Subject: [PATCH 21/26] bump memory usage check --- GLMakie/test/unit_tests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 53b37305607..d57091b3a49 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -308,7 +308,7 @@ end @test screen.root_scene === nothing @test screen.rendertask === nothing - @test (Base.summarysize(screen) / 10^6) < 1.4 + @test (Base.summarysize(screen) / 10^6) < 1.7 end # All should go to pool after close @test all(x-> x in GLMakie.SCREEN_REUSE_POOL, screens) From 5e522b7660274db3007c8d40f9260a2f27585a89 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 18:04:53 +0200 Subject: [PATCH 22/26] allow RGBA images to be tested --- ReferenceTests/src/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/ReferenceTests/src/runtests.jl b/ReferenceTests/src/runtests.jl index cb0d0672322..ff25b551831 100644 --- a/ReferenceTests/src/runtests.jl +++ b/ReferenceTests/src/runtests.jl @@ -42,6 +42,7 @@ function compare_images(a::AbstractMatrix{<:Union{RGB,RGBA}}, b::AbstractMatrix{ _norm(rgb1::RGBf, rgb2::RGBf) = sqrt(sum(((rgb1.r - rgb2.r)^2, (rgb1.g - rgb2.g)^2, (rgb1.b - rgb2.b)^2))) _norm(rgba1::RGBAf, rgba2::RGBAf) = sqrt(sum(((rgba1.r - rgba2.r)^2, (rgba1.g - rgba2.g)^2, (rgba1.b - rgba2.b)^2, (rgba1.alpha - rgba2.alpha)^2))) + _norm(rgba1::RGBAf, rgb2::RGBf) = sqrt(sum(((rgba1.r - rgb2.r)^2, (rgba1.g - rgb2.g)^2, (rgba1.b - rgb2.b)^2, (rgba1.alpha - 1)^2))) # compute the difference score as the maximum of the mean squared differences over the color # values of tiles over the image. using tiles is a simple way to increase the local sensitivity From c4c081705ea2384c984175dfd3b7e2f08cd76d7f Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 19:56:37 +0200 Subject: [PATCH 23/26] fix rendering for transparent root scenes --- GLMakie/src/postprocessing.jl | 2 +- GLMakie/src/rendering.jl | 2 +- GLMakie/src/screen.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index bb045c85bd4..3210b25560f 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -345,7 +345,7 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi # clear target # TODO: Could be skipped if glscenes[1] clears to opaque (maybe use dedicated shader?) # TODO: Should this be cleared if we don't own the target? - glClearColor(1,0,0,1) # DEBUG color + glClearColor(1,1,1,1) glClear(GL_COLOR_BUFFER_BIT) # transfer everything to the screen diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index b0e2fb535ae..4176317367b 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -21,7 +21,7 @@ function render_frame(screen::Screen, glscenes::Vector{GLScene}, resize_buffers= # Clear global buffers (color composition, objectid) glBindFramebuffer(GL_FRAMEBUFFER, fb.id) glDrawBuffers(2, [fb[:composition][1], fb[:objectid][1]]) # TODO: avoid alloc? - glClearColor(0, 0, 0, 0) + glClearColor(0, 0, 0, 1) glClear(GL_COLOR_BUFFER_BIT) # TODO: Does this require a lock to avoid changes to the glscenes array? diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 547f0e3d9e2..4312db1c166 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -771,7 +771,7 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma @inbounds for i in eachindex(screen.framecache) c = RGBA{Float32}(screen.framecache[i]) a = 1 - alpha(c) - inv_a = 1 / a + inv_a = ifelse(a == 0.0, 1, 1 / a) r = clamp(inv_a * red(c), 0.0, 1.0) g = clamp(inv_a * green(c), 0.0, 1.0) b = clamp(inv_a * blue(c), 0.0, 1.0) From 8150b729d44e33222ab4b87a83ec1e25e9c0dd8b Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 19 Aug 2024 20:24:36 +0200 Subject: [PATCH 24/26] fix px_per_unit --- GLMakie/src/postprocessing.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 3210b25560f..4f9294eea5e 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -73,7 +73,7 @@ function OIT_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin # Blend transparent onto opaque - wh = framebuffer_size(to_native(screen)) + wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) glDrawBuffer(color_id) GLAbstraction.render(pass) @@ -164,7 +164,7 @@ function ssao_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = (screen, ssao, projection) -> begin - wh = framebuffer_size(to_native(screen)) + wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) glDrawBuffer(normal_occ_id) # occlusion buffer @@ -238,7 +238,7 @@ function fxaa_postprocessor(framebuffer, shader_cache) color_id = framebuffer[:color][1] full_render = screen -> begin # TODO: make scissor explicit? - wh = framebuffer_size(to_native(screen)) + wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) # FXAA - calculate LUMA @@ -290,7 +290,7 @@ function compose_postprocessor(framebuffer, shader_cache) composition_buffer_id = framebuffer[:composition][1] full_render = (screen, x, y, w, h) -> begin glDrawBuffer(composition_buffer_id) - wh = framebuffer_size(to_native(screen)) + wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) # :color buffer is ful screen glScissor(x, y, w, h) # but we only care about the active section GLAbstraction.render(pass) # copy postprocess @@ -340,7 +340,7 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi glBindFramebuffer(GL_FRAMEBUFFER, OUTPUT_FRAMEBUFFER_ID) wh = framebuffer_size(to_native(screen)) glViewport(0, 0, wh[1], wh[2]) - glScissor(0, 0, wh[1], wh[2]) + # glScissor(0, 0, wh[1], wh[2]) # clear target # TODO: Could be skipped if glscenes[1] clears to opaque (maybe use dedicated shader?) From 3607f4a479134ac1a2f8de9a0c0593f4ddf6ddaf Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 20 Aug 2024 00:42:25 +0200 Subject: [PATCH 25/26] add overlay logic --- src/interaction/inspector.jl | 2 +- src/scenes.jl | 39 ++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/interaction/inspector.jl b/src/interaction/inspector.jl index 9c61cf05a58..d04f2e9120d 100644 --- a/src/interaction/inspector.jl +++ b/src/interaction/inspector.jl @@ -248,7 +248,7 @@ end function DataInspector(scene::Scene; priority = 100, kwargs...) parent = root(scene) @assert origin(viewport(parent)[]) == Vec2f(0) - scene = Scene(parent, viewport = parent.viewport, clear = false) + scene = Scene(parent, viewport = parent.viewport, clear = false, overlay = true) campixel!(scene) attrib_dict = Dict(kwargs) diff --git a/src/scenes.jl b/src/scenes.jl index 2b2e749972e..a36a122db64 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -78,6 +78,7 @@ mutable struct Scene <: AbstractScene "Children of the Scene inherit its transformation." children::Vector{Scene} + overlay_start::Int """ The Screens which the Scene is displayed to. @@ -105,6 +106,7 @@ mutable struct Scene <: AbstractScene plots::Vector{AbstractPlot}, theme::Attributes, children::Vector{Scene}, + overlay_start::Int, current_screens::Vector{MakieScreen}, backgroundcolor::Observable{RGBAf}, visible::Observable{Bool}, @@ -123,6 +125,7 @@ mutable struct Scene <: AbstractScene plots, theme, children, + overlay_start, current_screens, backgroundcolor, visible, @@ -207,6 +210,7 @@ function Scene(; transformation::Transformation = Transformation(transform_func), plots::Vector{AbstractPlot} = AbstractPlot[], children::Vector{Scene} = Scene[], + overlay_start::Int = length(children)+1, current_screens::Vector{MakieScreen} = MakieScreen[], parent = nothing, visible = Observable(true), @@ -244,7 +248,7 @@ function Scene(; scene = Scene( parent, events, viewport, clear, cam, camera_controls, transformation, plots, m_theme, - children, current_screens, bg, visible, ssao, _lights + children, overlay_start, current_screens, bg, visible, ssao, _lights ) camera isa Function && camera(scene) @@ -294,6 +298,7 @@ function Scene( camera=nothing, camera_controls=parent.camera_controls, transformation=Transformation(parent), + overlay::Bool = false, kw... ) @@ -322,7 +327,11 @@ function Scene( error("viewport must be an Observable{Rect2} or a Rect2") end end - push!(parent, child) + if overlay + push_overlay!(parent, child) + else + push!(parent, child) + end child.parent = parent return child end @@ -415,8 +424,18 @@ function free(scene::Scene) delete!(screen, scene) end # remove from parent - if !isnothing(scene.parent) - filter!(x-> x !== scene, scene.parent.children) + parent = scene.parent + if !isnothing(parent) + idx = findfirst(==(scene), parent.children) + if idx === nothing + @warn "Freed scene not a child of it's parent" + else + # if we are deletinng a normal scene we need to move the overlay start index + if !(parent.overlay_start <= idx <= length(parent.children)) + parent.overlay_start -= 1 + end + deleteat!(parent.children, idx) + end end empty!(scene.current_screens) scene.parent = nothing @@ -433,6 +452,7 @@ function Base.empty!(scene::Scene; free=false) end empty!(scene.children) + scene.overlay_start = 1 empty!(scene.plots) empty!(scene.theme) # conditional, since in free we dont want this! @@ -452,12 +472,23 @@ function Base.empty!(scene::Scene; free=false) end function Base.push!(parent::Scene, child::Scene) + @assert isempty(child.children) "Adding a scene with children to a parent not yet implemented" + insert!(parent.children, parent.overlay_start, child) + for screen in parent.current_screens + Base.invokelatest(insert!, screen, child, parent, parent.overlay_start) + end + parent.overlay_start += 1 + return +end + +function push_overlay!(parent::Scene, child::Scene) @assert isempty(child.children) "Adding a scene with children to a parent not yet implemented" push!(parent.children, child) idx = length(parent.children) for screen in parent.current_screens Base.invokelatest(insert!, screen, child, parent, idx) end + return end function Base.push!(plot::Plot, subplot) From eea9e228ccba09d6b67ce172f19ff51db94bd8b2 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 15 Nov 2024 16:56:13 +0100 Subject: [PATCH 26/26] fix merge error --- GLMakie/src/rendering.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 3f00c0ddbc9..bd51ec211b0 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -103,9 +103,8 @@ function render_frame(screen::Screen, glscene::GLScene) # TODO: only do this for 2D scenes # (3D and mixed scenes can't be correctly sorted based on world space z # shifts. Center of bbox would be better but needs caching) - function sortby(x) - robj = x[3] - plot = screen.cache2plot[robj.id] + function sortby(robj) + plot = glscene.robj2plot[robj.id] # TODO, use actual boundingbox # ~7% faster than calling zvalue2d doing the same thing? return Makie.transformationmatrix(plot)[][3, 4]