From 382a09cae64be93e00a65e6ae433bc88b00783f8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 23 Dec 2024 17:55:27 +0100 Subject: [PATCH 01/13] start reworking render pipeline --- GLMakie/src/postprocessing.jl | 267 ++++++++++++++++++++-------------- GLMakie/src/rendering.jl | 9 +- GLMakie/src/screen.jl | 35 ++--- 3 files changed, 175 insertions(+), 136 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 0195b040ae3..edbba263cc4 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -18,27 +18,50 @@ end rcpframe(x) = 1f0 ./ Vec2f(x[1], x[2]) -struct PostProcessor{F} - robjs::Vector{RenderObject} - render::F - constructor::Any +abstract type AbstractRenderStep end +prepare_step(screen, glscene, ::AbstractRenderStep) = nothing +run_step(screen, glscene, ::AbstractRenderStep) = nothing + +struct RenderPipeline + steps::Vector{AbstractRenderStep} +end + +function render_frame(screen, glscene, pipeline::RenderPipeline) + for step in pipeline.steps + prepare_step(screen, glscene, step) + end + for step in pipeline.steps + run_step(screen, glscene, step) + end + return end -function empty_postprocessor(args...; kwargs...) - PostProcessor(RenderObject[], screen -> nothing, empty_postprocessor) +# TODO: temporary, we should get to the point where this is not needed +struct EmptyRenderStep <: AbstractRenderStep end + +# Vaguely leaning on Vulkan Terminology +struct RenderPass{Name} <: AbstractRenderStep + framebuffer::GLFramebuffer + passes::Vector{RenderObject} + renames::Dict{Symbol, Symbol} # TODO: temporary until GLFramebuffer not shared +end +function RenderPass{Name}(framebuffer::GLFramebuffer, passes::Vector{RenderObject}) where {Name} + return RenderPass{Name}(framebuffer, passes, Dict{Symbol, Symbol}()) end +function RenderPass{:OIT}(screen) + @debug "Creating OIT postprocessor" + + framebuffer = screen.framebuffer -function OIT_postprocessor(framebuffer, shader_cache) # Based on https://jcgt.org/published/0002/02/09/, see #1390 # OIT setup shader = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/OIT_blend.frag") ) data = Dict{Symbol, Any}( - # :opaque_color => framebuffer[:color][2], :sum_color => framebuffer[:HDR_color][2], :prod_alpha => framebuffer[:OIT_weight][2], ) @@ -61,24 +84,24 @@ function OIT_postprocessor(framebuffer, shader_cache) ) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - 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 - - PostProcessor(RenderObject[pass], full_render, OIT_postprocessor) + return RenderPass{:OIT}(framebuffer, RenderObject[pass]) end +function run_step(screen, glscene, step::RenderPass{:OIT}) + # Blend transparent onto opaque + wh = size(screen.framebuffer) + glViewport(0, 0, wh[1], wh[2]) + glDrawBuffer(step.framebuffer[:color][1]) + GLAbstraction.render(step.passes[1]) + return +end +function RenderPass{:SSAO}(screen) + @debug "Creating SSAO postprocessor" + framebuffer = screen.framebuffer + renames = Dict{Symbol, Symbol}() -function ssao_postprocessor(framebuffer, shader_cache) # Add missing buffers if !haskey(framebuffer, :position) glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) @@ -95,8 +118,10 @@ function ssao_postprocessor(framebuffer, shader_cache) Vec4{Float16}, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge ) normal_occ_id = attach_colorbuffer!(framebuffer, :normal_occlusion, normal_occlusion_buffer) + renames[:normal_occlusion] = :normal_occlusion else normal_occ_id = framebuffer[:HDR_color][1] + renames[:normal_occlusion] = :HDR_color end push!(framebuffer.render_buffer_ids, normal_occ_id) end @@ -113,7 +138,7 @@ function ssao_postprocessor(framebuffer, shader_cache) # compute occlusion shader1 = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/SSAO.frag"), view = Dict( @@ -139,7 +164,7 @@ function ssao_postprocessor(framebuffer, shader_cache) # blur occlusion and combine with color shader2 = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/SSAO_blur.frag") ) @@ -152,57 +177,57 @@ function ssao_postprocessor(framebuffer, shader_cache) ) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) 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 - 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 - # 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) + return RenderPass{:SSAO}(framebuffer, [pass1, pass2], renames) +end + +function run_step(screen, glscene, step::RenderPass{:SSAO}) + wh = size(screen.framebuffer) + + glViewport(0, 0, wh[1], wh[2]) + glDrawBuffer(step.framebuffer[step.renames[:normal_occ_id]][1]) # occlusion buffer + glEnable(GL_SCISSOR_TEST) + ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) + + data1 = step.passes[1].uniforms + 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(step.passes[1]) end - PostProcessor(RenderObject[pass1, pass2], full_render, ssao_postprocessor) + # SSAO - blur occlusion and apply to color + glDrawBuffer(step.framebuffer[:color][1]) # color buffer + data2 = step.passes[2].uniforms + 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(step.passes[2]) + end + glDisable(GL_SCISSOR_TEST) + + return end -""" - fxaa_postprocessor(framebuffer, shader_cache) -Returns a PostProcessor that handles fxaa. -""" -function fxaa_postprocessor(framebuffer, shader_cache) +function RenderPass{:FXAA}(screen) + @debug "Creating FXAA postprocessor" + framebuffer = screen.framebuffer + renames = Dict{Symbol, Symbol}() + # Add missing buffers if !haskey(framebuffer, :color_luma) if !haskey(framebuffer, :HDR_color) @@ -211,14 +236,16 @@ function fxaa_postprocessor(framebuffer, shader_cache) RGBA{N0f8}, size(framebuffer), minfilter=:linear, x_repeat=:clamp_to_edge ) luma_id = attach_colorbuffer!(framebuffer, :color_luma, color_luma_buffer) + renames[:color_luma] = :color_luma else luma_id = framebuffer[:HDR_color][1] + renames[:color_luma] = :HDR_color end end # calculate luma for FXAA shader1 = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/postprocess.frag") ) @@ -231,7 +258,7 @@ function fxaa_postprocessor(framebuffer, shader_cache) # perform FXAA shader2 = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/fxaa.frag") ) @@ -242,40 +269,42 @@ function fxaa_postprocessor(framebuffer, shader_cache) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - 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) - GLAbstraction.render(pass1) - - # FXAA - perform anti-aliasing - glDrawBuffer(color_id) # color buffer - GLAbstraction.render(pass2) - end + return RenderPass{:FXAA}(framebuffer, RenderObject[pass1, pass2], renames) +end - PostProcessor(RenderObject[pass1, pass2], full_render, fxaa_postprocessor) +function run_step(screen, glscene, step::RenderPass{:FXAA}) + # TODO: make scissor explicit? + wh = size(screen.framebuffer) + glViewport(0, 0, wh[1], wh[2]) + + # FXAA - calculate LUMA + glDrawBuffer(step.framebuffer[step.renames[:color_luma]][1]) + # necessary with negative SSAO bias... + glClearColor(1, 1, 1, 1) + glClear(GL_COLOR_BUFFER_BIT) + GLAbstraction.render(step.passes[1]) + + # FXAA - perform anti-aliasing + glDrawBuffer(step.framebuffer[:color][1]) # color buffer + GLAbstraction.render(step.passes[2]) + + return end +struct BlitToScreen <: AbstractRenderStep + framebuffer::GLFramebuffer + pass::RenderObject + screen_framebuffer_id::Int +end -""" - to_screen_postprocessor(framebuffer, shader_cache, default_id = nothing) +# TODO: replacement for CImGUI? +function BlitToScreen(screen, screen_framebuffer_id = 0) + @debug "Creating to screen postprocessor" + framebuffer = screen.framebuffer -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. -""" -function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothing) # draw color buffer shader = LazyShader( - shader_cache, + screen.shader_cache, loadshader("postprocessing/fullscreen.vert"), loadshader("postprocessing/copy.frag") ) @@ -285,22 +314,36 @@ 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 - # 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)...) - glClear(GL_COLOR_BUFFER_BIT) - GLAbstraction.render(pass) # copy postprocess - end + return BlitToScreen(framebuffer, pass, screen_framebuffer_id) +end + +function run_step(screen, ::Nothing, step::BlitToScreen) + # 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, step.screen_framebuffer_id) + glViewport(0, 0, framebuffer_size(screen)...) + # 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,1,1,1) + glClear(GL_COLOR_BUFFER_BIT) - PostProcessor(RenderObject[pass], full_render, to_screen_postprocessor) + # transfer everything to the screen + GLAbstraction.render(step.pass) # copy postprocess + + return end -function destroy!(pp::PostProcessor) - while !isempty(pp.robjs) - destroy!(pop!(pp.robjs)) +function destroy!(step::T) where {T <: AbstractRenderStep} + @debug "Default destructor of $T" + if hasfield(T, :passes) + while !isempty(step.passes) + destroy!(pop!(step.passes)) + end + elseif hasfield(T, :pass) + destroy!(step.pass) end return -end +end \ No newline at end of file diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 4e6f9f65caf..b84b37b84d6 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -67,7 +67,8 @@ function render_frame(screen::Screen; resize_buffers=true) return !Bool(robj[:transparency][]) && Bool(robj[:ssao][]) end # SSAO - screen.postprocessors[1].render(screen) + run_step(screen, nothing, screen.render_pipeline[1]) + # render no SSAO glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) @@ -93,13 +94,13 @@ function render_frame(screen::Screen; resize_buffers=true) end # TRANSPARENT BLEND - screen.postprocessors[2].render(screen) + run_step(screen, nothing, screen.render_pipeline[2]) # FXAA - screen.postprocessors[3].render(screen) + run_step(screen, nothing, screen.render_pipeline[3]) # transfer everything to the screen - screen.postprocessors[4].render(screen) + run_step(screen, nothing, screen.render_pipeline[4]) return end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 5d2012b1187..77af7f90460 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -172,7 +172,7 @@ mutable struct Screen{GLWindow} <: MakieScreen screen2scene::Dict{WeakRef, ScreenID} screens::Vector{ScreenArea} renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}} - postprocessors::Vector{PostProcessor} + render_pipeline::Vector{AbstractRenderStep} cache::Dict{UInt64, RenderObject} cache2plot::Dict{UInt32, Plot} framecache::Matrix{RGB{N0f8}} @@ -198,7 +198,6 @@ mutable struct Screen{GLWindow} <: MakieScreen screen2scene::Dict{WeakRef, ScreenID}, screens::Vector{ScreenArea}, renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}}, - postprocessors::Vector{PostProcessor}, cache::Dict{UInt64, RenderObject}, cache2plot::Dict{UInt32, AbstractPlot}, reuse::Bool @@ -209,7 +208,7 @@ mutable struct Screen{GLWindow} <: MakieScreen glscreen, owns_glscreen, shader_cache, framebuffer, config, Threads.Atomic{Bool}(stop_renderloop), rendertask, BudgetedTimer(1.0 / 30.0), Observable(0f0), screen2scene, - screens, renderlist, postprocessors, cache, cache2plot, + screens, renderlist, AbstractRenderStep[], cache, cache2plot, Matrix{RGB{N0f8}}(undef, s), Observable(Makie.UnknownTickState), Observable(true), Observable(0f0), nothing, reuse, true, false ) @@ -281,12 +280,6 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) ShaderAbstractions.switch_context!(window) shader_cache = GLAbstraction.ShaderCache(window) fb = GLFramebuffer(initial_resolution) - postprocessors = [ - empty_postprocessor(), - empty_postprocessor(), - empty_postprocessor(), - to_screen_postprocessor(fb, shader_cache) - ] screen = Screen( window, owns_glscreen, shader_cache, fb, @@ -295,12 +288,16 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) Dict{WeakRef, ScreenID}(), ScreenArea[], Tuple{ZIndex, ScreenID, RenderObject}[], - postprocessors, Dict{UInt64, RenderObject}(), Dict{UInt32, AbstractPlot}(), reuse, ) + push!(screen.render_pipeline, EmptyRenderStep()) + push!(screen.render_pipeline, EmptyRenderStep()) + push!(screen.render_pipeline, EmptyRenderStep()) + push!(screen.render_pipeline, BlitToScreen(screen)) + if owns_glscreen GLFW.SetWindowRefreshCallback(window, refreshwindowcb(screen)) GLFW.SetWindowContentScaleCallback(window, scalechangecb(screen)) @@ -380,20 +377,18 @@ function apply_config!(screen::Screen, config::ScreenConfig; start_renderloop::B screen.scalefactor[] = !isnothing(config.scalefactor) ? config.scalefactor : scale_factor(glw) screen.px_per_unit[] = !isnothing(config.px_per_unit) ? config.px_per_unit : screen.scalefactor[] - function replace_processor!(postprocessor, idx) - fb = screen.framebuffer - shader_cache = screen.shader_cache - post = screen.postprocessors[idx] - if post.constructor !== postprocessor - destroy!(screen.postprocessors[idx]) - screen.postprocessors[idx] = postprocessor(fb, shader_cache) + function replace_renderpass!(pass, idx) + prev = screen.render_pipeline[idx] + if typeof(prev) !== pass + destroy!(prev) + screen.render_pipeline[idx] = pass(screen) end return end - replace_processor!(config.ssao ? ssao_postprocessor : empty_postprocessor, 1) - replace_processor!(config.oit ? OIT_postprocessor : empty_postprocessor, 2) - replace_processor!(config.fxaa ? fxaa_postprocessor : empty_postprocessor, 3) + replace_renderpass!(config.ssao ? RenderPass{:SSAO} : EmptyRenderStep, 1) + replace_renderpass!(config.oit ? RenderPass{:OIT} : EmptyRenderStep, 2) + replace_renderpass!(config.fxaa ? RenderPass{:FXAA} : EmptyRenderStep, 3) # TODO: replace shader programs with lighting to update N_lights & N_light_parameters From da4cf66e003518e9fc2eee09db0d1588d35c53e0 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 00:02:30 +0100 Subject: [PATCH 02/13] move gl part of Framebuffer to GLAbstraction --- GLMakie/src/GLAbstraction/GLAbstraction.jl | 9 +- GLMakie/src/GLAbstraction/GLFrameBuffer.jl | 175 +++++++++++++++++++++ GLMakie/src/glwindow.jl | 134 +++------------- GLMakie/src/postprocessing.jl | 63 ++++---- GLMakie/src/precompiles.jl | 2 +- GLMakie/src/rendering.jl | 10 +- GLMakie/src/screen.jl | 12 +- 7 files changed, 250 insertions(+), 155 deletions(-) create mode 100644 GLMakie/src/GLAbstraction/GLFrameBuffer.jl diff --git a/GLMakie/src/GLAbstraction/GLAbstraction.jl b/GLMakie/src/GLAbstraction/GLAbstraction.jl index 67c6843ed10..1b0044ba4d4 100644 --- a/GLMakie/src/GLAbstraction/GLAbstraction.jl +++ b/GLMakie/src/GLAbstraction/GLAbstraction.jl @@ -44,7 +44,7 @@ import ModernGL.glScissor include("GLUtils.jl") include("shaderabstraction.jl") -include("GLTypes.jl") +include("GLTypes.jl") # also includes GLTexture and GLBuffer export GLProgram # Shader/program object export Texture # Texture object, basically a 1/2/3D OpenGL data array export TextureParameters @@ -99,4 +99,11 @@ export getUniformsInfo export getProgramInfo export getAttributesInfo +include("GLFrameBuffer.jl") +export GLRenderbuffer +export GLFramebuffer +export attach_colorbuffer, attach_depthbuffer, attach_stencilbuffer +export get_attachment, get_buffer +export check_framebuffer + end # module diff --git a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl new file mode 100644 index 00000000000..c0f21417fd4 --- /dev/null +++ b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl @@ -0,0 +1,175 @@ +# For completion sake + +# Doesn't implement getindex, setindex etc I think? +mutable struct GLRenderbuffer + id::GLuint + size::NTuple{2, Int} + format::GLenum + context::GLContext + + function GLRenderbuffer(size, format::GLenum) + renderbuffer = GLuint[0] + glGenRenderbuffers(1, renderbuffer) + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[1]) + glRenderbufferStorage(GL_RENDERBUFFER, format, size...) + + obj = new(renderbuffer[1], size, format, current_context()) + finalizer(free, obj) + + return obj + end +end + +function bind(buffer::GLRenderbuffer) + if buffer.id == 0 + error("Binding freed GLRenderbuffer") + end + glBindRenderbuffer(GL_RENDERBUFFER, buffer.id) + return +end + +function unsafe_free(x::GLRenderbuffer) + # don't free if already freed + x.id == 0 && return + # don't free from other context + GLAbstraction.context_alive(x.context) || return + GLAbstraction.switch_context!(x.context) + id = Ref(x.id) + glDeleteRenderbuffers(1, id) + x.id = 0 + return +end + + +# TODO: Add RenderBuffer, resize!() with renderbuffer (recreate?) +mutable struct GLFramebuffer + id::GLuint + size::NTuple{2, Int} + + context::GLContext + + attachments::Dict{Symbol, GLenum} + buffers::Dict{Symbol, Texture} + counter::UInt32 # for color attachments + + function GLFramebuffer(size::NTuple{2, Int}) + # Create framebuffer + id = glGenFramebuffers() + glBindFramebuffer(GL_FRAMEBUFFER, id) + + obj = new( + id, size, current_context(), + Dict{Symbol, GLuint}(), + Dict{Symbol, Texture}(), + UInt32(0) + ) + finalizer(free, obj) + + return obj + end +end + +function bind(fb::GLFramebuffer) + if fb.id == 0 + error("Binding freed GLFramebuffer") + end + glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + return +end + +function unsafe_free(x::GLFramebuffer) + # don't free if already freed + x.id == 0 && return + # don't free from other context + GLAbstraction.context_alive(x.context) || return + GLAbstraction.switch_context!(x.context) + id = Ref(x.id) + glDeleteFramebuffers(1, id) + x.id = 0 + return +end + +Base.size(fb::GLFramebuffer) = fb.size +Base.haskey(fb::GLFramebuffer, key::Symbol) = haskey(fb.buffers, key) + +function Base.resize!(fb::GLFramebuffer, w::Int, h::Int) + (w > 0 && h > 0 && (w, h) != size(fb)) || return + for (key, buffer) in fb.buffers + resize_nocopy!(buffer, (w, h)) + end + fb.size = (w, h) + return +end + +function get_next_colorbuffer_attachment(fb::GLFramebuffer) + if fb.counter >= 15 + error("The framebuffer has exhausted its maximum number of color attachments.") + end + attachment = GL_COLOR_ATTACHMENT0 + fb.counter + fb.counter += 1 + return attachment +end + +attach_colorbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, get_next_colorbuffer_attachment(fb)) +attach_depthbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_DEPTH_ATTACHMENT) +attach_stencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_STENCIL_ATTACHMENT) + +function attach(fb::GLFramebuffer, key::Symbol, buffer, attachment::GLenum) + haskey(fb, key) && error("Cannot attach $key to Framebuffer because it is already set.") + if in(attachment, keys(fb.buffers)) + if attachment == GL_DEPTH_ATTACHMENT + type = "depth" + elseif attachment == GL_STENCIL_ATTACHMENT + type = "stencil" + else + type = "color" + end + error("Cannot attach $key as a $type attachment as it is already attached.") + end + + bind(fb) + _attach(buffer, attachment) + fb.attachments[key] = attachment + fb.buffers[key] = buffer + return attachment +end + +function _attach(t::Texture{T, 2}, attachment::GLenum) where {T} + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, t.id, 0) +end +function _attach(buffer::RenderBuffer, attachment::GLenum) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer) +end + +get_attachment(fb::GLFramebuffer, key::Symbol) = fb.attachments[key] +get_buffer(fb::GLFramebuffer, key::Symbol) = fb.buffers[key] + +function enum_to_error(s) + s == GL_FRAMEBUFFER_COMPLETE && return + s == GL_FRAMEBUFFER_UNDEFINED && + error("GL_FRAMEBUFFER_UNDEFINED: The specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist.") + s == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT && + error("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: At least one of the framebuffer attachment points is incomplete.") + s == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT && + error("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: The framebuffer does not have at least one image attached to it.") + s == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER && + error("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: The value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) specified by GL_DRAW_BUFFERi.") + s == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER && + error("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point specified by GL_READ_BUFFER.") + s == GL_FRAMEBUFFER_UNSUPPORTED && + error("GL_FRAMEBUFFER_UNSUPPORTED: The combination of internal formats of the attached images violates a driver implementation-dependent set of restrictions. Check your OpenGL driver!") + s == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE && + error("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: The value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; +if the value of GL_TEXTURE_SAMPLES is not the same for all attached textures; or, if the attached images consist of a mix of renderbuffers and textures, + the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES. + GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not consistent across all attached textures; + or, if the attached images include a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not set to GL_TRUE for all attached textures.") + s == GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS && + error("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: Any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target.") + return error("Unknown framebuffer completion error code: $s") +end + +function check_framebuffer() + status = glCheckFramebufferStatus(GL_FRAMEBUFFER) + return enum_to_error(status) +end \ No newline at end of file diff --git a/GLMakie/src/glwindow.jl b/GLMakie/src/glwindow.jl index c40d6a23f93..8e4d49b29da 100644 --- a/GLMakie/src/glwindow.jl +++ b/GLMakie/src/glwindow.jl @@ -10,86 +10,30 @@ end Base.convert(::Type{SelectionID{T}}, s::SelectionID) where T = SelectionID{T}(T(s.id), T(s.index)) Base.zero(::Type{GLMakie.SelectionID{T}}) where T = SelectionID{T}(T(0), T(0)) -mutable struct GLFramebuffer - resolution::Observable{NTuple{2, Int}} - id::GLuint - - buffer_ids::Dict{Symbol, GLuint} - buffers::Dict{Symbol, Texture} +mutable struct Framebuffer + fb::GLFramebuffer render_buffer_ids::Vector{GLuint} end # it's guaranteed, that they all have the same size -Base.size(fb::GLFramebuffer) = size(fb.buffers[:color]) -Base.haskey(fb::GLFramebuffer, key::Symbol) = haskey(fb.buffers, key) -Base.getindex(fb::GLFramebuffer, key::Symbol) = fb.buffer_ids[key] => fb.buffers[key] +# forwards... for now +Base.size(fb::Framebuffer) = size(fb.fb) +Base.haskey(fb::Framebuffer, key::Symbol) = haskey(fb.fb, key) +GLAbstraction.get_attachment(fb::Framebuffer, key::Symbol) = get_attachment(fb.fb, key) +GLAbstraction.get_buffer(fb::Framebuffer, key::Symbol) = get_buffer(fb.fb, key) +GLAbstraction.bind(fb::Framebuffer) = GLAbstraction.bind(fb.fb) -function getfallback(fb::GLFramebuffer, key::Symbol, fallback_key::Symbol) - haskey(fb, key) ? fb[key] : fb[fallback_key] +function getfallback_attachment(fb::Framebuffer, key::Symbol, fallback_key::Symbol) + haskey(fb, key) ? get_attachment(fb, key) : get_attachment(fb, fallback_key) end - - -function attach_framebuffer(t::Texture{T, 2}, attachment) where T - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, t.id, 0) +function getfallback_buffer(fb::Framebuffer, key::Symbol, fallback_key::Symbol) + haskey(fb, key) ? get_buffer(fb, key) : get_buffer(fb, fallback_key) end -# attach texture as color attachment with automatic id picking -function attach_colorbuffer!(fb::GLFramebuffer, key::Symbol, t::Texture{T, 2}) where T - if haskey(fb.buffer_ids, key) || haskey(fb.buffers, key) - error("Key $key already exists.") - end - - max_color_id = GL_COLOR_ATTACHMENT0 - for id in values(fb.buffer_ids) - if GL_COLOR_ATTACHMENT0 <= id <= GL_COLOR_ATTACHMENT15 && id > max_color_id - max_color_id = id - end - end - next_color_id = max_color_id + 0x1 - if next_color_id > GL_COLOR_ATTACHMENT15 - error("Ran out of color buffers.") - end - glFramebufferTexture2D(GL_FRAMEBUFFER, next_color_id, GL_TEXTURE_2D, t.id, 0) - push!(fb.buffer_ids, key => next_color_id) - push!(fb.buffers, key => t) - return next_color_id -end - -function enum_to_error(s) - s == GL_FRAMEBUFFER_COMPLETE && return - s == GL_FRAMEBUFFER_UNDEFINED && - error("GL_FRAMEBUFFER_UNDEFINED: The specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist.") - s == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT && - error("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: At least one of the framebuffer attachment points is incomplete.") - s == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT && - error("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: The framebuffer does not have at least one image attached to it.") - s == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER && - error("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: The value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) specified by GL_DRAW_BUFFERi.") - s == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER && - error("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point specified by GL_READ_BUFFER.") - s == GL_FRAMEBUFFER_UNSUPPORTED && - error("GL_FRAMEBUFFER_UNSUPPORTED: The combination of internal formats of the attached images violates a driver implementation-dependent set of restrictions. Check your OpenGL driver!") - s == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE && - error("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: The value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; -if the value of GL_TEXTURE_SAMPLES is not the same for all attached textures; or, if the attached images consist of a mix of renderbuffers and textures, - the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES. - GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not consistent across all attached textures; - or, if the attached images include a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not set to GL_TRUE for all attached textures.") - s == GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS && - error("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: Any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target.") - return error("Unknown framebuffer completion error code: $s") -end - -function check_framebuffer() - status = glCheckFramebufferStatus(GL_FRAMEBUFFER) - return enum_to_error(status) -end - -Makie.@noconstprop function GLFramebuffer(fb_size::NTuple{2, Int}) +Makie.@noconstprop function Framebuffer(fb_size::NTuple{2, Int}) # Create framebuffer - frambuffer_id = glGenFramebuffers() - glBindFramebuffer(GL_FRAMEBUFFER, frambuffer_id) + fb = GLFramebuffer(fb_size) # Buffers we always need # Holds the image that eventually gets displayed @@ -115,52 +59,20 @@ Makie.@noconstprop function GLFramebuffer(fb_size::NTuple{2, Int}) N0f8, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge ) - attach_framebuffer(color_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) - attach_framebuffer(depth_buffer, GL_DEPTH_ATTACHMENT) - attach_framebuffer(depth_buffer, GL_STENCIL_ATTACHMENT) + # attach buffers + color_attachment = attach_colorbuffer(fb, :color, color_buffer) + objectid_attachment = attach_colorbuffer(fb, :objectid, objectid_buffer) + attach_colorbuffer(fb, :HDR_color, HDR_color_buffer) + attach_colorbuffer(fb, :OIT_weight, OIT_weight_buffer) + attach_depthbuffer(fb, :depth, depth_buffer) + attach_stencilbuffer(fb, :stencil, depth_buffer) check_framebuffer() - fb_size_node = Observable(fb_size) - - # To allow adding postprocessors in various combinations we need to keep - # 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, - :objectid => GL_COLOR_ATTACHMENT1, - :HDR_color => GL_COLOR_ATTACHMENT2, - :OIT_weight => GL_COLOR_ATTACHMENT3, - :depth => GL_DEPTH_ATTACHMENT, - :stencil => GL_STENCIL_ATTACHMENT, - ) - buffers = Dict{Symbol, Texture}( - :color => color_buffer, - :objectid => objectid_buffer, - :HDR_color => HDR_color_buffer, - :OIT_weight => OIT_weight_buffer, - :depth => depth_buffer, - :stencil => depth_buffer - ) - - return GLFramebuffer( - fb_size_node, frambuffer_id, - buffer_ids, buffers, - [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1] - )::GLFramebuffer + return Framebuffer(fb, [color_attachment, objectid_attachment]) end -function Base.resize!(fb::GLFramebuffer, w::Int, h::Int) - (w > 0 && h > 0 && (w, h) != size(fb)) || return - for (name, buffer) in fb.buffers - resize_nocopy!(buffer, (w, h)) - end - fb.resolution[] = (w, h) - return nothing -end +Base.resize!(fb::Framebuffer, w::Int, h::Int) = resize!(fb.fb, w, h) struct MonitorProperties diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index edbba263cc4..5ebdc139fc0 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -62,8 +62,8 @@ function RenderPass{:OIT}(screen) loadshader("postprocessing/OIT_blend.frag") ) data = Dict{Symbol, Any}( - :sum_color => framebuffer[:HDR_color][2], - :prod_alpha => framebuffer[:OIT_weight][2], + :sum_color => get_buffer(framebuffer, :HDR_color), + :transmittance => get_buffer(framebuffer, :OIT_weight), ) pass = RenderObject( data, shader, @@ -84,14 +84,14 @@ function RenderPass{:OIT}(screen) ) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - return RenderPass{:OIT}(framebuffer, RenderObject[pass]) + return RenderPass{:OIT}(framebuffer.fb, RenderObject[pass]) end function run_step(screen, glscene, step::RenderPass{:OIT}) # Blend transparent onto opaque wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(step.framebuffer[:color][1]) + glDrawBuffer(get_attachment(step.framebuffer, :color)) GLAbstraction.render(step.passes[1]) return end @@ -104,23 +104,23 @@ function RenderPass{:SSAO}(screen) # Add missing buffers if !haskey(framebuffer, :position) - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + # GLAbstraction.bind(framebuffer) position_buffer = Texture( Vec3f, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge ) - pos_id = attach_colorbuffer!(framebuffer, :position, position_buffer) + pos_id = attach_colorbuffer(framebuffer, :position, position_buffer) push!(framebuffer.render_buffer_ids, pos_id) end if !haskey(framebuffer, :normal) if !haskey(framebuffer, :HDR_color) - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + # GLAbstraction.bind(framebuffer) normal_occlusion_buffer = Texture( Vec4{Float16}, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge ) - normal_occ_id = attach_colorbuffer!(framebuffer, :normal_occlusion, normal_occlusion_buffer) + normal_occ_id = attach_colorbuffer(framebuffer, :normal_occlusion, normal_occlusion_buffer) renames[:normal_occlusion] = :normal_occlusion else - normal_occ_id = framebuffer[:HDR_color][1] + normal_occ_id = get_attachment(framebuffer, :HDR_color) renames[:normal_occlusion] = :HDR_color end push!(framebuffer.render_buffer_ids, normal_occ_id) @@ -146,14 +146,14 @@ function RenderPass{:SSAO}(screen) ) ) data1 = Dict{Symbol, Any}( - :position_buffer => framebuffer[:position][2], - :normal_occlusion_buffer => getfallback(framebuffer, :normal_occlusion, :HDR_color)[2], + :position_buffer => get_buffer(framebuffer, :position), + :normal_occlusion_buffer => getfallback_buffer(framebuffer, :normal_occlusion, :HDR_color), :kernel => kernel, :noise => Texture( [normalize(Vec2f(2.0rand(2) .- 1.0)) for _ in 1:4, __ in 1:4], minfilter = :nearest, x_repeat = :repeat ), - :noise_scale => map(s -> Vec2f(s ./ 4.0), framebuffer.resolution), + :noise_scale => Vec2f(0.25f0 .* size(framebuffer)), :projection => Observable(Mat4f(I)), :bias => 0.025f0, :radius => 0.5f0 @@ -169,23 +169,23 @@ function RenderPass{:SSAO}(screen) loadshader("postprocessing/SSAO_blur.frag") ) data2 = Dict{Symbol, Any}( - :normal_occlusion => getfallback(framebuffer, :normal_occlusion, :HDR_color)[2], - :color_texture => framebuffer[:color][2], - :ids => framebuffer[:objectid][2], - :inv_texel_size => lift(rcpframe, framebuffer.resolution), + :normal_occlusion => getfallback_buffer(framebuffer, :normal_occlusion, :HDR_color), + :color_texture => get_output(framebuffer, :color), + :ids => get_buffer(framebuffer, :objectid), + :inv_texel_size => rcpframe(size(framebuffer)), :blur_range => Int32(2) ) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - return RenderPass{:SSAO}(framebuffer, [pass1, pass2], renames) + return RenderPass{:SSAO}(framebuffer.fb, [pass1, pass2], renames) end function run_step(screen, glscene, step::RenderPass{:SSAO}) wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(step.framebuffer[step.renames[:normal_occ_id]][1]) # occlusion buffer + glDrawBuffer(get_attachment(step.framebuffer, step.renames[:normal_occ_id])) # occlusion buffer glEnable(GL_SCISSOR_TEST) ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) @@ -206,7 +206,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) end # SSAO - blur occlusion and apply to color - glDrawBuffer(step.framebuffer[:color][1]) # color buffer + glDrawBuffer(get_attachment(step.framebuffer, :color)) # color buffer data2 = step.passes[2].uniforms for (screenid, scene) in screen.screens # Select the area of one leaf scene @@ -231,14 +231,14 @@ function RenderPass{:FXAA}(screen) # Add missing buffers if !haskey(framebuffer, :color_luma) if !haskey(framebuffer, :HDR_color) - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + # GLAbstraction.bind(framebuffer) color_luma_buffer = Texture( RGBA{N0f8}, size(framebuffer), minfilter=:linear, x_repeat=:clamp_to_edge ) - luma_id = attach_colorbuffer!(framebuffer, :color_luma, color_luma_buffer) + luma_id = attach_colorbuffer(framebuffer, :color_luma, color_luma_buffer) renames[:color_luma] = :color_luma else - luma_id = framebuffer[:HDR_color][1] + luma_id = get_attachment(framebuffer, :HDR_color) renames[:color_luma] = :HDR_color end end @@ -250,8 +250,8 @@ function RenderPass{:FXAA}(screen) loadshader("postprocessing/postprocess.frag") ) data1 = Dict{Symbol, Any}( - :color_texture => framebuffer[:color][2], - :object_ids => framebuffer[:objectid][2] + :color_texture => get_buffer(framebuffer, :color), + :object_ids => get_buffer(framebuffer, :objectid) ) pass1 = RenderObject(data1, shader1, PostprocessPrerender(), nothing) pass1.postrenderfunction = () -> draw_fullscreen(pass1.vertexarray.id) @@ -263,13 +263,13 @@ function RenderPass{:FXAA}(screen) loadshader("postprocessing/fxaa.frag") ) data2 = Dict{Symbol, Any}( - :color_texture => getfallback(framebuffer, :color_luma, :HDR_color)[2], - :RCPFrame => lift(rcpframe, framebuffer.resolution), + :color_texture => getfallback_buffer(framebuffer, :color_luma, :HDR_color), + :RCPFrame => rcpframe(size(framebuffer)), ) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - return RenderPass{:FXAA}(framebuffer, RenderObject[pass1, pass2], renames) + return RenderPass{:FXAA}(framebuffer.fb, RenderObject[pass1, pass2], renames) end function run_step(screen, glscene, step::RenderPass{:FXAA}) @@ -278,14 +278,15 @@ function run_step(screen, glscene, step::RenderPass{:FXAA}) glViewport(0, 0, wh[1], wh[2]) # FXAA - calculate LUMA - glDrawBuffer(step.framebuffer[step.renames[:color_luma]][1]) + glDrawBuffer(get_attachment(step.framebuffer, step.renames[:color_luma])) # necessary with negative SSAO bias... glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) GLAbstraction.render(step.passes[1]) # FXAA - perform anti-aliasing - glDrawBuffer(step.framebuffer[:color][1]) # color buffer + glDrawBuffer(get_attachment(step.framebuffer, :color)) # color buffer + step.passes[2][:RCPFrame] = rcpframe(size(step.framebuffer)) GLAbstraction.render(step.passes[2]) return @@ -309,12 +310,12 @@ function BlitToScreen(screen, screen_framebuffer_id = 0) loadshader("postprocessing/copy.frag") ) data = Dict{Symbol, Any}( - :color_texture => framebuffer[:color][2] + :color_texture => get_buffer(framebuffer, :color) ) pass = RenderObject(data, shader, PostprocessPrerender(), nothing) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - return BlitToScreen(framebuffer, pass, screen_framebuffer_id) + return BlitToScreen(framebuffer.fb, pass, screen_framebuffer_id) end function run_step(screen, ::Nothing, step::BlitToScreen) diff --git a/GLMakie/src/precompiles.jl b/GLMakie/src/precompiles.jl index 32072bce182..a8b81d09e12 100644 --- a/GLMakie/src/precompiles.jl +++ b/GLMakie/src/precompiles.jl @@ -60,7 +60,7 @@ let end precompile(Screen, (Scene, ScreenConfig)) -precompile(GLFramebuffer, (NTuple{2,Int},)) +precompile(Framebuffer, (NTuple{2,Int},)) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{Float32})) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{RGBAf})) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{RGBf})) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index b84b37b84d6..95d59496493 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -52,7 +52,7 @@ function render_frame(screen::Screen; resize_buffers=true) end # prepare stencil (for sub-scenes) - glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + GLAbstraction.bind(fb) 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) @@ -71,7 +71,7 @@ function render_frame(screen::Screen; resize_buffers=true) # render no SSAO - glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) + glDrawBuffers(2, [get_attachment(fb, :color), get_attachment(fb, :objectid)]) # render all non ssao GLAbstraction.render(screen) do robj return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) @@ -79,15 +79,15 @@ function render_frame(screen::Screen; resize_buffers=true) # TRANSPARENT RENDER # clear sums to 0 - glDrawBuffer(GL_COLOR_ATTACHMENT2) + glDrawBuffer(get_attachment(fb, :HDR_color)) glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) # clear alpha product to 1 - glDrawBuffer(GL_COLOR_ATTACHMENT3) + glDrawBuffer(get_attachment(fb, :OIT_weight)) glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) # draw - glDrawBuffers(3, [GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT3]) + glDrawBuffers(3, [get_attachment(fb, :HDR_color), get_attachment(fb, :objectid), get_attachment(fb, :OIT_weight)]) # Render only transparent objects GLAbstraction.render(screen) do robj return Bool(robj[:transparency][]) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 77af7f90460..f624c936e09 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -162,7 +162,7 @@ mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow owns_glscreen::Bool shader_cache::GLAbstraction.ShaderCache - framebuffer::GLFramebuffer + framebuffer::Framebuffer config::Union{Nothing, ScreenConfig} stop_renderloop::Threads.Atomic{Bool} rendertask::Union{Task, Nothing} @@ -190,7 +190,7 @@ mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow, owns_glscreen::Bool, shader_cache::GLAbstraction.ShaderCache, - framebuffer::GLFramebuffer, + framebuffer::Framebuffer, config::Union{Nothing, ScreenConfig}, stop_renderloop::Bool, rendertask::Union{Nothing, Task}, @@ -217,7 +217,7 @@ mutable struct Screen{GLWindow} <: MakieScreen end end -framebuffer_size(screen::Screen) = screen.framebuffer.resolution[] +framebuffer_size(screen::Screen) = size(screen.framebuffer) Makie.isvisible(screen::Screen) = screen.config.visible @@ -279,7 +279,7 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) # This is important for resource tracking, and only needed for the first context ShaderAbstractions.switch_context!(window) shader_cache = GLAbstraction.ShaderCache(window) - fb = GLFramebuffer(initial_resolution) + fb = Framebuffer(initial_resolution) screen = Screen( window, owns_glscreen, shader_cache, fb, @@ -756,7 +756,7 @@ function depthbuffer(screen::Screen) ShaderAbstractions.switch_context!(screen.glscreen) render_frame(screen, resize_buffers=false) # let it render glFinish() # block until opengl is done rendering - source = screen.framebuffer.buffers[:depth] + source = get_buffer(screen.framebuffer, :depth) depth = Matrix{Float32}(undef, size(source)) GLAbstraction.bind(source) GLAbstraction.glGetTexImage(source.texturetype, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depth) @@ -769,7 +769,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 = get_buffer(screen.framebuffer, :color) # 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) From 7cb1e73b1f1e199567ba7f45a1c98ab806a7e2ba Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 01:20:07 +0100 Subject: [PATCH 03/13] add show() --- GLMakie/src/GLAbstraction/GLFrameBuffer.jl | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl index c0f21417fd4..e55bfef7068 100644 --- a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl +++ b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl @@ -172,4 +172,38 @@ end function check_framebuffer() status = glCheckFramebufferStatus(GL_FRAMEBUFFER) return enum_to_error(status) +end + +function Base.show(io::IO, fb::GLFramebuffer) + X, Y = fb.size + print(io, "$X×$Y GLFrameBuffer(:") + join(io, string.(keys(fb.buffers)), ", :") + print(io, ")") +end + +function attachment_enum_to_string(x::GLenum) + x == GL_DEPTH_ATTACHMENT && return "GL_DEPTH_ATTACHMENT" + x == GL_STENCIL_ATTACHMENT && return "GL_STENCIL_ATTACHMENT" + i = Int(x - GL_COLOR_ATTACHMENT0) + return "GL_COLOR_ATTACHMENT$i" +end + +function Base.show(io::IO, ::MIME"text/plain", fb::GLFramebuffer) + X, Y = fb.size + print(io, "$X×$Y GLFrameBuffer()") + + ks = collect(keys(fb.buffers)) + key_strings = [":$k" for k in ks] + key_pad = mapreduce(length, max, key_strings) + key_strings = rpad.(key_strings, key_pad) + + attachments = map(key -> attachment_enum_to_string(get_attachment(fb, key)), ks) + idxs = sortperm(attachments) + attachment_pad = mapreduce(length, max, attachments) + attachments = rpad.(attachments, attachment_pad) + + for i in idxs + T = typeof(get_buffer(fb, ks[i])) + print(io, "\n ", key_strings[i], " => ", attachments[i], " ::", T) + end end \ No newline at end of file From 7119b1e845a2b9b1787068d8abfc971d209c530e Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 01:36:42 +0100 Subject: [PATCH 04/13] fix incorrect rename --- GLMakie/src/postprocessing.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 5ebdc139fc0..6930cbdf44d 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -62,8 +62,8 @@ function RenderPass{:OIT}(screen) loadshader("postprocessing/OIT_blend.frag") ) data = Dict{Symbol, Any}( - :sum_color => get_buffer(framebuffer, :HDR_color), - :transmittance => get_buffer(framebuffer, :OIT_weight), + :sum_color => get_buffer(framebuffer, :HDR_color), + :prod_alpha => get_buffer(framebuffer, :OIT_weight), ) pass = RenderObject( data, shader, @@ -202,6 +202,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) data1[:projection] = scene.camera.projection[] data1[:bias] = scene.ssao.bias[] data1[:radius] = scene.ssao.radius[] + datas1[:noise_scale] = Vec2f(0.25f0 .* size(step.framebuffer)) GLAbstraction.render(step.passes[1]) end @@ -215,6 +216,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) glScissor(ppu(minimum(a))..., ppu(widths(a))...) # update uniforms data2[:blur_range] = scene.ssao.blur + data2[:inv_texel_size] = rcpframe(size(framebuffer)) GLAbstraction.render(step.passes[2]) end glDisable(GL_SCISSOR_TEST) From 40319e604f03c20745f4f161b272250578c0616e Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 12:06:30 +0100 Subject: [PATCH 05/13] change copy-to-screen to a Blit operation --- GLMakie/src/postprocessing.jl | 97 +++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 6930cbdf44d..8ecdbb745c3 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -18,10 +18,37 @@ end rcpframe(x) = 1f0 ./ Vec2f(x[1], x[2]) + +# or maybe Task? Stage? +""" + AbstractRenderStep + +Represents a task or step that needs to run when rendering a frame. These +tasks are collected in the RenderPipeline. + +Each task may implement: +- `prepare_step(screen, glscene, step)`: Initialize the task. +- `run_step(screen, glscene, step)`: Run the task. + +Initialization is grouped together and runs before all run steps. If you need +to initialize just before your run, bundle it with the run. +""" abstract type AbstractRenderStep end prepare_step(screen, glscene, ::AbstractRenderStep) = nothing run_step(screen, glscene, ::AbstractRenderStep) = nothing +function destroy!(step::T) where {T <: AbstractRenderStep} + @debug "Default destructor of $T" + if hasfield(T, :passes) + while !isempty(step.passes) + destroy!(pop!(step.passes)) + end + elseif hasfield(T, :pass) + destroy!(step.pass) + end + return +end + struct RenderPipeline steps::Vector{AbstractRenderStep} end @@ -36,9 +63,12 @@ function render_frame(screen, glscene, pipeline::RenderPipeline) return end + # TODO: temporary, we should get to the point where this is not needed struct EmptyRenderStep <: AbstractRenderStep end + +# TODO: maybe call this a PostProcessor? # Vaguely leaning on Vulkan Terminology struct RenderPass{Name} <: AbstractRenderStep framebuffer::GLFramebuffer @@ -294,59 +324,40 @@ function run_step(screen, glscene, step::RenderPass{:FXAA}) return end + +# TODO: Could also handle integration with Gtk, CImGui, etc with a dedicated struct struct BlitToScreen <: AbstractRenderStep framebuffer::GLFramebuffer - pass::RenderObject screen_framebuffer_id::Int -end - -# TODO: replacement for CImGUI? -function BlitToScreen(screen, screen_framebuffer_id = 0) - @debug "Creating to screen postprocessor" - framebuffer = screen.framebuffer - # draw color buffer - shader = LazyShader( - screen.shader_cache, - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/copy.frag") - ) - data = Dict{Symbol, Any}( - :color_texture => get_buffer(framebuffer, :color) - ) - pass = RenderObject(data, shader, PostprocessPrerender(), nothing) - pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - - return BlitToScreen(framebuffer.fb, pass, screen_framebuffer_id) + # Screen not available yet + function BlitToScreen(screen, screen_framebuffer_id::Integer = 0) + @debug "Creating to screen postprocessor" + return new(screen.framebuffer.fb, screen_framebuffer_id) + end end function run_step(screen, ::Nothing, step::BlitToScreen) + # Set source + glBindFramebuffer(GL_READ_FRAMEBUFFER, step.framebuffer.id) + glReadBuffer(get_attachment(step.framebuffer, :color)) # for safety + # 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, step.screen_framebuffer_id) - glViewport(0, 0, framebuffer_size(screen)...) - # 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,1,1,1) - glClear(GL_COLOR_BUFFER_BIT) + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, step.screen_framebuffer_id) - # transfer everything to the screen - GLAbstraction.render(step.pass) # copy postprocess + src_w, src_h = framebuffer_size(screen) + if isnothing(screen.scene) + trg_w, trg_h = src_w, src_h + else + trg_w, trg_h = widths(screen.scene.viewport[]) + end - return -end + glBlitFramebuffer( + 0, 0, src_w, src_h, + 0, 0, trg_w, trg_h, + GL_COLOR_BUFFER_BIT, GL_LINEAR + ) -function destroy!(step::T) where {T <: AbstractRenderStep} - @debug "Default destructor of $T" - if hasfield(T, :passes) - while !isempty(step.passes) - destroy!(pop!(step.passes)) - end - elseif hasfield(T, :pass) - destroy!(step.pass) - end return -end \ No newline at end of file +end From 6cb59f95411b1952e4c2bc5fced7c87bf9fe0225 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 12:55:33 +0100 Subject: [PATCH 06/13] fix tests --- GLMakie/src/picking.jl | 16 ++++++++-------- GLMakie/test/unit_tests.jl | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/GLMakie/src/picking.jl b/GLMakie/src/picking.jl index cde1cd4d59f..c785166be3e 100644 --- a/GLMakie/src/picking.jl +++ b/GLMakie/src/picking.jl @@ -7,8 +7,8 @@ function pick_native(screen::Screen, rect::Rect2i) isopen(screen) || return Matrix{SelectionID{Int}}(undef, 0, 0) ShaderAbstractions.switch_context!(screen.glscreen) fb = screen.framebuffer - buff = fb.buffers[:objectid] - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) + buff = get_buffer(fb, :objectid) + GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) rx, ry = minimum(rect) rw, rh = widths(rect) @@ -28,8 +28,8 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}) isopen(screen) || return SelectionID{Int}(0, 0) ShaderAbstractions.switch_context!(screen.glscreen) fb = screen.framebuffer - buff = fb.buffers[:objectid] - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) + buff = get_buffer(fb, :objectid) + GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) x, y = floor.(Int, xy) w, h = size(screen.scene) @@ -78,9 +78,9 @@ function Makie.pick_closest(scene::Scene, screen::Screen, xy, range) dx = x1 - x0; dy = y1 - y0 ShaderAbstractions.switch_context!(screen.glscreen) - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) + GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) - buff = fb.buffers[:objectid] + buff = get_buffer(fb, :objectid) sids = zeros(SelectionID{UInt32}, dx, dy) glReadPixels(x0, y0, dx, dy, buff.format, buff.pixeltype, sids) @@ -119,9 +119,9 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) dx = x1 - x0; dy = y1 - y0 ShaderAbstractions.switch_context!(screen.glscreen) - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) + GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) - buff = fb.buffers[:objectid] + buff = get_buffer(fb, :objectid) picks = zeros(SelectionID{UInt32}, dx, dy) glReadPixels(x0, y0, dx, dy, buff.format, buff.pixeltype, picks) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index c14d1ba7504..53cfcfc129f 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -11,39 +11,39 @@ end screen = display(GLMakie.Screen(visible = false), Figure()) cache = screen.shader_cache; # Postprocessing shaders - @test length(cache.shader_cache) == 5 - @test length(cache.template_cache) == 5 - @test length(cache.program_cache) == 4 + @test length(cache.shader_cache) == 4 + @test length(cache.template_cache) == 4 + @test length(cache.program_cache) == 3 # Shaders for scatter + linesegments + poly etc (axis) display(screen, scatter(1:4)) - @test length(cache.shader_cache) == 18 - @test length(cache.template_cache) == 18 - @test length(cache.program_cache) == 11 + @test length(cache.shader_cache) == 17 + @test length(cache.template_cache) == 17 + @test length(cache.program_cache) == 10 # No new shaders should be added: display(screen, scatter(1:4)) - @test length(cache.shader_cache) == 18 - @test length(cache.template_cache) == 18 - @test length(cache.program_cache) == 11 + @test length(cache.shader_cache) == 17 + @test length(cache.template_cache) == 17 + @test length(cache.program_cache) == 10 # Same for linesegments display(screen, linesegments(1:4)) - @test length(cache.shader_cache) == 18 - @test length(cache.template_cache) == 18 - @test length(cache.program_cache) == 11 + @test length(cache.shader_cache) == 17 + @test length(cache.template_cache) == 17 + @test length(cache.program_cache) == 10 # heatmap hasn't been compiled so one new program should be added display(screen, heatmap([1,2,2.5,3], [1,2,2.5,3], rand(4,4))) - @test length(cache.shader_cache) == 20 - @test length(cache.template_cache) == 20 - @test length(cache.program_cache) == 12 + @test length(cache.shader_cache) == 19 + @test length(cache.template_cache) == 19 + @test length(cache.program_cache) == 11 # For second time no new shaders should be added display(screen, heatmap([1,2,2.5,3], [1,2,2.5,3], rand(4,4))) - @test length(cache.shader_cache) == 20 - @test length(cache.template_cache) == 20 - @test length(cache.program_cache) == 12 + @test length(cache.shader_cache) == 19 + @test length(cache.template_cache) == 19 + @test length(cache.program_cache) == 11 end @testset "unit tests" begin From ed75c7389419db771abbc8730c8b56b15698dda2 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 15:15:02 +0100 Subject: [PATCH 07/13] add Render step --- GLMakie/src/postprocessing.jl | 89 +++++++++++++++++++++++++++++++++++ GLMakie/src/rendering.jl | 74 +++++------------------------ GLMakie/src/screen.jl | 9 ++-- 3 files changed, 107 insertions(+), 65 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 8ecdbb745c3..24b3b3f9f16 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -68,6 +68,92 @@ end struct EmptyRenderStep <: AbstractRenderStep end +@enum FilterOptions begin + FilterFalse = 0 + FilterTrue = 1 + FilterAny = 2 +end +compare(val::Bool, filter::FilterOptions) = (filter == FilterAny) || (val == Int(filter)) +compare(val::Integer, filter::FilterOptions) = (filter == FilterAny) || (val == Int(filter)) + +struct RenderPlots <: AbstractRenderStep + framebuffer::GLFramebuffer + targets::Vector{GLuint} + clear::Vector{Pair{Int, Vec4f}} # target index -> color + + ssao::FilterOptions + transparency::FilterOptions + fxaa::FilterOptions +end + +function RenderPlots(screen, stage) + fb = screen.framebuffer + if stage === :SSAO + return RenderPlots( + fb.fb, fb.render_buffer_ids, [3 => Vec4f(0), 4 => Vec4f(0)], + FilterTrue, FilterFalse, FilterAny) + elseif stage === :FXAA + return RenderPlots( + fb.fb, get_attachment.(Ref(fb), [:color, :objectid]), Pair{Int, Vec4f}[], + FilterFalse, FilterFalse, FilterAny) + elseif stage === :OIT + targets = get_attachment.(Ref(fb), [:HDR_color, :objectid, :OIT_weight]) + # HDR_color containing sums clears to 0 + # OIT_weight containing products clears to 1 + clear = [1 => Vec4f(0), 3 => Vec4f(1)] + return RenderPlots(fb.fb, targets, clear, FilterAny, FilterTrue, FilterAny) + else + error("Incorrect stage = $stage given. Should be :SSAO, :FXAA or :OIT.") + end +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 run_step(screen, glscene, step::RenderPlots) + # Somehow errors in here get ignored silently!? + try + GLAbstraction.bind(step.framebuffer) + + for (idx, color) in step.clear + idx <= length(step.targets) || continue + glDrawBuffer(step.targets[idx]) + glClearColor(color...) + glClear(GL_COLOR_BUFFER_BIT) + end + + glDrawBuffers(length(step.targets), step.targets) + + for (zindex, screenid, elem) in screen.renderlist + should_render = elem.visible && + compare(elem[:ssao][], step.ssao) && + compare(elem[:transparency][], step.transparency) && + compare(elem[:fxaa][], step.fxaa) + should_render || continue + + found, scene = id2scene(screen, screenid) + (found && 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 + + + + # TODO: maybe call this a PostProcessor? # Vaguely leaning on Vulkan Terminology struct RenderPass{Name} <: AbstractRenderStep @@ -79,6 +165,8 @@ function RenderPass{Name}(framebuffer::GLFramebuffer, passes::Vector{RenderObjec return RenderPass{Name}(framebuffer, passes, Dict{Symbol, Symbol}()) end + + function RenderPass{:OIT}(screen) @debug "Creating OIT postprocessor" @@ -325,6 +413,7 @@ function run_step(screen, glscene, step::RenderPass{:FXAA}) end + # TODO: Could also handle integration with Gtk, CImGui, etc with a dedicated struct struct BlitToScreen <: AbstractRenderStep framebuffer::GLFramebuffer diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 95d59496493..cbd82a0eef8 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -51,85 +51,35 @@ function render_frame(screen::Screen; resize_buffers=true) resize!(fb, round.(Int, ppu .* size(screen.scene))...) end - # prepare stencil (for sub-scenes) + # clear global buffers GLAbstraction.bind(fb) - glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) + glDrawBuffers(2, get_attachment.(Ref(fb), [:color, :objectid])) glClearColor(0, 0, 0, 0) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - glDrawBuffer(fb.render_buffer_ids[1]) + # draw backgrounds + glDrawBuffer(get_attachment(fb, :color)) 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 run_step(screen, nothing, screen.render_pipeline[1]) + # run SSAO + run_step(screen, nothing, screen.render_pipeline[2]) + # render all plots without SSAO and transparency + run_step(screen, nothing, screen.render_pipeline[3]) - # render no SSAO - glDrawBuffers(2, [get_attachment(fb, :color), get_attachment(fb, :objectid)]) - # render all non ssao - GLAbstraction.render(screen) do robj - return !Bool(robj[:transparency][]) && !Bool(robj[:ssao][]) - end - - # TRANSPARENT RENDER - # clear sums to 0 - glDrawBuffer(get_attachment(fb, :HDR_color)) - glClearColor(0, 0, 0, 0) - glClear(GL_COLOR_BUFFER_BIT) - # clear alpha product to 1 - glDrawBuffer(get_attachment(fb, :OIT_weight)) - glClearColor(1, 1, 1, 1) - glClear(GL_COLOR_BUFFER_BIT) - # draw - glDrawBuffers(3, [get_attachment(fb, :HDR_color), get_attachment(fb, :objectid), get_attachment(fb, :OIT_weight)]) # Render only transparent objects - GLAbstraction.render(screen) do robj - return Bool(robj[:transparency][]) - end + run_step(screen, nothing, screen.render_pipeline[4]) # TRANSPARENT BLEND - run_step(screen, nothing, screen.render_pipeline[2]) + run_step(screen, nothing, screen.render_pipeline[5]) # FXAA - run_step(screen, nothing, screen.render_pipeline[3]) + run_step(screen, nothing, screen.render_pipeline[6]) # transfer everything to the screen - run_step(screen, nothing, screen.render_pipeline[4]) - - 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 + run_step(screen, nothing, screen.render_pipeline[7]) - 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 diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index f624c936e09..dbd339eee76 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -293,7 +293,10 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) reuse, ) + push!(screen.render_pipeline, RenderPlots(screen, :SSAO)) push!(screen.render_pipeline, EmptyRenderStep()) + push!(screen.render_pipeline, RenderPlots(screen, :FXAA)) + push!(screen.render_pipeline, RenderPlots(screen, :OIT)) push!(screen.render_pipeline, EmptyRenderStep()) push!(screen.render_pipeline, EmptyRenderStep()) push!(screen.render_pipeline, BlitToScreen(screen)) @@ -386,9 +389,9 @@ function apply_config!(screen::Screen, config::ScreenConfig; start_renderloop::B return end - replace_renderpass!(config.ssao ? RenderPass{:SSAO} : EmptyRenderStep, 1) - replace_renderpass!(config.oit ? RenderPass{:OIT} : EmptyRenderStep, 2) - replace_renderpass!(config.fxaa ? RenderPass{:FXAA} : EmptyRenderStep, 3) + replace_renderpass!(config.ssao ? RenderPass{:SSAO} : EmptyRenderStep, 2) + replace_renderpass!(config.oit ? RenderPass{:OIT} : EmptyRenderStep, 5) + replace_renderpass!(config.fxaa ? RenderPass{:FXAA} : EmptyRenderStep, 6) # TODO: replace shader programs with lighting to update N_lights & N_light_parameters From a2288b9ba3af9cf95f85a85d30e32e1c20caee12 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 15:24:10 +0100 Subject: [PATCH 08/13] fix SSAO --- GLMakie/src/glwindow.jl | 2 +- GLMakie/src/postprocessing.jl | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/GLMakie/src/glwindow.jl b/GLMakie/src/glwindow.jl index 8e4d49b29da..b7e580b7b59 100644 --- a/GLMakie/src/glwindow.jl +++ b/GLMakie/src/glwindow.jl @@ -22,7 +22,7 @@ Base.haskey(fb::Framebuffer, key::Symbol) = haskey(fb.fb, key) GLAbstraction.get_attachment(fb::Framebuffer, key::Symbol) = get_attachment(fb.fb, key) GLAbstraction.get_buffer(fb::Framebuffer, key::Symbol) = get_buffer(fb.fb, key) GLAbstraction.bind(fb::Framebuffer) = GLAbstraction.bind(fb.fb) - +GLAbstraction.attach_colorbuffer(fb::Framebuffer, key, val) = GLAbstraction.attach_colorbuffer(fb.fb, key, val) function getfallback_attachment(fb::Framebuffer, key::Symbol, fallback_key::Symbol) haskey(fb, key) ? get_attachment(fb, key) : get_attachment(fb, fallback_key) end diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 24b3b3f9f16..a01bbeb8c37 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -288,7 +288,7 @@ function RenderPass{:SSAO}(screen) ) data2 = Dict{Symbol, Any}( :normal_occlusion => getfallback_buffer(framebuffer, :normal_occlusion, :HDR_color), - :color_texture => get_output(framebuffer, :color), + :color_texture => get_buffer(framebuffer, :color), :ids => get_buffer(framebuffer, :objectid), :inv_texel_size => rcpframe(size(framebuffer)), :blur_range => Int32(2) @@ -303,7 +303,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) wh = size(screen.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(get_attachment(step.framebuffer, step.renames[:normal_occ_id])) # occlusion buffer + glDrawBuffer(get_attachment(step.framebuffer, step.renames[:normal_occlusion])) # occlusion buffer glEnable(GL_SCISSOR_TEST) ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) @@ -317,10 +317,10 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) a = viewport(scene)[] glScissor(ppu(minimum(a))..., ppu(widths(a))...) # update uniforms - data1[:projection] = scene.camera.projection[] + data1[:projection] = Mat4f(scene.camera.projection[]) data1[:bias] = scene.ssao.bias[] data1[:radius] = scene.ssao.radius[] - datas1[:noise_scale] = Vec2f(0.25f0 .* size(step.framebuffer)) + data1[:noise_scale] = Vec2f(0.25f0 .* size(step.framebuffer)) GLAbstraction.render(step.passes[1]) end @@ -334,7 +334,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) glScissor(ppu(minimum(a))..., ppu(widths(a))...) # update uniforms data2[:blur_range] = scene.ssao.blur - data2[:inv_texel_size] = rcpframe(size(framebuffer)) + data2[:inv_texel_size] = rcpframe(size(step.framebuffer)) GLAbstraction.render(step.passes[2]) end glDisable(GL_SCISSOR_TEST) From b2f999218a6150fc4409ceb6e6beb8869c7718b1 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 15:48:18 +0100 Subject: [PATCH 09/13] add SortPlots task and cleanup render_frame --- GLMakie/src/postprocessing.jl | 19 +++++++ GLMakie/src/rendering.jl | 93 ++++++++++++----------------------- GLMakie/src/screen.jl | 10 ++-- 3 files changed, 57 insertions(+), 65 deletions(-) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index a01bbeb8c37..bb1d18272a6 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -68,6 +68,25 @@ end struct EmptyRenderStep <: AbstractRenderStep end + +struct SortPlots <: AbstractRenderStep end + +function run_step(screen, glscene, ::SortPlots) + function sortby(x) + robj = x[3] + plot = screen.cache2plot[robj.id] + # TODO, use actual boundingbox + # ~7% faster than calling zvalue2d doing the same thing? + return Makie.transformationmatrix(plot)[][3, 4] + # return Makie.zvalue2d(plot) + end + + sort!(screen.renderlist; by=sortby) + return +end + + + @enum FilterOptions begin FilterFalse = 0 FilterTrue = 1 diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index cbd82a0eef8..330aaaf44c6 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -1,21 +1,38 @@ -function setup!(screen::Screen) +function setup!(screen::Screen, resize_buffers) + # Make sure this context is active (for multi-window rendering) + ShaderAbstractions.switch_context!(to_native(screen)) + + # Resize framebuffer to window size + fb = screen.framebuffer + GLAbstraction.bind(fb) + if resize_buffers && !isnothing(screen.scene) + ppu = screen.px_per_unit[] + resize!(fb, round.(Int, ppu .* size(screen.scene))...) + end + + # clear objectid, depth and stencil + glDrawBuffer(get_attachment(fb, :objectid)) + glClearColor(0, 0, 0, 0) + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + + # clear color buffer + glDrawBuffer(get_attachment(fb, :color)) + glClearColor(1, 1, 1, 1) + glClear(GL_COLOR_BUFFER_BIT) + + # draw scene backgrounds glEnable(GL_SCISSOR_TEST) if isopen(screen) && !isnothing(screen.scene) ppu = screen.px_per_unit[] - glScissor(0, 0, round.(Int, size(screen.scene) .* ppu)...) - glClearColor(1, 1, 1, 1) - glClear(GL_COLOR_BUFFER_BIT) for (id, scene) in screen.screens - if scene.visible[] + if scene.visible[] && scene.clear[] 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 + glScissor(rt...) + c = scene.backgroundcolor[] + glClearColor(red(c), green(c), blue(c), alpha(c)) + glClear(GL_COLOR_BUFFER_BIT) end end end @@ -27,59 +44,11 @@ end Renders a single frame of a `window` """ function render_frame(screen::Screen; resize_buffers=true) - nw = to_native(screen) - ShaderAbstractions.switch_context!(nw) + setup!(screen, resize_buffers) - function sortby(x) - robj = x[3] - plot = screen.cache2plot[robj.id] - # TODO, use actual boundingbox - # ~7% faster than calling zvalue2d doing the same thing? - return Makie.transformationmatrix(plot)[][3, 4] - # return Makie.zvalue2d(plot) + for step in screen.render_pipeline + run_step(screen, nothing, step) end - sort!(screen.renderlist; by=sortby) - - # 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.scene) - ppu = screen.px_per_unit[] - resize!(fb, round.(Int, ppu .* size(screen.scene))...) - end - - # clear global buffers - GLAbstraction.bind(fb) - glDrawBuffers(2, get_attachment.(Ref(fb), [:color, :objectid])) - glClearColor(0, 0, 0, 0) - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) - - # draw backgrounds - glDrawBuffer(get_attachment(fb, :color)) - setup!(screen) - - # render with SSAO - run_step(screen, nothing, screen.render_pipeline[1]) - # run SSAO - run_step(screen, nothing, screen.render_pipeline[2]) - - # render all plots without SSAO and transparency - run_step(screen, nothing, screen.render_pipeline[3]) - - # Render only transparent objects - run_step(screen, nothing, screen.render_pipeline[4]) - - # TRANSPARENT BLEND - run_step(screen, nothing, screen.render_pipeline[5]) - - # FXAA - run_step(screen, nothing, screen.render_pipeline[6]) - - # transfer everything to the screen - run_step(screen, nothing, screen.render_pipeline[7]) - return end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index dbd339eee76..8d319361c25 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -293,6 +293,10 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) reuse, ) + # NOTE + # The transparent color buffer is reused by SSAO and FXAA. Changing the + # render order here may introduce artifacts because of that. + push!(screen.render_pipeline, SortPlots()) push!(screen.render_pipeline, RenderPlots(screen, :SSAO)) push!(screen.render_pipeline, EmptyRenderStep()) push!(screen.render_pipeline, RenderPlots(screen, :FXAA)) @@ -389,9 +393,9 @@ function apply_config!(screen::Screen, config::ScreenConfig; start_renderloop::B return end - replace_renderpass!(config.ssao ? RenderPass{:SSAO} : EmptyRenderStep, 2) - replace_renderpass!(config.oit ? RenderPass{:OIT} : EmptyRenderStep, 5) - replace_renderpass!(config.fxaa ? RenderPass{:FXAA} : EmptyRenderStep, 6) + replace_renderpass!(config.ssao ? RenderPass{:SSAO} : EmptyRenderStep, 3) + replace_renderpass!(config.oit ? RenderPass{:OIT} : EmptyRenderStep, 6) + replace_renderpass!(config.fxaa ? RenderPass{:FXAA} : EmptyRenderStep, 7) # TODO: replace shader programs with lighting to update N_lights & N_light_parameters From 9abac6d28dafe5014f98159233f34d69863b70a2 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 21:47:28 +0100 Subject: [PATCH 10/13] use dedicated framebuffers --- GLMakie/src/GLAbstraction/GLAbstraction.jl | 2 +- GLMakie/src/GLAbstraction/GLFrameBuffer.jl | 8 +- GLMakie/src/glwindow.jl | 90 ++++++++++++---- GLMakie/src/picking.jl | 8 +- GLMakie/src/postprocessing.jl | 118 ++++++++------------- GLMakie/src/precompiles.jl | 2 +- GLMakie/src/rendering.jl | 8 +- GLMakie/src/screen.jl | 20 ++-- 8 files changed, 141 insertions(+), 115 deletions(-) diff --git a/GLMakie/src/GLAbstraction/GLAbstraction.jl b/GLMakie/src/GLAbstraction/GLAbstraction.jl index 1b0044ba4d4..cd8f38132ea 100644 --- a/GLMakie/src/GLAbstraction/GLAbstraction.jl +++ b/GLMakie/src/GLAbstraction/GLAbstraction.jl @@ -102,7 +102,7 @@ export getAttributesInfo include("GLFrameBuffer.jl") export GLRenderbuffer export GLFramebuffer -export attach_colorbuffer, attach_depthbuffer, attach_stencilbuffer +export attach_colorbuffer, attach_depthbuffer, attach_stencilbuffer, attach_depthstencilbuffer export get_attachment, get_buffer export check_framebuffer diff --git a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl index e55bfef7068..6f89fb89bec 100644 --- a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl +++ b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl @@ -76,6 +76,7 @@ function bind(fb::GLFramebuffer) glBindFramebuffer(GL_FRAMEBUFFER, fb.id) return end +unbind(::GLFramebuffer) = glBindFramebuffer(GL_FRAMEBUFFER, 0) function unsafe_free(x::GLFramebuffer) # don't free if already freed @@ -113,6 +114,7 @@ end attach_colorbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, get_next_colorbuffer_attachment(fb)) attach_depthbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_DEPTH_ATTACHMENT) attach_stencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_STENCIL_ATTACHMENT) +attach_depthstencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_DEPTH_STENCIL_ATTACHMENT) function attach(fb::GLFramebuffer, key::Symbol, buffer, attachment::GLenum) haskey(fb, key) && error("Cannot attach $key to Framebuffer because it is already set.") @@ -121,6 +123,8 @@ function attach(fb::GLFramebuffer, key::Symbol, buffer, attachment::GLenum) type = "depth" elseif attachment == GL_STENCIL_ATTACHMENT type = "stencil" + elseif attachment == GL_DEPTH_STENCIL_ATTACHMENT + type = "depth-stencil" else type = "color" end @@ -178,7 +182,7 @@ function Base.show(io::IO, fb::GLFramebuffer) X, Y = fb.size print(io, "$X×$Y GLFrameBuffer(:") join(io, string.(keys(fb.buffers)), ", :") - print(io, ")") + print(io, ") with id ", fb.id) end function attachment_enum_to_string(x::GLenum) @@ -190,7 +194,7 @@ end function Base.show(io::IO, ::MIME"text/plain", fb::GLFramebuffer) X, Y = fb.size - print(io, "$X×$Y GLFrameBuffer()") + print(io, "$X×$Y GLFrameBuffer() with id ", fb.id) ks = collect(keys(fb.buffers)) key_strings = [":$k" for k in ks] diff --git a/GLMakie/src/glwindow.jl b/GLMakie/src/glwindow.jl index b7e580b7b59..bf920a595a1 100644 --- a/GLMakie/src/glwindow.jl +++ b/GLMakie/src/glwindow.jl @@ -10,31 +10,30 @@ end Base.convert(::Type{SelectionID{T}}, s::SelectionID) where T = SelectionID{T}(T(s.id), T(s.index)) Base.zero(::Type{GLMakie.SelectionID{T}}) where T = SelectionID{T}(T(0), T(0)) -mutable struct Framebuffer +mutable struct FramebufferFactory fb::GLFramebuffer + buffers::Dict{Symbol, Texture} # TODO: temp, should be unnamed collection render_buffer_ids::Vector{GLuint} + children::Vector{GLFramebuffer} # TODO: how else can we handle resizing? end # it's guaranteed, that they all have the same size -# forwards... for now -Base.size(fb::Framebuffer) = size(fb.fb) -Base.haskey(fb::Framebuffer, key::Symbol) = haskey(fb.fb, key) -GLAbstraction.get_attachment(fb::Framebuffer, key::Symbol) = get_attachment(fb.fb, key) -GLAbstraction.get_buffer(fb::Framebuffer, key::Symbol) = get_buffer(fb.fb, key) -GLAbstraction.bind(fb::Framebuffer) = GLAbstraction.bind(fb.fb) -GLAbstraction.attach_colorbuffer(fb::Framebuffer, key, val) = GLAbstraction.attach_colorbuffer(fb.fb, key, val) -function getfallback_attachment(fb::Framebuffer, key::Symbol, fallback_key::Symbol) - haskey(fb, key) ? get_attachment(fb, key) : get_attachment(fb, fallback_key) -end -function getfallback_buffer(fb::Framebuffer, key::Symbol, fallback_key::Symbol) - haskey(fb, key) ? get_buffer(fb, key) : get_buffer(fb, fallback_key) -end +# TODO: forwards... for now +Base.size(fb::FramebufferFactory) = size(fb.fb) +Base.haskey(fb::FramebufferFactory, key::Symbol) = haskey(fb.buffers, key) +GLAbstraction.get_buffer(fb::FramebufferFactory, key::Symbol) = fb.buffers[key] +GLAbstraction.bind(fb::FramebufferFactory) = GLAbstraction.bind(fb.fb) +function Base.resize!(fb::FramebufferFactory, w::Int, h::Int) + foreach(tex -> GLAbstraction.resize_nocopy!(tex, (w, h)), values(fb.buffers)) + resize!(fb.fb, w, h) + filter!(fb -> fb.id != 0, fb.children) # TODO: is this ok? + foreach(fb -> resize!(fb, w, h), fb.children) + return +end -Makie.@noconstprop function Framebuffer(fb_size::NTuple{2, Int}) - # Create framebuffer - fb = GLFramebuffer(fb_size) +Makie.@noconstprop function FramebufferFactory(fb_size::NTuple{2, Int}) # Buffers we always need # Holds the image that eventually gets displayed color_buffer = Texture( @@ -59,20 +58,65 @@ Makie.@noconstprop function Framebuffer(fb_size::NTuple{2, Int}) N0f8, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge ) + buffers = Dict{Symbol, Texture}( + :color => color_buffer, + :objectid => objectid_buffer, + :HDR_color => HDR_color_buffer, + :OIT_weight => OIT_weight_buffer, + :depth_stencil => depth_buffer, + ) + + # Create render framebuffer + fb = GLFramebuffer(fb_size) + # attach buffers color_attachment = attach_colorbuffer(fb, :color, color_buffer) objectid_attachment = attach_colorbuffer(fb, :objectid, objectid_buffer) - attach_colorbuffer(fb, :HDR_color, HDR_color_buffer) - attach_colorbuffer(fb, :OIT_weight, OIT_weight_buffer) - attach_depthbuffer(fb, :depth, depth_buffer) - attach_stencilbuffer(fb, :stencil, depth_buffer) + attach_depthstencilbuffer(fb, :depth_stencil, depth_buffer) + attach_colorbuffer(fb, :HDR_color, HDR_color_buffer) # TODO: framebuffer for RenderPlots + attach_colorbuffer(fb, :OIT_weight, OIT_weight_buffer) # TODO: framebuffer for RenderPlots + + check_framebuffer() + + return FramebufferFactory(fb, buffers, [color_attachment, objectid_attachment], GLFramebuffer[]) +end + +# TODO: temporary +function Base.push!(factory::FramebufferFactory, kv::Pair{Symbol, <: Texture}) + if haskey(factory.buffers, kv[1]) + @error("Pushed buffer $(kv[1]) already assigned.") + return + end + push!(factory.buffers, kv) + return +end + +function generate_framebuffer(factory::FramebufferFactory, names...) + filter!(fb -> fb.id != 0, factory.children) # cleanup? + + parse_arg(name::Symbol) = name => name + parse_arg(p::Pair{Symbol, Symbol}) = p + parse_arg(x::Any) = error("$x not accepted") + + fb = GLFramebuffer(size(factory.fb)) + attach_depthstencilbuffer(fb, :depth_stencil, factory.buffers[:depth_stencil]) + + for arg in names + lookup, name = parse_arg(arg) + haskey(factory.buffers, lookup) || error("Add buffers yourself for now") + haskey(fb, name) && error("Can't add duplicate buffer $lookup => $name") + in(lookup, [:depth, :stencil]) && error("Depth and stencil always exist under the same name.") + + attach_colorbuffer(fb, name, factory.buffers[lookup]) + end check_framebuffer() - return Framebuffer(fb, [color_attachment, objectid_attachment]) + push!(factory.children, fb) + + return fb end -Base.resize!(fb::Framebuffer, w::Int, h::Int) = resize!(fb.fb, w, h) struct MonitorProperties diff --git a/GLMakie/src/picking.jl b/GLMakie/src/picking.jl index c785166be3e..c8f3c70821c 100644 --- a/GLMakie/src/picking.jl +++ b/GLMakie/src/picking.jl @@ -6,7 +6,7 @@ function pick_native(screen::Screen, rect::Rect2i) isopen(screen) || return Matrix{SelectionID{Int}}(undef, 0, 0) ShaderAbstractions.switch_context!(screen.glscreen) - fb = screen.framebuffer + fb = screen.framebuffer_factory buff = get_buffer(fb, :objectid) GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) @@ -27,7 +27,7 @@ end function pick_native(screen::Screen, xy::Vec{2, Float64}) isopen(screen) || return SelectionID{Int}(0, 0) ShaderAbstractions.switch_context!(screen.glscreen) - fb = screen.framebuffer + fb = screen.framebuffer_factory buff = get_buffer(fb, :objectid) GLAbstraction.bind(fb) glReadBuffer(GL_COLOR_ATTACHMENT1) @@ -70,7 +70,7 @@ function Makie.pick_closest(scene::Scene, screen::Screen, xy, range) w, h = size(screen.scene) # unitless dimensions ((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) || return (nothing, 0) - fb = screen.framebuffer + fb = screen.framebuffer_factory ppu = screen.px_per_unit[] w, h = size(fb) # pixel dimensions x0, y0 = max.(1, floor.(Int, ppu .* (xy .- range))) @@ -111,7 +111,7 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) return Tuple{AbstractPlot, Int}[] end - fb = screen.framebuffer + fb = screen.framebuffer_factory ppu = screen.px_per_unit[] w, h = size(fb) # pixel dimensions x0, y0 = max.(1, floor.(Int, ppu .* (xy .- range))) diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index bb1d18272a6..86b650ac82e 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -106,21 +106,21 @@ struct RenderPlots <: AbstractRenderStep end function RenderPlots(screen, stage) - fb = screen.framebuffer + fb = screen.framebuffer_factory.fb if stage === :SSAO return RenderPlots( - fb.fb, fb.render_buffer_ids, [3 => Vec4f(0), 4 => Vec4f(0)], + fb, screen.framebuffer_factory.render_buffer_ids, [3 => Vec4f(0), 4 => Vec4f(0)], FilterTrue, FilterFalse, FilterAny) elseif stage === :FXAA return RenderPlots( - fb.fb, get_attachment.(Ref(fb), [:color, :objectid]), Pair{Int, Vec4f}[], + fb, get_attachment.(Ref(fb), [:color, :objectid]), Pair{Int, Vec4f}[], FilterFalse, FilterFalse, FilterAny) elseif stage === :OIT targets = get_attachment.(Ref(fb), [:HDR_color, :objectid, :OIT_weight]) # HDR_color containing sums clears to 0 # OIT_weight containing products clears to 1 clear = [1 => Vec4f(0), 3 => Vec4f(1)] - return RenderPlots(fb.fb, targets, clear, FilterAny, FilterTrue, FilterAny) + return RenderPlots(fb, targets, clear, FilterAny, FilterTrue, FilterAny) else error("Incorrect stage = $stage given. Should be :SSAO, :FXAA or :OIT.") end @@ -178,10 +178,6 @@ end struct RenderPass{Name} <: AbstractRenderStep framebuffer::GLFramebuffer passes::Vector{RenderObject} - renames::Dict{Symbol, Symbol} # TODO: temporary until GLFramebuffer not shared -end -function RenderPass{Name}(framebuffer::GLFramebuffer, passes::Vector{RenderObject}) where {Name} - return RenderPass{Name}(framebuffer, passes, Dict{Symbol, Symbol}()) end @@ -189,7 +185,8 @@ end function RenderPass{:OIT}(screen) @debug "Creating OIT postprocessor" - framebuffer = screen.framebuffer + factory = screen.framebuffer_factory + framebuffer = generate_framebuffer(factory, :color) # Based on https://jcgt.org/published/0002/02/09/, see #1390 # OIT setup @@ -199,8 +196,8 @@ function RenderPass{:OIT}(screen) loadshader("postprocessing/OIT_blend.frag") ) data = Dict{Symbol, Any}( - :sum_color => get_buffer(framebuffer, :HDR_color), - :prod_alpha => get_buffer(framebuffer, :OIT_weight), + :sum_color => get_buffer(factory.fb, :HDR_color), + :prod_alpha => get_buffer(factory.fb, :OIT_weight), ) pass = RenderObject( data, shader, @@ -221,12 +218,13 @@ function RenderPass{:OIT}(screen) ) pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) - return RenderPass{:OIT}(framebuffer.fb, RenderObject[pass]) + return RenderPass{:OIT}(framebuffer, RenderObject[pass]) end function run_step(screen, glscene, step::RenderPass{:OIT}) # Blend transparent onto opaque - wh = size(screen.framebuffer) + GLAbstraction.bind(step.framebuffer) + wh = size(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) glDrawBuffer(get_attachment(step.framebuffer, :color)) GLAbstraction.render(step.passes[1]) @@ -236,32 +234,22 @@ end function RenderPass{:SSAO}(screen) @debug "Creating SSAO postprocessor" - framebuffer = screen.framebuffer - renames = Dict{Symbol, Symbol}() + + factory = screen.framebuffer_factory # Add missing buffers - if !haskey(framebuffer, :position) - # GLAbstraction.bind(framebuffer) - position_buffer = Texture( - Vec3f, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge - ) - pos_id = attach_colorbuffer(framebuffer, :position, position_buffer) - push!(framebuffer.render_buffer_ids, pos_id) - end - if !haskey(framebuffer, :normal) - if !haskey(framebuffer, :HDR_color) - # GLAbstraction.bind(framebuffer) - normal_occlusion_buffer = Texture( - Vec4{Float16}, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge - ) - normal_occ_id = attach_colorbuffer(framebuffer, :normal_occlusion, normal_occlusion_buffer) - renames[:normal_occlusion] = :normal_occlusion - else - normal_occ_id = get_attachment(framebuffer, :HDR_color) - renames[:normal_occlusion] = :HDR_color - end - push!(framebuffer.render_buffer_ids, normal_occ_id) - end + pos_tex = Texture(Vec3f, size(screen), minfilter = :nearest, x_repeat = :clamp_to_edge) + push!(factory, :position => pos_tex) + # HDR_color always exists... + + # TODO: temp - update renderbuffers and main renderbuffer + # eventually RenderPlots should have dedicated GLFramebuffers too, which + # derive their draw buffers from the abstracted render pipeline + pos_id = attach_colorbuffer(factory.fb, :position, pos_tex) + normal_id = attach_colorbuffer(factory.fb, :normal, get_buffer(factory, :HDR_color)) + push!(factory.render_buffer_ids, pos_id, normal_id) + + framebuffer = generate_framebuffer(factory, :color, :HDR_color => :normal_occlusion) # SSAO setup N_samples = 64 @@ -283,14 +271,14 @@ function RenderPass{:SSAO}(screen) ) ) data1 = Dict{Symbol, Any}( - :position_buffer => get_buffer(framebuffer, :position), - :normal_occlusion_buffer => getfallback_buffer(framebuffer, :normal_occlusion, :HDR_color), + :position_buffer => get_buffer(factory.fb, :position), + :normal_occlusion_buffer => get_buffer(factory.fb, :normal), :kernel => kernel, :noise => Texture( [normalize(Vec2f(2.0rand(2) .- 1.0)) for _ in 1:4, __ in 1:4], minfilter = :nearest, x_repeat = :repeat ), - :noise_scale => Vec2f(0.25f0 .* size(framebuffer)), + :noise_scale => Vec2f(0.25f0 .* size(screen)), :projection => Observable(Mat4f(I)), :bias => 0.025f0, :radius => 0.5f0 @@ -306,23 +294,24 @@ function RenderPass{:SSAO}(screen) loadshader("postprocessing/SSAO_blur.frag") ) data2 = Dict{Symbol, Any}( - :normal_occlusion => getfallback_buffer(framebuffer, :normal_occlusion, :HDR_color), - :color_texture => get_buffer(framebuffer, :color), - :ids => get_buffer(framebuffer, :objectid), - :inv_texel_size => rcpframe(size(framebuffer)), + :normal_occlusion => get_buffer(framebuffer, :normal_occlusion), + :color_texture => get_buffer(factory.fb, :color), + :ids => get_buffer(factory.fb, :objectid), + :inv_texel_size => rcpframe(size(screen)), :blur_range => Int32(2) ) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - return RenderPass{:SSAO}(framebuffer.fb, [pass1, pass2], renames) + return RenderPass{:SSAO}(framebuffer, [pass1, pass2]) end function run_step(screen, glscene, step::RenderPass{:SSAO}) - wh = size(screen.framebuffer) + GLAbstraction.bind(step.framebuffer) + wh = size(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(get_attachment(step.framebuffer, step.renames[:normal_occlusion])) # occlusion buffer + glDrawBuffer(get_attachment(step.framebuffer, :normal_occlusion)) # occlusion buffer glEnable(GL_SCISSOR_TEST) ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) @@ -364,23 +353,8 @@ end function RenderPass{:FXAA}(screen) @debug "Creating FXAA postprocessor" - framebuffer = screen.framebuffer - renames = Dict{Symbol, Symbol}() - - # Add missing buffers - if !haskey(framebuffer, :color_luma) - if !haskey(framebuffer, :HDR_color) - # GLAbstraction.bind(framebuffer) - color_luma_buffer = Texture( - RGBA{N0f8}, size(framebuffer), minfilter=:linear, x_repeat=:clamp_to_edge - ) - luma_id = attach_colorbuffer(framebuffer, :color_luma, color_luma_buffer) - renames[:color_luma] = :color_luma - else - luma_id = get_attachment(framebuffer, :HDR_color) - renames[:color_luma] = :HDR_color - end - end + factory = screen.framebuffer_factory + framebuffer = generate_framebuffer(factory, :color, :HDR_color => :color_luma) # calculate luma for FXAA shader1 = LazyShader( @@ -389,8 +363,8 @@ function RenderPass{:FXAA}(screen) loadshader("postprocessing/postprocess.frag") ) data1 = Dict{Symbol, Any}( - :color_texture => get_buffer(framebuffer, :color), - :object_ids => get_buffer(framebuffer, :objectid) + :color_texture => get_buffer(factory.fb, :color), + :object_ids => get_buffer(factory.fb, :objectid) ) pass1 = RenderObject(data1, shader1, PostprocessPrerender(), nothing) pass1.postrenderfunction = () -> draw_fullscreen(pass1.vertexarray.id) @@ -402,22 +376,24 @@ function RenderPass{:FXAA}(screen) loadshader("postprocessing/fxaa.frag") ) data2 = Dict{Symbol, Any}( - :color_texture => getfallback_buffer(framebuffer, :color_luma, :HDR_color), + :color_texture => get_buffer(framebuffer, :color_luma), :RCPFrame => rcpframe(size(framebuffer)), ) pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - return RenderPass{:FXAA}(framebuffer.fb, RenderObject[pass1, pass2], renames) + return RenderPass{:FXAA}(framebuffer, RenderObject[pass1, pass2]) end function run_step(screen, glscene, step::RenderPass{:FXAA}) + GLAbstraction.bind(step.framebuffer) + # TODO: make scissor explicit? - wh = size(screen.framebuffer) + wh = size(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) # FXAA - calculate LUMA - glDrawBuffer(get_attachment(step.framebuffer, step.renames[:color_luma])) + glDrawBuffer(get_attachment(step.framebuffer, :color_luma)) # necessary with negative SSAO bias... glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) @@ -441,7 +417,7 @@ struct BlitToScreen <: AbstractRenderStep # Screen not available yet function BlitToScreen(screen, screen_framebuffer_id::Integer = 0) @debug "Creating to screen postprocessor" - return new(screen.framebuffer.fb, screen_framebuffer_id) + return new(screen.framebuffer_factory.fb, screen_framebuffer_id) end end diff --git a/GLMakie/src/precompiles.jl b/GLMakie/src/precompiles.jl index a8b81d09e12..b27033a54dd 100644 --- a/GLMakie/src/precompiles.jl +++ b/GLMakie/src/precompiles.jl @@ -60,7 +60,7 @@ let end precompile(Screen, (Scene, ScreenConfig)) -precompile(Framebuffer, (NTuple{2,Int},)) +precompile(FramebufferFactory, (NTuple{2,Int},)) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{Float32})) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{RGBAf})) precompile(glTexImage, (GLenum, Int, GLenum, Int, Int, Int, GLenum, GLenum, Ptr{RGBf})) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 330aaaf44c6..1fd18750f6b 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -3,13 +3,15 @@ function setup!(screen::Screen, resize_buffers) ShaderAbstractions.switch_context!(to_native(screen)) # Resize framebuffer to window size - fb = screen.framebuffer - GLAbstraction.bind(fb) + fb = screen.framebuffer_factory.fb if resize_buffers && !isnothing(screen.scene) ppu = screen.px_per_unit[] - resize!(fb, round.(Int, ppu .* size(screen.scene))...) + resize!(screen.framebuffer_factory, round.(Int, ppu .* size(screen.scene))...) end + GLAbstraction.bind(fb) + glViewport(0, 0, size(fb)...) + # clear objectid, depth and stencil glDrawBuffer(get_attachment(fb, :objectid)) glClearColor(0, 0, 0, 0) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 8d319361c25..0707aa79705 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -162,7 +162,7 @@ mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow owns_glscreen::Bool shader_cache::GLAbstraction.ShaderCache - framebuffer::Framebuffer + framebuffer_factory::FramebufferFactory config::Union{Nothing, ScreenConfig} stop_renderloop::Threads.Atomic{Bool} rendertask::Union{Task, Nothing} @@ -190,7 +190,7 @@ mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow, owns_glscreen::Bool, shader_cache::GLAbstraction.ShaderCache, - framebuffer::Framebuffer, + framebuffer_factory::FramebufferFactory, config::Union{Nothing, ScreenConfig}, stop_renderloop::Bool, rendertask::Union{Nothing, Task}, @@ -203,9 +203,9 @@ mutable struct Screen{GLWindow} <: MakieScreen reuse::Bool ) where {GLWindow} - s = size(framebuffer) + s = size(framebuffer_factory) screen = new{GLWindow}( - glscreen, owns_glscreen, shader_cache, framebuffer, + glscreen, owns_glscreen, shader_cache, framebuffer_factory, config, Threads.Atomic{Bool}(stop_renderloop), rendertask, BudgetedTimer(1.0 / 30.0), Observable(0f0), screen2scene, screens, renderlist, AbstractRenderStep[], cache, cache2plot, @@ -217,7 +217,7 @@ mutable struct Screen{GLWindow} <: MakieScreen end end -framebuffer_size(screen::Screen) = size(screen.framebuffer) +framebuffer_size(screen::Screen) = size(screen.framebuffer_factory) Makie.isvisible(screen::Screen) = screen.config.visible @@ -279,7 +279,7 @@ Makie.@noconstprop function empty_screen(debugging::Bool, reuse::Bool, window) # This is important for resource tracking, and only needed for the first context ShaderAbstractions.switch_context!(window) shader_cache = GLAbstraction.ShaderCache(window) - fb = Framebuffer(initial_resolution) + fb = FramebufferFactory(initial_resolution) screen = Screen( window, owns_glscreen, shader_cache, fb, @@ -500,7 +500,7 @@ Base.wait(scene::Scene) = wait(Makie.getscreen(scene)) Base.show(io::IO, screen::Screen) = print(io, "GLMakie.Screen(...)") Base.isopen(x::Screen) = isopen(x.glscreen) -Base.size(x::Screen) = size(x.framebuffer) +Base.size(x::Screen) = size(x.framebuffer_factory) function add_scene!(screen::Screen, scene::Scene) get!(screen.screen2scene, WeakRef(scene)) do @@ -731,7 +731,7 @@ function Base.resize!(screen::Screen, w::Int, h::Int) # independently of the window scale factor. fbscale = screen.px_per_unit[] fbw, fbh = round.(Int, fbscale .* (w, h)) - resize!(screen.framebuffer, fbw, fbh) + resize!(screen.framebuffer_factory, fbw, fbh) return nothing end @@ -763,7 +763,7 @@ function depthbuffer(screen::Screen) ShaderAbstractions.switch_context!(screen.glscreen) render_frame(screen, resize_buffers=false) # let it render glFinish() # block until opengl is done rendering - source = get_buffer(screen.framebuffer, :depth) + source = get_buffer(screen.framebuffer_factory, :depth) depth = Matrix{Float32}(undef, size(source)) GLAbstraction.bind(source) GLAbstraction.glGetTexImage(source.texturetype, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depth) @@ -776,7 +776,7 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma error("Screen not open!") end ShaderAbstractions.switch_context!(screen.glscreen) - ctex = get_buffer(screen.framebuffer, :color) + ctex = get_buffer(screen.framebuffer_factory, :color) # 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) From 5764c4bde88f51684b809b84053b85756360f56d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 22:05:04 +0100 Subject: [PATCH 11/13] fix tests --- GLMakie/test/unit_tests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 53cfcfc129f..2d035533ec0 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -326,7 +326,7 @@ end screen = display(GLMakie.Screen(visible = true, scalefactor = 2), fig) @test screen.scalefactor[] === 2f0 @test screen.px_per_unit[] === 2f0 # inherited from scale factor - @test size(screen.framebuffer) == (2W, 2H) + @test size(screen.framebuffer_factory) == (2W, 2H) @test GLMakie.window_size(screen.glscreen) == scaled(screen, (W, H)) # check that picking works through the resized GL buffers @@ -353,7 +353,7 @@ end screen = display(GLMakie.Screen(visible = false, scalefactor = 2, px_per_unit = 1), fig) @test screen.scalefactor[] === 2f0 @test screen.px_per_unit[] === 1f0 - @test size(screen.framebuffer) == (W, H) + @test size(screen.framebuffer_factory) == (W, H) # decrease the scale factor after-the-fact screen.scalefactor[] = 1 From 38beac8d3df05448b4cb1e348845744dd26c3346 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 22:42:30 +0100 Subject: [PATCH 12/13] fix depthbuffer() --- GLMakie/src/screen.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 0707aa79705..3b0abec985f 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -763,7 +763,7 @@ function depthbuffer(screen::Screen) ShaderAbstractions.switch_context!(screen.glscreen) render_frame(screen, resize_buffers=false) # let it render glFinish() # block until opengl is done rendering - source = get_buffer(screen.framebuffer_factory, :depth) + source = get_buffer(screen.framebuffer_factory, :depth_stencil) depth = Matrix{Float32}(undef, size(source)) GLAbstraction.bind(source) GLAbstraction.glGetTexImage(source.texturetype, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depth) From 1d28aa8af8a5864d924378ba88782806af2f183a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 24 Dec 2024 23:37:56 +0100 Subject: [PATCH 13/13] simplify glDrawBuffer(s) --- GLMakie/src/GLAbstraction/GLAbstraction.jl | 1 + GLMakie/src/GLAbstraction/GLFrameBuffer.jl | 92 ++++++++++++++-------- GLMakie/src/postprocessing.jl | 19 ++--- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/GLMakie/src/GLAbstraction/GLAbstraction.jl b/GLMakie/src/GLAbstraction/GLAbstraction.jl index cd8f38132ea..96317021e63 100644 --- a/GLMakie/src/GLAbstraction/GLAbstraction.jl +++ b/GLMakie/src/GLAbstraction/GLAbstraction.jl @@ -104,6 +104,7 @@ export GLRenderbuffer export GLFramebuffer export attach_colorbuffer, attach_depthbuffer, attach_stencilbuffer, attach_depthstencilbuffer export get_attachment, get_buffer +export set_draw_buffers export check_framebuffer end # module diff --git a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl index 6f89fb89bec..548ec8c1112 100644 --- a/GLMakie/src/GLAbstraction/GLFrameBuffer.jl +++ b/GLMakie/src/GLAbstraction/GLFrameBuffer.jl @@ -48,8 +48,9 @@ mutable struct GLFramebuffer context::GLContext - attachments::Dict{Symbol, GLenum} - buffers::Dict{Symbol, Texture} + name2idx::Dict{Symbol, Int} + attachments::Vector{GLenum} + buffers::Vector{Texture} counter::UInt32 # for color attachments function GLFramebuffer(size::NTuple{2, Int}) @@ -59,9 +60,7 @@ mutable struct GLFramebuffer obj = new( id, size, current_context(), - Dict{Symbol, GLuint}(), - Dict{Symbol, Texture}(), - UInt32(0) + Dict{Symbol, Int}(), GLenum[], Texture[], UInt32(0) ) finalizer(free, obj) @@ -69,14 +68,32 @@ mutable struct GLFramebuffer end end -function bind(fb::GLFramebuffer) +function bind(fb::GLFramebuffer, target = fb.id) if fb.id == 0 error("Binding freed GLFramebuffer") end - glBindFramebuffer(GL_FRAMEBUFFER, fb.id) + glBindFramebuffer(GL_FRAMEBUFFER, target) return end -unbind(::GLFramebuffer) = glBindFramebuffer(GL_FRAMEBUFFER, 0) + +""" + draw_buffers(fb::GLFrameBuffer[, N::Int]) + +Activates the first N color buffers attached to the given GLFramebuffer. If N +is not given all color attachments are activated. +""" +function set_draw_buffers(fb::GLFramebuffer, N::Integer = fb.counter) + bind(fb) + glDrawBuffers(N, fb.attachments) +end +function set_draw_buffers(fb::GLFramebuffer, key::Symbol) + bind(fb) + glDrawBuffer(get_attachment(fb, key)) +end +function set_draw_buffers(fb::GLFramebuffer, keys::Symbol...) + bind(fb) + glDrawBuffer(get_attachment.(Ref(fb), keys)) +end function unsafe_free(x::GLFramebuffer) # don't free if already freed @@ -91,11 +108,11 @@ function unsafe_free(x::GLFramebuffer) end Base.size(fb::GLFramebuffer) = fb.size -Base.haskey(fb::GLFramebuffer, key::Symbol) = haskey(fb.buffers, key) +Base.haskey(fb::GLFramebuffer, key::Symbol) = haskey(fb.name2idx, key) function Base.resize!(fb::GLFramebuffer, w::Int, h::Int) (w > 0 && h > 0 && (w, h) != size(fb)) || return - for (key, buffer) in fb.buffers + for buffer in fb.buffers resize_nocopy!(buffer, (w, h)) end fb.size = (w, h) @@ -108,17 +125,25 @@ function get_next_colorbuffer_attachment(fb::GLFramebuffer) end attachment = GL_COLOR_ATTACHMENT0 + fb.counter fb.counter += 1 - return attachment + return (fb.counter, attachment) end -attach_colorbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, get_next_colorbuffer_attachment(fb)) -attach_depthbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_DEPTH_ATTACHMENT) -attach_stencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_STENCIL_ATTACHMENT) -attach_depthstencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) = attach(fb, key, buffer, GL_DEPTH_STENCIL_ATTACHMENT) +function attach_colorbuffer(fb::GLFramebuffer, key::Symbol, buffer) + return attach(fb, key, buffer, get_next_colorbuffer_attachment(fb)...) +end +function attach_depthbuffer(fb::GLFramebuffer, key::Symbol, buffer) + return attach(fb, key, buffer, length(fb.attachments) + 1, GL_DEPTH_ATTACHMENT) +end +function attach_stencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) + return attach(fb, key, buffer, length(fb.attachments) + 1, GL_STENCIL_ATTACHMENT) +end +function attach_depthstencilbuffer(fb::GLFramebuffer, key::Symbol, buffer) + return attach(fb, key, buffer, length(fb.attachments) + 1, GL_DEPTH_STENCIL_ATTACHMENT) +end -function attach(fb::GLFramebuffer, key::Symbol, buffer, attachment::GLenum) +function attach(fb::GLFramebuffer, key::Symbol, buffer, idx::Integer, attachment::GLenum) haskey(fb, key) && error("Cannot attach $key to Framebuffer because it is already set.") - if in(attachment, keys(fb.buffers)) + if attachment in fb.attachments if attachment == GL_DEPTH_ATTACHMENT type = "depth" elseif attachment == GL_STENCIL_ATTACHMENT @@ -132,21 +157,27 @@ function attach(fb::GLFramebuffer, key::Symbol, buffer, attachment::GLenum) end bind(fb) - _attach(buffer, attachment) - fb.attachments[key] = attachment - fb.buffers[key] = buffer + gl_attach(buffer, attachment) + # keep depth/stenctil/depth_stencil at end so that we can directly use + # fb.attachments when setting drawbuffers + for (k, v) in fb.name2idx + fb.name2idx[k] = ifelse(v < idx, v, v+1) + end + fb.name2idx[key] = idx + insert!(fb.attachments, idx, attachment) + insert!(fb.buffers, idx, buffer) return attachment end -function _attach(t::Texture{T, 2}, attachment::GLenum) where {T} +function gl_attach(t::Texture{T, 2}, attachment::GLenum) where {T} glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, t.id, 0) end -function _attach(buffer::RenderBuffer, attachment::GLenum) +function gl_attach(buffer::RenderBuffer, attachment::GLenum) glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer) end -get_attachment(fb::GLFramebuffer, key::Symbol) = fb.attachments[key] -get_buffer(fb::GLFramebuffer, key::Symbol) = fb.buffers[key] +get_attachment(fb::GLFramebuffer, key::Symbol) = fb.attachments[fb.name2idx[key]] +get_buffer(fb::GLFramebuffer, key::Symbol) = fb.buffers[fb.name2idx[key]] function enum_to_error(s) s == GL_FRAMEBUFFER_COMPLETE && return @@ -181,7 +212,7 @@ end function Base.show(io::IO, fb::GLFramebuffer) X, Y = fb.size print(io, "$X×$Y GLFrameBuffer(:") - join(io, string.(keys(fb.buffers)), ", :") + join(io, string.(keys(fb.name2idx)), ", :") print(io, ") with id ", fb.id) end @@ -196,18 +227,17 @@ function Base.show(io::IO, ::MIME"text/plain", fb::GLFramebuffer) X, Y = fb.size print(io, "$X×$Y GLFrameBuffer() with id ", fb.id) - ks = collect(keys(fb.buffers)) + ks = collect(keys(fb.name2idx)) + sort!(ks, by = k -> fb.name2idx[k]) key_strings = [":$k" for k in ks] key_pad = mapreduce(length, max, key_strings) key_strings = rpad.(key_strings, key_pad) - attachments = map(key -> attachment_enum_to_string(get_attachment(fb, key)), ks) - idxs = sortperm(attachments) + attachments = attachment_enum_to_string(fb.attachments) attachment_pad = mapreduce(length, max, attachments) attachments = rpad.(attachments, attachment_pad) - for i in idxs - T = typeof(get_buffer(fb, ks[i])) - print(io, "\n ", key_strings[i], " => ", attachments[i], " ::", T) + for (key, attachment, buffer) in zip(key_strings, attachments, fb.buffers) + print(io, "\n ", key, " => ", attachment, " ::", typeof(buffer)) end end \ No newline at end of file diff --git a/GLMakie/src/postprocessing.jl b/GLMakie/src/postprocessing.jl index 86b650ac82e..f9a193eba4a 100644 --- a/GLMakie/src/postprocessing.jl +++ b/GLMakie/src/postprocessing.jl @@ -223,10 +223,9 @@ end function run_step(screen, glscene, step::RenderPass{:OIT}) # Blend transparent onto opaque - GLAbstraction.bind(step.framebuffer) wh = size(step.framebuffer) + set_draw_buffers(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(get_attachment(step.framebuffer, :color)) GLAbstraction.render(step.passes[1]) return end @@ -307,11 +306,10 @@ function RenderPass{:SSAO}(screen) end function run_step(screen, glscene, step::RenderPass{:SSAO}) - GLAbstraction.bind(step.framebuffer) - wh = size(step.framebuffer) + set_draw_buffers(step.framebuffer, :normal_occlusion) # occlusion buffer + wh = size(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) - glDrawBuffer(get_attachment(step.framebuffer, :normal_occlusion)) # occlusion buffer glEnable(GL_SCISSOR_TEST) ppu = (x) -> round.(Int, screen.px_per_unit[] .* x) @@ -333,7 +331,7 @@ function run_step(screen, glscene, step::RenderPass{:SSAO}) end # SSAO - blur occlusion and apply to color - glDrawBuffer(get_attachment(step.framebuffer, :color)) # color buffer + set_draw_buffers(step.framebuffer, :color) # color buffer data2 = step.passes[2].uniforms for (screenid, scene) in screen.screens # Select the area of one leaf scene @@ -386,21 +384,18 @@ function RenderPass{:FXAA}(screen) end function run_step(screen, glscene, step::RenderPass{:FXAA}) - GLAbstraction.bind(step.framebuffer) - + # FXAA - calculate LUMA + set_draw_buffers(step.framebuffer, :color_luma) # TODO: make scissor explicit? wh = size(step.framebuffer) glViewport(0, 0, wh[1], wh[2]) - - # FXAA - calculate LUMA - glDrawBuffer(get_attachment(step.framebuffer, :color_luma)) # necessary with negative SSAO bias... glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) GLAbstraction.render(step.passes[1]) # FXAA - perform anti-aliasing - glDrawBuffer(get_attachment(step.framebuffer, :color)) # color buffer + set_draw_buffers(step.framebuffer, :color) # color buffer step.passes[2][:RCPFrame] = rcpframe(size(step.framebuffer)) GLAbstraction.render(step.passes[2])