diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index 06a5452f3b2..1c0f10aed93 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -121,22 +121,52 @@ void gr_opengl_flip() if (Cmdline_window_res) { GL_state.PopFramebufferState(); - glViewport(0, 0, Cmdline_window_res->first, Cmdline_window_res->second); - - opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_GAMMA_BLIT, 0)); - - GL_state.Texture.Enable(0, GL_TEXTURE_2D, Back_texture); - Current_shader->program->Uniforms.setTextureUniform("tex", 0); + const float gamma = Cmdline_no_set_gamma ? 1.0f : Gr_gamma; + GLuint present_source = Back_framebuffer; + + // At gamma 1.0 the correction shader is an identity transform, so skip the extra + // fullscreen pass and present Back_framebuffer directly. + if (gamma != 1.0f) { + // Gamma-correct Back_texture into an offscreen scratch texture. This is an FBO -> FBO + // draw, the same class of operation as every other post-process pass. Presenting straight + // into the window-system default framebuffer from a shader draw that samples a + // just-rendered texture caused flicker on some drivers, so the hand-off to the screen + // always goes through glBlitFramebuffer instead. + GL_state.BindFrameBuffer(GammaBlit_framebuffer); + glViewport(0, 0, gr_screen.max_w, gr_screen.max_h); + + opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_GAMMA_BLIT, 0)); + + GL_state.Texture.Enable(0, GL_TEXTURE_2D, Back_texture); + Current_shader->program->Uniforms.setTextureUniform("tex", 0); + + GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE); + GL_state.SetZbufferType(ZBUFFER_TYPE_NONE); + // The last material of the frame may have left color writes disabled (e.g. a masked + // model pass in the briefing map render). The old blit-only present ignored the color + // mask, but this is a regular draw and needs color writes and no stencil to take effect. + GL_state.ColorMask(true, true, true, true); + GL_state.StencilTest(GL_FALSE); + + opengl_set_generic_uniform_data( + [gamma](graphics::generic_data::gamma_blit_data* data) { + data->gamma = gamma; + }); + + opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f); + + present_source = GammaBlit_framebuffer; + } - GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE); - GL_state.SetZbufferType(ZBUFFER_TYPE_NONE); + GL_state.BindFrameBuffer(0, GL_DRAW_FRAMEBUFFER); + GL_state.BindFrameBuffer(present_source, GL_READ_FRAMEBUFFER); - opengl_set_generic_uniform_data( - [](graphics::generic_data::gamma_blit_data* data) { - data->gamma = Cmdline_no_set_gamma ? 1.f : Gr_gamma; - }); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glDrawBuffer(GL_BACK); + glBlitFramebuffer(0, 0, gr_screen.max_w, gr_screen.max_h, 0, 0, Cmdline_window_res->first, Cmdline_window_res->second, GL_COLOR_BUFFER_BIT, GL_LINEAR); + glDrawBuffer(GL_NONE); - opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f); + GL_state.BindFrameBuffer(0, GL_READ_FRAMEBUFFER); } if (Cmdline_gl_finish) diff --git a/code/graphics/opengl/gropengldraw.cpp b/code/graphics/opengl/gropengldraw.cpp index 4e5a039e979..63ac62ffe6b 100644 --- a/code/graphics/opengl/gropengldraw.cpp +++ b/code/graphics/opengl/gropengldraw.cpp @@ -55,6 +55,9 @@ GLuint Back_framebuffer; GLuint Back_texture; GLuint Back_depth_texture; +GLuint GammaBlit_framebuffer; +GLuint GammaBlit_texture; + GLuint Distortion_framebuffer = 0; GLuint Distortion_texture[2]; int Distortion_switch = 0; @@ -578,6 +581,44 @@ void opengl_setup_scene_textures() glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Back_depth_texture, 0); gr_zbuffer_set(GR_ZBUFF_FULL); glClear(GL_DEPTH_BUFFER_BIT); + + // Intermediate target for the gamma-correction pass. The gamma pass samples Back_texture and writes + // here (an offscreen FBO->FBO draw, same as every other post-process pass); the result is then + // presented to the window via glBlitFramebuffer. Some drivers show flicker when a shader draw that + // samples a just-rendered texture targets the window-system default framebuffer directly, so the + // final presentation step always goes through a blit instead. + glGenFramebuffers(1, &GammaBlit_framebuffer); + GL_state.BindFrameBuffer(GammaBlit_framebuffer); + opengl_set_object_label(GL_FRAMEBUFFER, GammaBlit_framebuffer, "Gamma blit framebuffer"); + + if (Scene_ldr_texture != 0 && Scene_texture_width == gr_screen.max_w && Scene_texture_height == gr_screen.max_h) { + // Scene_ldr_texture is dead scratch by flip time: its consumers (tonemap, AA, the final + // post pass) all write before reading within gr_opengl_post_process_end(), which completes + // before the flip. Reuse it as the gamma pass target instead of allocating another + // screen-sized texture. GammaBlit_texture stays 0 so shutdown won't delete it. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_ldr_texture, 0); + } else { + // Scene textures are clamped to GL_max_renderbuffer_size and may not cover the screen; + // fall back to a dedicated texture in that case. + glGenTextures(1, &GammaBlit_texture); + + GL_state.Texture.SetActiveUnit(0); + GL_state.Texture.SetTarget(GL_TEXTURE_2D); + GL_state.Texture.Enable(GammaBlit_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, gr_screen.max_w, gr_screen.max_h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, GammaBlit_texture, 0); + opengl_set_object_label(GL_TEXTURE, GammaBlit_texture, "Gamma blit texture"); + } + + GL_state.BindFrameBuffer(Back_framebuffer); } //Setup thruster distortion framebuffer @@ -695,7 +736,17 @@ void opengl_scene_texture_shutdown() glDeleteFramebuffers(1, &Back_framebuffer); Back_framebuffer = 0; } - + + if (GammaBlit_texture) { + glDeleteTextures(1, &GammaBlit_texture); + GammaBlit_texture = 0; + } + + if (GammaBlit_framebuffer) { + glDeleteFramebuffers(1, &GammaBlit_framebuffer); + GammaBlit_framebuffer = 0; + } + glDeleteTextures(2, Distortion_texture); Distortion_texture[0] = 0; Distortion_texture[1] = 0; diff --git a/code/graphics/opengl/gropengldraw.h b/code/graphics/opengl/gropengldraw.h index f85da1b1ca9..3cfd3183c80 100644 --- a/code/graphics/opengl/gropengldraw.h +++ b/code/graphics/opengl/gropengldraw.h @@ -39,6 +39,9 @@ extern GLuint Cockpit_depth_texture; extern GLuint Back_framebuffer; extern GLuint Back_texture; +extern GLuint GammaBlit_framebuffer; +extern GLuint GammaBlit_texture; + void gr_opengl_update_distortion(); void gr_opengl_sphere(material *material_def, float rad);