From ff258e3d3c7d8b93db214b86a9df1af89c138c58 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Fri, 1 May 2026 17:20:43 +0200 Subject: [PATCH] Sanitize NaN/Inf in env cubemap and tonemap to stop one-frame black flashes --- gl/cubemap.cpp | 13 +++++++++---- shaders/envmapping.glsl | 17 +++++++++++++++-- shaders/tonemapping.glsl | 13 ++++++++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/gl/cubemap.cpp b/gl/cubemap.cpp index 1b8d71b9..ca0e6c08 100644 --- a/gl/cubemap.cpp +++ b/gl/cubemap.cpp @@ -17,11 +17,16 @@ gl::cubemap::~cubemap() void gl::cubemap::alloc(GLint format, int width, int height, GLenum components, GLenum type) { - glBindTexture(GL_TEXTURE_CUBE_MAP, *this); - for (GLuint tgt = GL_TEXTURE_CUBE_MAP_POSITIVE_X; tgt <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; tgt++) - glTexImage2D(tgt, 0, format, width, height, 0, components, type, nullptr); + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); + for (GLuint tgt = GL_TEXTURE_CUBE_MAP_POSITIVE_X; tgt <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; tgt++) + glTexImage2D(tgt, 0, format, width, height, 0, components, type, nullptr); - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + // make the cubemap sample-complete with no mipmaps required. + // generate_mipmaps() will later override MIN_FILTER once mips actually exist. + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } void gl::cubemap::bind(int unit) diff --git a/shaders/envmapping.glsl b/shaders/envmapping.glsl index 019d742b..028f2e70 100644 --- a/shaders/envmapping.glsl +++ b/shaders/envmapping.glsl @@ -8,6 +8,14 @@ vec3 envmap_color( vec3 normal ) vec3 refvec = reflect(f_pos.xyz, normal); // view space refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space vec3 envcolor = texture(envmap, refvec).rgb; + // Sanitize. The env cubemap can briefly contain NaN/Inf after a + // reflection-pass regeneration (m_empty_cubemap was bound during the + // face render and on NVIDIA can sample as undefined). Without this + // guard, a single bad texel propagates through env_reflection in + // apply_lights() and produces one-frame black flashes on glossy + // (esp. specgloss) surfaces. + if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); + envcolor = max(envcolor, vec3(0.0)); #else vec3 envcolor = vec3(0.5); #endif @@ -21,8 +29,13 @@ vec3 envmap_color_lod(vec3 fragnormal, float lod) #if ENVMAP_ENABLED vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing - return textureLod(envmap, refvec, lod).rgb; + vec3 envcolor = textureLod(envmap, refvec, lod).rgb; + // See envmap_color() above — same NaN/Inf sanitize, also needed here + // because mipmap generation propagates a single NaN texel across the + // whole mip chain. + if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); + return max(envcolor, vec3(0.0)); #else return vec3(0.5); // was vec3(0.0), match the non-LOD fallback #endif -} \ No newline at end of file +} diff --git a/shaders/tonemapping.glsl b/shaders/tonemapping.glsl index 27fff21b..152b3b7e 100644 --- a/shaders/tonemapping.glsl +++ b/shaders/tonemapping.glsl @@ -46,6 +46,17 @@ vec4 tonemap(vec4 x) // with pureWhite=1.0 it collapses to identity (L*(1+L)/(1+L) = L) and // just clips HDR>1.0 at the framebuffer -> washed-out / burnt look. // ACES gives a smooth highlight shoulder + slight toe contrast. - return FBOUT(vec4(ACESFilm(x.rgb), x.a)); + + // Last-line-of-defense sanitize. ACESFilm has the form + // (x*(a*x+b)) / (x*(c*x+d)+e) + // which maps NaN -> NaN and +Inf -> NaN (Inf/Inf). Either turns the + // pixel black after framebuffer clamp. A negative HDR input feeds a + // negative numerator/denominator and can produce non-physical output + // that also looks like a black flash. Clamp to a sensible range + // before the curve so a single bad upstream pixel can't escape. + vec3 hdr = x.rgb; + hdr = mix(hdr, vec3(0.0), vec3(any(isnan(hdr)) || any(isinf(hdr)))); + hdr = max(hdr, vec3(0.0)); + return FBOUT(vec4(ACESFilm(hdr), x.a)); //return FBOUT(vec4(reinhard(x.rgb), x.a)); }