From eddd6fb019b42ade7f85e2e177215c5184bf9d1d Mon Sep 17 00:00:00 2001 From: "Alexander J. Semenuk" Date: Sun, 19 Jul 2026 14:59:17 -0400 Subject: [PATCH] fix(gfx): generate cloud mipmaps after unbinding the render FBO (#4344) Found while investigating the jak2/jak3 one-frame sky flicker (see the fog CLUT inline payload PR: `#4343`). ## Problem `TextureAnimator::run_clouds` calls `glGenerateMipmap` on the final cloud texture while that texture is still attached to the currently bound draw framebuffer (the `FramebufferTexturePairContext` is still in scope). Reading a texture that is attached to the bound framebuffer is a driver hazard even outside a draw call. Every other site in this file already avoids this: `run_slime` and `opengl_upload_resize_texture` both close the FBO context scope before generating mipmaps. `run_clouds` is the one outlier, presumably an oversight. ## Fix Scope the `FramebufferTexturePairContext` in braces and generate the mipmaps after it restores the previous framebuffer binding, matching the established pattern in the rest of the file. ## Test plan - [ ] jak2/jak3 clouds render identically (hires and normal cloud modes) (AI-assisted) --- .../opengl_renderer/TextureAnimator.cpp | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/game/graphics/opengl_renderer/TextureAnimator.cpp b/game/graphics/opengl_renderer/TextureAnimator.cpp index 523608c732..8e27a976f1 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.cpp +++ b/game/graphics/opengl_renderer/TextureAnimator.cpp @@ -2910,17 +2910,21 @@ GLint TextureAnimator::run_clouds(const SkyInput& input, bool hires) { } } - FramebufferTexturePairContext ctxt(final_tex); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - glUniform1i(m_uniforms.enable_tex, 2); - glBindTexture(GL_TEXTURE_2D, blend_tex.texture()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glUniform1f(m_uniforms.minimum, input.cloud_min); - glUniform1f(m_uniforms.maximum, input.cloud_max); - glDisable(GL_BLEND); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + { + FramebufferTexturePairContext ctxt(final_tex); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glUniform1i(m_uniforms.enable_tex, 2); + glBindTexture(GL_TEXTURE_2D, blend_tex.texture()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glUniform1f(m_uniforms.minimum, input.cloud_min); + glUniform1f(m_uniforms.maximum, input.cloud_max); + glDisable(GL_BLEND); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + // generate mipmaps only after final_tex is no longer attached to the bound + // framebuffer, matching run_slime and opengl_upload_resize_texture. glBindTexture(GL_TEXTURE_2D, final_tex.texture()); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0);