From af138dae3684aa725274789244213cb5760daa67 Mon Sep 17 00:00:00 2001 From: wily-coyote <24258996+wily-coyote@users.noreply.github.com> Date: Tue, 17 Dec 2024 00:07:30 -0500 Subject: [PATCH 1/5] feat(gl): shader stacking This commit adds proper shader stacking behavior to the OpenGL renderer by rendering each shader one by one. Previously, selecting multiple shaders through the menu or by editing config.json would only render the last post-processing shader in the stack. --- src/render_gl.go | 125 +++++++++++++++++++++++++++++++++--------- src/render_gl_gl32.go | 125 +++++++++++++++++++++++++++++++++--------- 2 files changed, 200 insertions(+), 50 deletions(-) diff --git a/src/render_gl.go b/src/render_gl.go index 1415305c..b135009f 100644 --- a/src/render_gl.go +++ b/src/render_gl.go @@ -332,6 +332,9 @@ type Renderer_GL21 struct { cubemapFilteringShader *ShaderProgram_GL21 stageVertexBuffer uint32 stageIndexBuffer uint32 + // Postprocessing + fbo_pp uint32 + fbo_pp_texture uint32 enableModel bool enableShadow bool @@ -457,6 +460,8 @@ func (r *Renderer_GL21) Init() { } gl.ActiveTexture(gl.TEXTURE0) + + // create a texture for r.fbo gl.GenTextures(1, &r.fbo_texture) if sys.multisampleAntialiasing > 0 { @@ -471,11 +476,48 @@ func (r *Renderer_GL21) Init() { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) if sys.multisampleAntialiasing > 0 { - gl.TexImage2DMultisample(gl.TEXTURE_2D_MULTISAMPLE, sys.multisampleAntialiasing, gl.RGBA, sys.scrrect[2], sys.scrrect[3], true) + gl.TexImage2DMultisample( + gl.TEXTURE_2D_MULTISAMPLE, + sys.multisampleAntialiasing, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + true, + ) } else { - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, sys.scrrect[2], sys.scrrect[3], 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + nil, + ) } + // r.fbo_pp_texture + gl.GenTextures(1, &r.fbo_pp_texture) + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + nil, + ) + + // done with r.fbo_texture, unbind it gl.BindTexture(gl.TEXTURE_2D, 0) //r.rbo_depth = gl.CreateRenderbuffer() @@ -499,6 +541,7 @@ func (r *Renderer_GL21) Init() { //gl.BindRenderbuffer(gl.RENDERBUFFER, gl.NoRenderbuffer) } + // create an FBO for our r.fbo, which is then for r.fbo_texture gl.GenFramebuffers(1, &r.fbo) gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) @@ -516,6 +559,13 @@ func (r *Renderer_GL21) Init() { gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, r.fbo_texture, 0) gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, r.rbo_depth) } + + // create an FBO for our postprocessing needs + gl.GenFramebuffers(1, &r.fbo_pp) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, r.fbo_pp_texture, 0) + + // create an FBO for our model stuff if r.enableModel { if r.enableShadow { gl.GenFramebuffersEXT(1, &r.fbo_shadow) @@ -572,29 +622,28 @@ func (r *Renderer_GL21) BlendReset() { gl.BlendFunc(r.MapBlendFunction(BlendSrcAlpha), r.MapBlendFunction(BlendOneMinusSrcAlpha)) } func (r *Renderer_GL21) EndFrame() { + x, y, width, height := int32(0), int32(0), int32(sys.scrrect[2]), int32(sys.scrrect[3]) + if sys.multisampleAntialiasing > 0 { gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, r.fbo_f) gl.BindFramebuffer(gl.READ_FRAMEBUFFER, r.fbo) - gl.BlitFramebuffer(0, 0, sys.scrrect[2], sys.scrrect[3], 0, 0, sys.scrrect[2], sys.scrrect[3], gl.COLOR_BUFFER_BIT, gl.LINEAR) + gl.BlitFramebuffer(x, y, width, height, x, y, width, height, gl.COLOR_BUFFER_BIT, gl.LINEAR) } - x, y, resizedWidth, resizedHeight := sys.window.GetScaledViewportSize() - postShader := r.postShaderSelect[len(sys.externalShaderList)] - var scaleMode int32 // GL enum - if sys.windowScaleMode == true { + if sys.windowScaleMode { scaleMode = gl.LINEAR } else { scaleMode = gl.NEAREST } - gl.Viewport(x, y, int32(resizedWidth), int32(resizedHeight)) - gl.BindFramebuffer(gl.FRAMEBUFFER, 0) - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - - gl.UseProgram(postShader.program) - gl.Disable(gl.BLEND) + // set the viewport to the unscaled bounds for post-processing + gl.Viewport(x, y, width, height) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our postprocessing FBO is the output + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // clear that FBO + // tell GL to use the output before it (r.fbo/r.fbo_texture) + // as the texture for the quad we're about to render gl.ActiveTexture(gl.TEXTURE0) if sys.multisampleAntialiasing > 0 { gl.BindTexture(gl.TEXTURE_2D, r.fbo_f_texture.handle) @@ -602,21 +651,47 @@ func (r *Renderer_GL21) EndFrame() { gl.BindTexture(gl.TEXTURE_2D, r.fbo_texture) } - // set post-processing parameters - gl.Uniform1i(postShader.u["Texture_GL21"], 0) - gl.Uniform2f(postShader.u["TextureSize"], float32(resizedWidth), float32(resizedHeight)) - gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) + for idx, postShader := range r.postShaderSelect { + if(idx >= len(r.postShaderSelect)-1){ + // this is the last shader, + // so we ask GL to scale it and output it + // to FB0, the default frame buffer that the user sees + x, y, width, height := sys.window.GetScaledViewportSize() + gl.Viewport(x, y, width, height) + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + // clear FB0 just to make sure + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + } - gl.BindBuffer(gl.ARRAY_BUFFER, r.postVertBuffer) + // tell GL we want to use our shader program + gl.UseProgram(postShader.program) + gl.Disable(gl.BLEND) - loc := postShader.a["VertCoord"] - gl.EnableVertexAttribArray(uint32(loc)) - gl.VertexAttribPointerWithOffset(uint32(loc), 2, gl.FLOAT, false, 0, 0) + // set post-processing parameters + gl.Uniform1i(postShader.u["Texture_GL21"], 0) + gl.Uniform2f(postShader.u["TextureSize"], float32(width), float32(height)) + gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) - gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) - gl.DisableVertexAttribArray(uint32(loc)) + // this actually draws the image to the FBO + // by constructing a quad (2 tris) + gl.BindBuffer(gl.ARRAY_BUFFER, r.postVertBuffer) + + // construct the UVs of the quad + loc := postShader.a["VertCoord"] + gl.EnableVertexAttribArray(uint32(loc)) + gl.VertexAttribPointer(uint32(loc), 2, gl.FLOAT, false, 0, nil) + + // construct the quad and draw it + gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + gl.DisableVertexAttribArray(uint32(loc)) + + // we've now output to r.fbo_pp/r.fbo_pp_texture. + // that becomes the input for our next shader or whatever after + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + } } func (r *Renderer_GL21) Await() { diff --git a/src/render_gl_gl32.go b/src/render_gl_gl32.go index 3858d723..f1ca2e09 100644 --- a/src/render_gl_gl32.go +++ b/src/render_gl_gl32.go @@ -332,6 +332,9 @@ type Renderer_GL32 struct { stageVertexBuffer uint32 stageIndexBuffer uint32 vao uint32 + // Postprocessing + fbo_pp uint32 + fbo_pp_texture uint32 enableModel bool enableShadow bool @@ -466,6 +469,8 @@ func (r *Renderer_GL32) Init() { } gl.ActiveTexture(gl.TEXTURE0) + + // create a texture for r.fbo gl.GenTextures(1, &r.fbo_texture) if sys.multisampleAntialiasing > 0 { @@ -480,12 +485,48 @@ func (r *Renderer_GL32) Init() { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) if sys.multisampleAntialiasing > 0 { - gl.TexImage2DMultisample(gl.TEXTURE_2D_MULTISAMPLE, sys.multisampleAntialiasing, gl.RGBA, sys.scrrect[2], sys.scrrect[3], true) - + gl.TexImage2DMultisample( + gl.TEXTURE_2D_MULTISAMPLE, + sys.multisampleAntialiasing, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + true, + ) } else { - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, sys.scrrect[2], sys.scrrect[3], 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + nil, + ) } + // r.fbo_pp_texture + gl.GenTextures(1, &r.fbo_pp_texture) + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + sys.scrrect[2], + sys.scrrect[3], + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + nil, + ) + + // done with r.fbo_texture, unbind it gl.BindTexture(gl.TEXTURE_2D, 0) //r.rbo_depth = gl.CreateRenderbuffer() @@ -509,6 +550,7 @@ func (r *Renderer_GL32) Init() { //gl.BindRenderbuffer(gl.RENDERBUFFER, gl.NoRenderbuffer) } + // create an FBO for our r.fbo, which is then for r.fbo_texture gl.GenFramebuffers(1, &r.fbo) gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) @@ -527,6 +569,12 @@ func (r *Renderer_GL32) Init() { gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, r.rbo_depth) } + // create an FBO for our postprocessing needs + gl.GenFramebuffers(1, &r.fbo_pp) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, r.fbo_pp_texture, 0) + + // create an FBO for our model stuff if r.enableModel { if r.enableShadow { gl.GenFramebuffers(1, &r.fbo_shadow) @@ -580,17 +628,18 @@ func (r *Renderer_GL32) BlendReset() { gl.BlendFunc(r.MapBlendFunction(BlendSrcAlpha), r.MapBlendFunction(BlendOneMinusSrcAlpha)) } func (r *Renderer_GL32) EndFrame() { + // tell GL to use our vertex array object + // this'll be where our quad is stored gl.BindVertexArray(r.vao) + x, y, width, height := int32(0), int32(0), int32(sys.scrrect[2]), int32(sys.scrrect[3]) + if sys.multisampleAntialiasing > 0 { gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, r.fbo_f) gl.BindFramebuffer(gl.READ_FRAMEBUFFER, r.fbo) - gl.BlitFramebuffer(0, 0, sys.scrrect[2], sys.scrrect[3], 0, 0, sys.scrrect[2], sys.scrrect[3], gl.COLOR_BUFFER_BIT, gl.LINEAR) + gl.BlitFramebuffer(x, y, width, height, x, y, width, height, gl.COLOR_BUFFER_BIT, gl.LINEAR) } - x, y, resizedWidth, resizedHeight := sys.window.GetScaledViewportSize() - postShader := r.postShaderSelect[len(sys.externalShaderList)] - var scaleMode int32 // GL enum if sys.windowScaleMode { scaleMode = gl.LINEAR @@ -598,13 +647,13 @@ func (r *Renderer_GL32) EndFrame() { scaleMode = gl.NEAREST } - gl.Viewport(x, y, int32(resizedWidth), int32(resizedHeight)) - gl.BindFramebuffer(gl.FRAMEBUFFER, 0) - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - - gl.UseProgram(postShader.program) - gl.Disable(gl.BLEND) + // set the viewport to the unscaled bounds for post-processing + gl.Viewport(x, y, width, height) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our postprocessing FBO is the output + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // clear that FBO + // tell GL to use the output before it (r.fbo/r.fbo_texture) + // as the texture for the quad we're about to render gl.ActiveTexture(gl.TEXTURE0) if sys.multisampleAntialiasing > 0 { gl.BindTexture(gl.TEXTURE_2D, r.fbo_f_texture.handle) @@ -612,21 +661,47 @@ func (r *Renderer_GL32) EndFrame() { gl.BindTexture(gl.TEXTURE_2D, r.fbo_texture) } - // set post-processing parameters - gl.Uniform1i(postShader.u["Texture_GL32"], 0) - gl.Uniform2f(postShader.u["TextureSize"], float32(resizedWidth), float32(resizedHeight)) - gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) + for idx, postShader := range r.postShaderSelect { + if(idx >= len(r.postShaderSelect)-1){ + // this is the last shader, + // so we ask GL to scale it and output it + // to FB0, the default frame buffer that the user sees + x, y, width, height := sys.window.GetScaledViewportSize() + gl.Viewport(x, y, width, height) + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + // clear FB0 just to make sure + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + } - gl.BindBuffer(gl.ARRAY_BUFFER, r.postVertBuffer) + // tell GL we want to use our shader program + gl.UseProgram(postShader.program) + gl.Disable(gl.BLEND) - loc := postShader.a["VertCoord"] - gl.EnableVertexAttribArray(uint32(loc)) - gl.VertexAttribPointer(uint32(loc), 2, gl.FLOAT, false, 0, nil) + // set post-processing parameters + gl.Uniform1i(postShader.u["Texture_GL32"], 0) + gl.Uniform2f(postShader.u["TextureSize"], float32(width), float32(height)) + gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) - gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) - gl.DisableVertexAttribArray(uint32(loc)) + // this actually draws the image to the FBO + // by constructing a quad (2 tris) + gl.BindBuffer(gl.ARRAY_BUFFER, r.postVertBuffer) + + // construct the UVs of the quad + loc := postShader.a["VertCoord"] + gl.EnableVertexAttribArray(uint32(loc)) + gl.VertexAttribPointer(uint32(loc), 2, gl.FLOAT, false, 0, nil) + + // construct the quad and draw it + gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) + gl.DisableVertexAttribArray(uint32(loc)) + + // we've now output to r.fbo_pp/r.fbo_pp_texture. + // that becomes the input for our next shader or whatever after + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + } } func (r *Renderer_GL32) Await() { From 1d5486dbba012bb4900f48bf3b0ea45a0d4d5aea Mon Sep 17 00:00:00 2001 From: wily-coyote <24258996+wily-coyote@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:18:04 -0500 Subject: [PATCH 2/5] fix: undefined behavior for 3+ shaders This commit fixes graphical glitches when 3+ shaders are selected by not writing to the same FBO. Oops. --- src/render_gl.go | 36 ++++++++++++++++++--------- src/render_gl_gl32.go | 57 ++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/render_gl.go b/src/render_gl.go index b135009f..63e1c743 100644 --- a/src/render_gl.go +++ b/src/render_gl.go @@ -623,6 +623,7 @@ func (r *Renderer_GL21) BlendReset() { } func (r *Renderer_GL21) EndFrame() { x, y, width, height := int32(0), int32(0), int32(sys.scrrect[2]), int32(sys.scrrect[3]) + time := glfw.GetTime() // consistent time across all shaders if sys.multisampleAntialiasing > 0 { gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, r.fbo_f) @@ -640,19 +641,32 @@ func (r *Renderer_GL21) EndFrame() { // set the viewport to the unscaled bounds for post-processing gl.Viewport(x, y, width, height) gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our postprocessing FBO is the output - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // clear that FBO + gl.Clear(gl.COLOR_BUFFER_BIT) // clear that FBO + gl.ActiveTexture(gl.TEXTURE0) // later referred to by Texture_GL32 - // tell GL to use the output before it (r.fbo/r.fbo_texture) - // as the texture for the quad we're about to render - gl.ActiveTexture(gl.TEXTURE0) + fbo_texture := r.fbo_texture if sys.multisampleAntialiasing > 0 { - gl.BindTexture(gl.TEXTURE_2D, r.fbo_f_texture.handle) - } else { - gl.BindTexture(gl.TEXTURE_2D, r.fbo_texture) + fbo_texture = r.fbo_f_texture.handle } + // disable blending + gl.Disable(gl.BLEND) + + for i := 0; i < len(r.postShaderSelect); i++ { + postShader := r.postShaderSelect[i] + + // this is here because it is undefined + // behavior to write to the same FBO + if(i%2==0){ + // ping! + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our post-processing FBO is the output + gl.BindTexture(gl.TEXTURE_2D, fbo_texture) // the previous texture is our input + } else { + // pong! + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) // the reverse + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + } - for idx, postShader := range r.postShaderSelect { - if(idx >= len(r.postShaderSelect)-1){ + if(i >= len(r.postShaderSelect)-1){ // this is the last shader, // so we ask GL to scale it and output it // to FB0, the default frame buffer that the user sees @@ -665,12 +679,11 @@ func (r *Renderer_GL21) EndFrame() { // tell GL we want to use our shader program gl.UseProgram(postShader.program) - gl.Disable(gl.BLEND) // set post-processing parameters gl.Uniform1i(postShader.u["Texture_GL21"], 0) gl.Uniform2f(postShader.u["TextureSize"], float32(width), float32(height)) - gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) + gl.Uniform1f(postShader.u["CurrentTime"], float32(time)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) @@ -689,7 +702,6 @@ func (r *Renderer_GL21) EndFrame() { // we've now output to r.fbo_pp/r.fbo_pp_texture. // that becomes the input for our next shader or whatever after - gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } } diff --git a/src/render_gl_gl32.go b/src/render_gl_gl32.go index f1ca2e09..66887d7b 100644 --- a/src/render_gl_gl32.go +++ b/src/render_gl_gl32.go @@ -448,22 +448,23 @@ func (r *Renderer_GL32) Init() { // Calculate total amount of shaders loaded. r.postShaderSelect = make([]*ShaderProgram_GL32, 1+len(sys.externalShaderList)) - // Ident shader (no postprocessing) - r.postShaderSelect[0], _ = r.newShaderProgram(identVertShader, identFragShader, "", "Identity Postprocess", true) - r.postShaderSelect[0].RegisterAttributes("VertCoord", "TexCoord") - r.postShaderSelect[0].RegisterUniforms("Texture_GL32", "TextureSize", "CurrentTime") - // External Shaders for i := 0; i < len(sys.externalShaderList); i++ { - r.postShaderSelect[1+i], _ = r.newShaderProgram(sys.externalShaders[0][i], - sys.externalShaders[1][i], "", fmt.Sprintf("Postprocess Shader #%v", i+1), true) - r.postShaderSelect[1+i].RegisterAttributes("VertCoord", "TexCoord") - loc := r.postShaderSelect[1+i].a["TexCoord"] + r.postShaderSelect[i], _ = r.newShaderProgram(sys.externalShaders[0][i], + sys.externalShaders[1][i], "", fmt.Sprintf("Postprocess Shader #%v", i), true) + r.postShaderSelect[i].RegisterAttributes("VertCoord", "TexCoord") + loc := r.postShaderSelect[i].a["TexCoord"] gl.VertexAttribPointer(uint32(loc), 3, gl.FLOAT, false, 5*4, gl.PtrOffset(2*4)) gl.EnableVertexAttribArray(uint32(loc)) - r.postShaderSelect[1+i].RegisterUniforms("Texture_GL32", "TextureSize", "CurrentTime") + r.postShaderSelect[i].RegisterUniforms("Texture_GL32", "TextureSize", "CurrentTime") } + // Ident shader (no postprocessing). This is the last one + identShader, _ := r.newShaderProgram(identVertShader, identFragShader, "", "Identity Postprocess", true) + identShader.RegisterAttributes("VertCoord", "TexCoord") + identShader.RegisterUniforms("Texture_GL32", "TextureSize", "CurrentTime") + r.postShaderSelect[len(r.postShaderSelect)-1] = identShader + if sys.multisampleAntialiasing > 0 { gl.Enable(gl.MULTISAMPLE) } @@ -633,6 +634,7 @@ func (r *Renderer_GL32) EndFrame() { gl.BindVertexArray(r.vao) x, y, width, height := int32(0), int32(0), int32(sys.scrrect[2]), int32(sys.scrrect[3]) + time := glfw.GetTime() // consistent time across all shaders if sys.multisampleAntialiasing > 0 { gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, r.fbo_f) @@ -650,19 +652,32 @@ func (r *Renderer_GL32) EndFrame() { // set the viewport to the unscaled bounds for post-processing gl.Viewport(x, y, width, height) gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our postprocessing FBO is the output - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // clear that FBO + gl.Clear(gl.COLOR_BUFFER_BIT) // clear that FBO + gl.ActiveTexture(gl.TEXTURE0) // later referred to by Texture_GL32 - // tell GL to use the output before it (r.fbo/r.fbo_texture) - // as the texture for the quad we're about to render - gl.ActiveTexture(gl.TEXTURE0) + fbo_texture := r.fbo_texture if sys.multisampleAntialiasing > 0 { - gl.BindTexture(gl.TEXTURE_2D, r.fbo_f_texture.handle) - } else { - gl.BindTexture(gl.TEXTURE_2D, r.fbo_texture) + fbo_texture = r.fbo_f_texture.handle } + // disable blending + gl.Disable(gl.BLEND) + + for i := 0; i < len(r.postShaderSelect); i++ { + postShader := r.postShaderSelect[i] + + // this is here because it is undefined + // behavior to write to the same FBO + if(i%2==0){ + // ping! + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo_pp) // our post-processing FBO is the output + gl.BindTexture(gl.TEXTURE_2D, fbo_texture) // the previous texture is our input + } else { + // pong! + gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) // the reverse + gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) + } - for idx, postShader := range r.postShaderSelect { - if(idx >= len(r.postShaderSelect)-1){ + if(i >= len(r.postShaderSelect)-1){ // this is the last shader, // so we ask GL to scale it and output it // to FB0, the default frame buffer that the user sees @@ -675,12 +690,11 @@ func (r *Renderer_GL32) EndFrame() { // tell GL we want to use our shader program gl.UseProgram(postShader.program) - gl.Disable(gl.BLEND) // set post-processing parameters gl.Uniform1i(postShader.u["Texture_GL32"], 0) gl.Uniform2f(postShader.u["TextureSize"], float32(width), float32(height)) - gl.Uniform1f(postShader.u["CurrentTime"], float32(glfw.GetTime())) + gl.Uniform1f(postShader.u["CurrentTime"], float32(time)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode) @@ -699,7 +713,6 @@ func (r *Renderer_GL32) EndFrame() { // we've now output to r.fbo_pp/r.fbo_pp_texture. // that becomes the input for our next shader or whatever after - gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } } From baef949c7c77bd92185c0dfb2aa9051f378917a8 Mon Sep 17 00:00:00 2001 From: wily-coyote <24258996+wily-coyote@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:23:55 -0500 Subject: [PATCH 3/5] fix: use MSAA FBO if needed --- src/render_gl.go | 5 ++++- src/render_gl_gl32.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/render_gl.go b/src/render_gl.go index 63e1c743..f30497f6 100644 --- a/src/render_gl.go +++ b/src/render_gl.go @@ -644,10 +644,13 @@ func (r *Renderer_GL21) EndFrame() { gl.Clear(gl.COLOR_BUFFER_BIT) // clear that FBO gl.ActiveTexture(gl.TEXTURE0) // later referred to by Texture_GL32 + fbo := r.fbo fbo_texture := r.fbo_texture if sys.multisampleAntialiasing > 0 { + fbo = r.fbo_f fbo_texture = r.fbo_f_texture.handle } + // disable blending gl.Disable(gl.BLEND) @@ -662,7 +665,7 @@ func (r *Renderer_GL21) EndFrame() { gl.BindTexture(gl.TEXTURE_2D, fbo_texture) // the previous texture is our input } else { // pong! - gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) // the reverse + gl.BindFramebuffer(gl.FRAMEBUFFER, fbo) // the reverse gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } diff --git a/src/render_gl_gl32.go b/src/render_gl_gl32.go index 66887d7b..ec78fbbc 100644 --- a/src/render_gl_gl32.go +++ b/src/render_gl_gl32.go @@ -655,10 +655,13 @@ func (r *Renderer_GL32) EndFrame() { gl.Clear(gl.COLOR_BUFFER_BIT) // clear that FBO gl.ActiveTexture(gl.TEXTURE0) // later referred to by Texture_GL32 + fbo := r.fbo fbo_texture := r.fbo_texture if sys.multisampleAntialiasing > 0 { + fbo = r.fbo_f fbo_texture = r.fbo_f_texture.handle } + // disable blending gl.Disable(gl.BLEND) @@ -673,7 +676,7 @@ func (r *Renderer_GL32) EndFrame() { gl.BindTexture(gl.TEXTURE_2D, fbo_texture) // the previous texture is our input } else { // pong! - gl.BindFramebuffer(gl.FRAMEBUFFER, r.fbo) // the reverse + gl.BindFramebuffer(gl.FRAMEBUFFER, fbo) // the reverse gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } From adb28ebf5a40c9ce1908e2493270c4098e7b2bb1 Mon Sep 17 00:00:00 2001 From: wily-coyote <24258996+wily-coyote@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:08:53 -0500 Subject: [PATCH 4/5] refactor: removed extraneous BindTexture call --- src/render_gl.go | 4 ---- src/render_gl_gl32.go | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/render_gl.go b/src/render_gl.go index f30497f6..3160a6c2 100644 --- a/src/render_gl.go +++ b/src/render_gl.go @@ -702,10 +702,6 @@ func (r *Renderer_GL21) EndFrame() { // construct the quad and draw it gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) gl.DisableVertexAttribArray(uint32(loc)) - - // we've now output to r.fbo_pp/r.fbo_pp_texture. - // that becomes the input for our next shader or whatever after - gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } } diff --git a/src/render_gl_gl32.go b/src/render_gl_gl32.go index ec78fbbc..e3f0d616 100644 --- a/src/render_gl_gl32.go +++ b/src/render_gl_gl32.go @@ -713,10 +713,6 @@ func (r *Renderer_GL32) EndFrame() { // construct the quad and draw it gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) gl.DisableVertexAttribArray(uint32(loc)) - - // we've now output to r.fbo_pp/r.fbo_pp_texture. - // that becomes the input for our next shader or whatever after - gl.BindTexture(gl.TEXTURE_2D, r.fbo_pp_texture) } } From 1ca2f81446cbe0296b60cd023be66baa11636cb1 Mon Sep 17 00:00:00 2001 From: wily-coyote <24258996+wily-coyote@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:01:57 -0500 Subject: [PATCH 5/5] refactor: change FBO formats to int8 signed for compatibility with some shaders --- src/render_gl.go | 10 ++++++---- src/render_gl_gl32.go | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/render_gl.go b/src/render_gl.go index 3160a6c2..323d2c46 100644 --- a/src/render_gl.go +++ b/src/render_gl.go @@ -474,12 +474,14 @@ func (r *Renderer_GL21) Init() { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - + + // Shaders might use negative values, so + // we specify that we want signed pixels if sys.multisampleAntialiasing > 0 { gl.TexImage2DMultisample( gl.TEXTURE_2D_MULTISAMPLE, sys.multisampleAntialiasing, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], true, @@ -488,7 +490,7 @@ func (r *Renderer_GL21) Init() { gl.TexImage2D( gl.TEXTURE_2D, 0, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], 0, @@ -508,7 +510,7 @@ func (r *Renderer_GL21) Init() { gl.TexImage2D( gl.TEXTURE_2D, 0, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], 0, diff --git a/src/render_gl_gl32.go b/src/render_gl_gl32.go index e3f0d616..0f396323 100644 --- a/src/render_gl_gl32.go +++ b/src/render_gl_gl32.go @@ -485,11 +485,13 @@ func (r *Renderer_GL32) Init() { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + // Shaders might use negative values, so + // we specify that we want signed pixels if sys.multisampleAntialiasing > 0 { gl.TexImage2DMultisample( gl.TEXTURE_2D_MULTISAMPLE, sys.multisampleAntialiasing, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], true, @@ -498,7 +500,7 @@ func (r *Renderer_GL32) Init() { gl.TexImage2D( gl.TEXTURE_2D, 0, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], 0, @@ -518,7 +520,7 @@ func (r *Renderer_GL32) Init() { gl.TexImage2D( gl.TEXTURE_2D, 0, - gl.RGBA, + gl.RGBA8_SNORM, sys.scrrect[2], sys.scrrect[3], 0,