diff --git a/Project.toml b/Project.toml index 45c9a75..ba2f369 100644 --- a/Project.toml +++ b/Project.toml @@ -22,7 +22,7 @@ ShaderAbstractions = "65257c39-d410-5151-9873-9b3e5be5013e" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] -AbstractPlotting = "0.15.1" +AbstractPlotting = "0.15.12" ColorTypes = "0.9, 0.10" Colors = "0.11, 0.12" FileIO = "1.1" diff --git a/src/GLVisualize/assets/shader/fragment_output.frag b/src/GLVisualize/assets/shader/fragment_output.frag index 7ce6b2b..f334cbc 100644 --- a/src/GLVisualize/assets/shader/fragment_output.frag +++ b/src/GLVisualize/assets/shader/fragment_output.frag @@ -2,8 +2,8 @@ layout(location=0) out vec4 fragment_color; layout(location=1) out uvec2 fragment_groupid; -layout(location=2) out vec4 fragment_position; -layout(location=3) out vec4 fragment_normal_occlusion; +{{buffers}} + in vec4 o_view_pos; in vec3 o_normal; @@ -15,7 +15,5 @@ void write2framebuffer(vec4 color, uvec2 id){ fragment_color = color; // For plot/sprite picking fragment_groupid = id; - // For SSAO - fragment_position = o_view_pos; - fragment_normal_occlusion.xyz = o_normal; + {{buffer_writes}} } diff --git a/src/GLVisualize/visualize_interface.jl b/src/GLVisualize/visualize_interface.jl index c3b6ba6..08601a1 100644 --- a/src/GLVisualize/visualize_interface.jl +++ b/src/GLVisualize/visualize_interface.jl @@ -74,7 +74,9 @@ struct GLVisualizeShader <: AbstractLazyShader view["GLSL_EXTENSIONS"] = "#extension GL_ARB_conservative_depth: enable" view["SUPPORTED_EXTENSIONS"] = "#define DETPH_LAYOUT" end - args = Dict{Symbol,Any}(kw_args) + view["buffers"] = get_buffers() + view["buffer_writes"] = get_buffer_writes() + args = Dict{Symbol, Any}(kw_args) args[:view] = view args[:fragdatalocation] = [(0, "fragment_color"), (1, "fragment_groupid")] new(map(x -> assetpath("shader", x), paths), args) @@ -165,3 +167,27 @@ function visualize(@nospecialize(main), @nospecialize(s), @nospecialize(data)) end return assemble_shader(data) end + +# Make changes to fragment_output to match what's needed for postprocessing +using ..GLMakie: enable_SSAO +function get_buffers() + if enable_SSAO[] + """ + layout(location=2) out vec4 fragment_position; + layout(location=3) out vec3 fragment_normal_occlusion; + """ + else + "" + end +end + +function get_buffer_writes() + if enable_SSAO[] + """ + fragment_position = o_view_pos; + fragment_normal_occlusion.xyz = o_normal; + """ + else + "" + end +end diff --git a/src/gl_backend.jl b/src/gl_backend.jl index a9efab1..788c2ce 100644 --- a/src/gl_backend.jl +++ b/src/gl_backend.jl @@ -44,10 +44,15 @@ function get_texture!(atlas) return tex end +# TODO +const enable_SSAO = Ref(false) +const enable_FXAA = Ref(true) + include("GLVisualize/GLVisualize.jl") using .GLVisualize include("glwindow.jl") +include("postprocessing.jl") include("screen.jl") include("rendering.jl") include("events.jl") diff --git a/src/glwindow.jl b/src/glwindow.jl index fff84ed..022fca6 100644 --- a/src/glwindow.jl +++ b/src/glwindow.jl @@ -8,147 +8,19 @@ struct SelectionID{T <: Integer} <: FieldVector{2, T} index::T end -function draw_fullscreen(vao_id) - glBindVertexArray(vao_id) - glDrawArrays(GL_TRIANGLES, 0, 3) - glBindVertexArray(0) - return -end - -struct PostprocessPrerender end - -function (sp::PostprocessPrerender)() - glDepthMask(GL_TRUE) - glDisable(GL_DEPTH_TEST) - glDisable(GL_BLEND) - glDisable(GL_CULL_FACE) - return -end - -const PostProcessROBJ = RenderObject{PostprocessPrerender} mutable struct GLFramebuffer resolution::Node{NTuple{2, Int}} id::NTuple{2, GLuint} - color::Texture{RGBA{N0f8}, 2} - objectid::Texture{Vec{2, GLuint}, 2} - depth::Texture{GLAbstraction.DepthStencil_24_8, 2} - position::Texture{Vec4f0, 2} - normal_occlusion::Texture{Vec4f0, 2} - ssao_noise::Texture{Vec2f0, 2} - - color_luma::Texture{RGBA{N0f8}, 2} - - postprocess::NTuple{5, PostProcessROBJ} + buffers::Dict{Symbol, Texture} + render_buffer_ids::Vector{GLuint} end -Base.size(fb::GLFramebuffer) = size(fb.color) # it's guaranteed, that they all have the same size - -loadshader(name) = joinpath(@__DIR__, "GLVisualize", "assets", "shader", name) - -rcpframe(x) = 1f0 ./ Vec2f0(x[1], x[2]) - -""" -Creates a postprocessing render object. -This will transfer the pixels from the color texture of the Framebuffer -to the screen and while at it, it can do some postprocessing (not doing it right now): -E.g fxaa anti aliasing, color correction etc. -""" -function postprocess( - color, position, normal_occlusion, ssao_noise, - objectid, color_luma, - framebuffer_size - ) - - # SSAO setup - N_samples = 64 - lerp_min = 0.1f0 - lerp_max = 1.0f0 - kernel = map(1:N_samples) do i - n = normalize([2.0rand() .- 1.0, 2.0rand() .- 1.0, rand()]) - scale = lerp_min + (lerp_max - lerp_min) * (i / N_samples)^2 - v = Vec3f0(scale * rand() * n) - end - - # compute occlusion - shader1 = LazyShader( - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/SSAO.frag"), - view = Dict( - "N_samples" => "$N_samples" - ) - ) - data1 = Dict{Symbol, Any}( - :position_buffer => position, - :normal_occlusion_buffer => normal_occlusion, - :kernel => kernel, - :noise => ssao_noise, - :noise_scale => map(s -> Vec2f0(s ./ 4.0), framebuffer_size), - :projection => Node(Mat4f0(I)), - :bias => Node(0.025f0), - :radius => Node(0.5f0) - ) - pass1 = RenderObject(data1, shader1, PostprocessPrerender(), nothing) - pass1.postrenderfunction = () -> draw_fullscreen(pass1.vertexarray.id) +# it's guaranteed, that they all have the same size +Base.size(fb::GLFramebuffer) = size(fb.buffers[:color]) - # blur occlusion and combine with color - shader2 = LazyShader( - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/SSAO_blur.frag") - ) - data2 = Dict{Symbol, Any}( - :normal_occlusion => normal_occlusion, - :color_texture => color, - :ids => objectid, - :inv_texel_size => map(t -> Vec2f0(1f0/t[1], 1f0/t[2]), framebuffer_size), - :blur_range => Node(Int32(2)) - ) - pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) - pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) - - - # calculate luma for FXAA - shader3 = LazyShader( - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/postprocess.frag") - ) - data3 = Dict{Symbol, Any}( - :color_texture => color - ) - pass3 = RenderObject(data3, shader3, PostprocessPrerender(), nothing) - pass3.postrenderfunction = () -> draw_fullscreen(pass3.vertexarray.id) - - - # perform FXAA - shader4 = LazyShader( - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/fxaa.frag") - ) - data4 = Dict{Symbol, Any}( - :color_texture => color_luma, - :RCPFrame => lift(rcpframe, framebuffer_size), - ) - pass4 = RenderObject(data4, shader4, PostprocessPrerender(), nothing) - pass4.postrenderfunction = () -> draw_fullscreen(pass4.vertexarray.id) - - - # draw color buffer - shader5 = LazyShader( - loadshader("postprocessing/fullscreen.vert"), - loadshader("postprocessing/copy.frag") - ) - data5 = Dict{Symbol, Any}( - :color_texture => color - ) - pass5 = RenderObject(data5, shader5, PostprocessPrerender(), nothing) - pass5.postrenderfunction = () -> draw_fullscreen(pass5.vertexarray.id) - - - return (pass1, pass2, pass3, pass4, pass5) -end - function attach_framebuffer(t::Texture{T, 2}, attachment) where T glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, t.id, 0) end @@ -160,8 +32,6 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) color_buffer = Texture(RGBA{N0f8}, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge) objectid_buffer = Texture(Vec{2, GLuint}, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge) - position_buffer = Texture(Vec4f0, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge) - normal_occlusion_buffer = Texture(Vec4f0, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge) depth_buffer = Texture( Ptr{GLAbstraction.DepthStencil_24_8}(C_NULL), fb_size, @@ -170,15 +40,8 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) format = GL_DEPTH_STENCIL ) - ssao_noise = Texture( - [normalize(Vec2f0(2.0rand(2) .- 1.0)) for _ in 1:4, __ in 1:4], - minfilter = :nearest, x_repeat = :repeat - ) - attach_framebuffer(color_buffer, GL_COLOR_ATTACHMENT0) attach_framebuffer(objectid_buffer, GL_COLOR_ATTACHMENT1) - attach_framebuffer(position_buffer, GL_COLOR_ATTACHMENT2) - attach_framebuffer(normal_occlusion_buffer, GL_COLOR_ATTACHMENT3) attach_framebuffer(depth_buffer, GL_DEPTH_ATTACHMENT) attach_framebuffer(depth_buffer, GL_STENCIL_ATTACHMENT) @@ -187,42 +50,35 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) # Second Framebuffer + # postprocessor adds buffers here color_luma_framebuffer = glGenFramebuffers() glBindFramebuffer(GL_FRAMEBUFFER, color_luma_framebuffer) - color_luma = Texture(RGBA{N0f8}, fb_size, minfilter=:linear, x_repeat=:clamp_to_edge) - attach_framebuffer(color_luma, GL_COLOR_ATTACHMENT0) - @assert status == GL_FRAMEBUFFER_COMPLETE glBindFramebuffer(GL_FRAMEBUFFER, 0) fb_size_node = Node(fb_size) - p = postprocess( - color_buffer, position_buffer, normal_occlusion_buffer, ssao_noise, #occlusion, - objectid_buffer, color_luma, - fb_size_node + buffers = Dict( + :color => color_buffer, + :objectid => objectid_buffer, + :depth => depth_buffer ) return GLFramebuffer( fb_size_node, (render_framebuffer, color_luma_framebuffer), - color_buffer, objectid_buffer, depth_buffer, - position_buffer, normal_occlusion_buffer, ssao_noise,# occlusion, - color_luma, - p + buffers, + [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1] ) end function Base.resize!(fb::GLFramebuffer, window_size) ws = Int.((window_size[1], window_size[2])) if ws != size(fb) && all(x-> x > 0, window_size) - resize_nocopy!(fb.color, ws) - resize_nocopy!(fb.objectid, ws) - resize_nocopy!(fb.depth, ws) - resize_nocopy!(fb.position, ws) - resize_nocopy!(fb.normal_occlusion, ws) - resize_nocopy!(fb.color_luma, ws) + for (name, buffer) in fb.buffers + resize_nocopy!(buffer, ws) + end fb.resolution[] = ws end nothing diff --git a/src/postprocessing.jl b/src/postprocessing.jl new file mode 100644 index 0000000..d2abc04 --- /dev/null +++ b/src/postprocessing.jl @@ -0,0 +1,256 @@ +# Utilities: +function draw_fullscreen(vao_id) + glBindVertexArray(vao_id) + glDrawArrays(GL_TRIANGLES, 0, 3) + glBindVertexArray(0) + return +end + +struct PostprocessPrerender end + +function (sp::PostprocessPrerender)() + glDepthMask(GL_TRUE) + glDisable(GL_DEPTH_TEST) + glDisable(GL_BLEND) + glDisable(GL_CULL_FACE) + return +end + +const PostProcessROBJ = RenderObject{PostprocessPrerender} + +rcpframe(x) = 1f0 ./ Vec2f0(x[1], x[2]) +loadshader(name) = joinpath(@__DIR__, "GLVisualize", "assets", "shader", name) + + + + +struct PostProcessor{F} + robjs::Vector{PostProcessROBJ} + render::F +end + +function empty_postprocessor(args...; kwargs...) + PostProcessor(PostProcessROBJ[], screen -> nothing) +end + + + +function ssao_postprocessor(framebuffer) + # Add missing buffers + if !haskey(framebuffer.buffers, :position) + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + position_buffer = Texture( + Vec4f0, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge + ) + attach_framebuffer(position_buffer, GL_COLOR_ATTACHMENT2) + push!(framebuffer.buffers, :position => position_buffer) + end + if !haskey(framebuffer.buffers, :normal) + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + normal_occlusion_buffer = Texture( + Vec4f0, size(framebuffer), minfilter = :nearest, x_repeat = :clamp_to_edge + ) + attach_framebuffer(normal_occlusion_buffer, GL_COLOR_ATTACHMENT3) + push!(framebuffer.buffers, :normal_occlusion => normal_occlusion_buffer) + end + + # Add buffers written in primary render (before postprocessing) + if !(GL_COLOR_ATTACHMENT2 in framebuffer.render_buffer_ids) + push!(framebuffer.render_buffer_ids, GL_COLOR_ATTACHMENT2) + end + if !(GL_COLOR_ATTACHMENT3 in framebuffer.render_buffer_ids) + push!(framebuffer.render_buffer_ids, GL_COLOR_ATTACHMENT3) + end + + # SSAO setup + N_samples = 64 + lerp_min = 0.1f0 + lerp_max = 1.0f0 + kernel = map(1:N_samples) do i + n = normalize([2.0rand() .- 1.0, 2.0rand() .- 1.0, rand()]) + scale = lerp_min + (lerp_max - lerp_min) * (i / N_samples)^2 + v = Vec3f0(scale * rand() * n) + end + + + + # compute occlusion + shader1 = LazyShader( + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/SSAO.frag"), + view = Dict( + "N_samples" => "$N_samples" + ) + ) + data1 = Dict{Symbol, Any}( + :position_buffer => framebuffer.buffers[:position], + :normal_occlusion_buffer => framebuffer.buffers[:normal_occlusion], + :kernel => kernel, + :noise => Texture( + [normalize(Vec2f0(2.0rand(2) .- 1.0)) for _ in 1:4, __ in 1:4], + minfilter = :nearest, x_repeat = :repeat + ), + :noise_scale => map(s -> Vec2f0(s ./ 4.0), framebuffer.resolution), + :projection => Node(Mat4f0(I)), + :bias => Node(0.025f0), + :radius => Node(0.5f0) + ) + pass1 = RenderObject(data1, shader1, PostprocessPrerender(), nothing) + pass1.postrenderfunction = () -> draw_fullscreen(pass1.vertexarray.id) + + + # blur occlusion and combine with color + shader2 = LazyShader( + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/SSAO_blur.frag") + ) + data2 = Dict{Symbol, Any}( + :normal_occlusion => framebuffer.buffers[:normal_occlusion], + :color_texture => framebuffer.buffers[:color], + :ids => framebuffer.buffers[:objectid], + :inv_texel_size => lift(rcpframe, framebuffer.resolution), + :blur_range => Node(Int32(2)) + ) + pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) + pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) + + + + full_render = screen -> begin + fb = screen.framebuffer + w, h = size(fb) + + # Setup rendering + # SSAO - calculate occlusion + glDrawBuffer(GL_COLOR_ATTACHMENT3) # occlusion buffer + glViewport(0, 0, w, h) + # glClearColor(1, 1, 1, 1) # 1 means no darkening + # glClear(GL_COLOR_BUFFER_BIT) + + for (screenid, scene) in screen.screens + # update uniforms + SSAO = scene.SSAO + data1[:projection][] = scene.camera.projection[] + data1[:bias][] = Float32(to_value(get(SSAO, :bias, 0.025))) + data1[:radius][] = Float32(to_value(get(SSAO, :radius, 0.5))) + # use stencil to select one scene + glStencilFunc(GL_EQUAL, screenid, 0xff) + GLAbstraction.render(pass1) + end + + # SSAO - blur occlusion and apply to color + glDrawBuffer(GL_COLOR_ATTACHMENT0) # color buffer + for (screenid, scene) in screen.screens + # update uniforms + SSAO = scene.attributes.SSAO + data2[:blur_range][] = Int32(to_value(get(SSAO, :blur, 2))) + # use stencil to select one scene + glStencilFunc(GL_EQUAL, screenid, 0xff) + GLAbstraction.render(pass2) + end + glDisable(GL_STENCIL_TEST) + end + + PostProcessor([pass1, pass2], full_render) +end + + + +""" + fxaa_postprocessor(framebuffer) + +Returns a PostProcessor that handles fxaa. +""" +function fxaa_postprocessor(framebuffer) + # Add missing buffers + if !haskey(framebuffer.buffers, :color_luma) + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[2]) + color_luma_buffer = Texture( + RGBA{N0f8}, size(framebuffer), minfilter=:linear, x_repeat=:clamp_to_edge + ) + attach_framebuffer(color_luma_buffer, GL_COLOR_ATTACHMENT0) + push!(framebuffer.buffers, :color_luma => color_luma_buffer) + end + + + + # calculate luma for FXAA + shader1 = LazyShader( + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/postprocess.frag") + ) + data1 = Dict{Symbol, Any}( + :color_texture => framebuffer.buffers[:color] + ) + pass1 = RenderObject(data1, shader1, PostprocessPrerender(), nothing) + pass1.postrenderfunction = () -> draw_fullscreen(pass1.vertexarray.id) + + # perform FXAA + shader2 = LazyShader( + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/fxaa.frag") + ) + data2 = Dict{Symbol, Any}( + :color_texture => framebuffer.buffers[:color_luma], + :RCPFrame => lift(rcpframe, framebuffer.resolution), + ) + pass2 = RenderObject(data2, shader2, PostprocessPrerender(), nothing) + pass2.postrenderfunction = () -> draw_fullscreen(pass2.vertexarray.id) + + + + full_render = screen -> begin + fb = screen.framebuffer + w, h = size(fb) + + # FXAA - calculate LUMA + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[2]) + glDrawBuffer(GL_COLOR_ATTACHMENT0) # color_luma buffer + 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 + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.id[1]) + glDrawBuffer(GL_COLOR_ATTACHMENT0) # color buffer + # glViewport(0, 0, w, h) # not necessary + GLAbstraction.render(pass2) + end + + PostProcessor([pass1, pass2], full_render) +end + + +""" + to_screen_postprocessor(framebuffer) + +Sets up a Postprocessor which copies the color buffer to the screen. Used as a +final step for displaying the screen. +""" +function to_screen_postprocessor(framebuffer) + # draw color buffer + shader = LazyShader( + loadshader("postprocessing/fullscreen.vert"), + loadshader("postprocessing/copy.frag") + ) + data = Dict{Symbol, Any}( + :color_texture => framebuffer.buffers[:color] + ) + pass = RenderObject(data, shader, PostprocessPrerender(), nothing) + pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id) + + full_render = screen -> begin + fb = screen.framebuffer + w, h = size(fb) + + # transfer everything to the screen + glBindFramebuffer(GL_FRAMEBUFFER, 0) + glViewport(0, 0, w, h) + glClear(GL_COLOR_BUFFER_BIT) + GLAbstraction.render(pass) # copy postprocess + end + + PostProcessor([pass], full_render) +end diff --git a/src/rendering.jl b/src/rendering.jl index 6e573da..cc6b9c1 100644 --- a/src/rendering.jl +++ b/src/rendering.jl @@ -131,17 +131,16 @@ function render_frame(screen::Screen; resize_buffers=true) # prepare stencil (for sub-scenes) glEnable(GL_STENCIL_TEST) glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) # color framebuffer - glDrawBuffers(4, [ - GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 - ]) + glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) glEnable(GL_STENCIL_TEST) glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE) glStencilMask(0xff) glClearStencil(0) glClearColor(0, 0, 0, 0) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) + glDrawBuffer(fb.render_buffer_ids[1]) setup!(screen) + glDrawBuffers(length(fb.render_buffer_ids), fb.render_buffer_ids) # render with FXAA & SSAO glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) @@ -150,42 +149,8 @@ function render_frame(screen::Screen; resize_buffers=true) GLAbstraction.render(screen, true, true) - # SSAO - calculate occlusion - # glDrawBuffer(GL_COLOR_ATTACHMENT4) # occlusion buffer - glDrawBuffer(GL_COLOR_ATTACHMENT3) # occlusion buffer - glViewport(0, 0, w, h) - # glClearColor(1, 1, 1, 1) # 1 means no darkening - # glClear(GL_COLOR_BUFFER_BIT) - - for (screenid, scene) in screen.screens - # update uniforms - SSAO = scene.SSAO - # if SSAO.enable[] - uniforms = fb.postprocess[1].uniforms - uniforms[:projection][] = scene.camera.projection[] - uniforms[:bias][] = Float32(to_value(get(SSAO, :bias, 0.025))) - uniforms[:radius][] = Float32(to_value(get(SSAO, :radius, 0.5))) - # use stencil to select one scene - glStencilFunc(GL_EQUAL, screenid, 0xff) - GLAbstraction.render(fb.postprocess[1]) - # end - end - - # SSAO - blur occlusion and apply to color - glDrawBuffer(GL_COLOR_ATTACHMENT0) # color buffer - for (screenid, scene) in screen.screens - # update uniforms - SSAO = scene.attributes.SSAO - # if SSAO.enable[] - uniforms = fb.postprocess[2].uniforms - uniforms[:blur_range][] = Int32(to_value(get(SSAO, :blur, 2))) - - # use stencil to select one scene - glStencilFunc(GL_EQUAL, screenid, 0xff) - GLAbstraction.render(fb.postprocess[2]) - # end - end - glDisable(GL_STENCIL_TEST) + # SSAO + screen.postprocessors[1].render(screen) # render with FXAA but no SSAO glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) @@ -195,20 +160,9 @@ function render_frame(screen::Screen; resize_buffers=true) GLAbstraction.render(screen, true, false) glDisable(GL_STENCIL_TEST) - # FXAA - calculate LUMA - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[2]) - glDrawBuffer(GL_COLOR_ATTACHMENT0) # color_luma buffer - glViewport(0, 0, w, h) - # necessary with negative SSAO bias... - glClearColor(1, 1, 1, 1) - glClear(GL_COLOR_BUFFER_BIT) - GLAbstraction.render(fb.postprocess[3]) + # FXAA + screen.postprocessors[2].render(screen) - # FXAA - perform anti-aliasing - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) - glDrawBuffer(GL_COLOR_ATTACHMENT0) # color buffer - # glViewport(0, 0, w, h) # not necessary - GLAbstraction.render(fb.postprocess[4]) # no FXAA primary render glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) @@ -219,10 +173,8 @@ function render_frame(screen::Screen; resize_buffers=true) glDisable(GL_STENCIL_TEST) # transfer everything to the screen - glBindFramebuffer(GL_FRAMEBUFFER, 0) - glViewport(0, 0, w, h) - glClear(GL_COLOR_BUFFER_BIT) - GLAbstraction.render(fb.postprocess[5]) # copy postprocess + screen.postprocessors[3].render(screen) + return end diff --git a/src/screen.jl b/src/screen.jl index 56e6d31..260b3e2 100644 --- a/src/screen.jl +++ b/src/screen.jl @@ -12,10 +12,11 @@ mutable struct Screen <: GLScreen screen2scene::Dict{WeakRef, ScreenID} screens::Vector{ScreenArea} renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}} + postprocessors::Vector{PostProcessor} cache::Dict{UInt64, RenderObject} cache2plot::Dict{UInt16, AbstractPlot} framecache::Matrix{RGB{N0f8}} - render_tick::Node{Nothing} + render_tick::Observable{Nothing} window_open::Observable{Bool} function Screen( glscreen::GLFW.Window, @@ -24,20 +25,21 @@ mutable struct Screen <: GLScreen screen2scene::Dict{WeakRef, ScreenID}, screens::Vector{ScreenArea}, renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}}, + postprocessors::Vector{PostProcessor}, cache::Dict{UInt64, RenderObject}, cache2plot::Dict{UInt16, AbstractPlot}, ) s = size(framebuffer) obj = new( glscreen, framebuffer, rendertask, screen2scene, - screens, renderlist, cache, cache2plot, + screens, renderlist, postprocessors, cache, cache2plot, Matrix{RGB{N0f8}}(undef, s), Observable(nothing), Observable(true) ) end end -GeometryBasics.widths(x::Screen) = size(x.framebuffer.color) +GeometryBasics.widths(x::Screen) = size(x.framebuffer) Base.wait(x::Screen) = isassigned(x.rendertask) && wait(x.rendertask[]) Base.wait(scene::Scene) = wait(AbstractPlotting.getscreen(scene)) @@ -146,7 +148,7 @@ heatmap(depth_color, colormap=:grays, show_axis=false) function depthbuffer(screen::Screen) render_frame(screen, resize_buffers=false) # let it render glFinish() # block until opengl is done rendering - source = screen.framebuffer.depth + source = screen.framebuffer.buffers[:depth] depth = Matrix{Float32}(undef, size(source)) GLAbstraction.bind(source) GLAbstraction.glGetTexImage(source.texturetype, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depth) @@ -158,7 +160,7 @@ function AbstractPlotting.colorbuffer(screen::Screen, format::AbstractPlotting.I if !isopen(screen) error("Screen not open!") end - ctex = screen.framebuffer.color + ctex = screen.framebuffer.buffers[:color] # polling may change window size, when its bigger than monitor! # we still need to poll though, to get all the newest events! # GLFW.PollEvents() @@ -259,18 +261,20 @@ end function display_loading_image(screen::Screen) fb = screen.framebuffer - fbsize = size(fb.color) + fbsize = size(fb) image = get_loading_image(fbsize) if size(image) == fbsize nw = to_native(screen) - fb.color[1:size(image, 1), 1:size(image, 2)] = image # transfer loading image to gpu framebuffer + # transfer loading image to gpu framebuffer + fb.buffers[:color][1:size(image, 1), 1:size(image, 2)] = image ShaderAbstractions.is_context_active(nw) || return w, h = fbsize glBindFramebuffer(GL_FRAMEBUFFER, 0) # transfer back to window glViewport(0, 0, w, h) glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) - GLAbstraction.render(fb.postprocess[3]) # copy postprocess + # GLAbstraction.render(fb.postprocess[end]) # copy postprocess + GLAbstraction.render(screen.postprocessors[end].robjs[1]) GLFW.SwapBuffers(nw) else error("loading_image needs to be Matrix{RGBA{N0f8}} with size(loading_image) == resolution") @@ -326,12 +330,19 @@ function Screen(; resize_native!(window, resolution...; wait_for_resize=false) fb = GLFramebuffer(resolution) + postprocessors = [ + enable_SSAO[] ? ssao_postprocessor(fb) : empty_postprocessor(), + enable_FXAA[] ? fxaa_postprocessor(fb) : empty_postprocessor(), + to_screen_postprocessor(fb) + ] + screen = Screen( window, fb, RefValue{Task}(), Dict{WeakRef, ScreenID}(), ScreenArea[], Tuple{ZIndex, ScreenID, RenderObject}[], + postprocessors, Dict{UInt64, RenderObject}(), Dict{UInt16, AbstractPlot}(), ) @@ -379,7 +390,7 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}) sid = Base.RefValue{SelectionID{UInt32}}() window_size = widths(screen) fb = screen.framebuffer - buff = fb.objectid + buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) x, y = floor.(Int, xy) @@ -395,7 +406,7 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}, range::Float64) isopen(screen) || return SelectionID{Int}(0, 0) window_size = widths(screen) fb = screen.framebuffer - buff = fb.objectid + buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) @@ -442,7 +453,7 @@ end function AbstractPlotting.pick(screen::Screen, rect::IRect2D) window_size = widths(screen) fb = screen.framebuffer - buff = fb.objectid + buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) x, y = minimum(rect)