From 996d1009857c40d6ce861baa392fb28e996a31f4 Mon Sep 17 00:00:00 2001 From: Zanibar <91260002+taylnos@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:54:54 -0500 Subject: [PATCH] allightmapblur_gl_test compares the dispatch's 8 bits with the fragment passes' single precision, which AMD's 8-bit targets do not round as the store does On the RX 6800 (driver 26.8.1) the texel for texel case failed at every blur size and convention: about 4,200 of 94,357 pixels, 4.4%, where the dispatch's occlusion was exactly one 8-bit step above the fragment passes'. The driver hands a fragment shader's colour to an RGBA8 target at half precision, rounded toward zero, before rounding it to 8 bits: the light map held exactly that of the passes' single precision at all 84,337 drawn texels. The dispatch's rg8 store rounds the single precision itself to the nearest 8 bits. The two blurs agree; only the targets' rounding of them differed. The exact case's second pass now draws into a single-precision copy of the light map, seeded with its texels so the sky keeps them, and the dispatch's store must hold the 8 bits nearest it, or at a tie either of the two. One texel lay at 165.50002 steps, which the store rounded down. Co-Authored-By: Claude Opus 5.5 --- .../alrender/tests/allightmapblur_gl_scene.h | 29 +++++++++++++------ .../alrender/tests/allightmapblur_gl_test.cpp | 7 +++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/indra/alrender/tests/allightmapblur_gl_scene.h b/indra/alrender/tests/allightmapblur_gl_scene.h index 27679a8b5f..7b4d55a0a2 100644 --- a/indra/alrender/tests/allightmapblur_gl_scene.h +++ b/indra/alrender/tests/allightmapblur_gl_scene.h @@ -95,7 +95,7 @@ namespace allightmapblur_gl mFragment.unload(); mCompute.unload(); mShaders.clearShaderObjects(); - for (LLRenderTarget* target : { &mGBuffer, &mLight, &mScratch, &mFloatScratch, &mComputeLight, &mBlurred }) + for (LLRenderTarget* target : { &mGBuffer, &mLight, &mScratch, &mFloatScratch, &mFloatLight, &mComputeLight, &mBlurred }) { target->release(); } @@ -229,8 +229,9 @@ namespace allightmapblur_gl REQUIRE(mGBuffer.ensure(gbuffer) == LLRenderTarget::EnsureResult::Built); REQUIRE(mLight.ensure(light, mGBuffer) == LLRenderTarget::EnsureResult::Built); REQUIRE(mScratch.ensure(light, mGBuffer) == LLRenderTarget::EnsureResult::Built); - REQUIRE(mFloatScratch.ensure({ .width = w, .height = h, .color = { GL_RGBA32F } }, mGBuffer) == - LLRenderTarget::EnsureResult::Built); + const ALRenderTargetDesc single{ .width = w, .height = h, .color = { GL_RGBA32F } }; + REQUIRE(mFloatScratch.ensure(single, mGBuffer) == LLRenderTarget::EnsureResult::Built); + REQUIRE(mFloatLight.ensure(single, mGBuffer) == LLRenderTarget::EnsureResult::Built); REQUIRE(mComputeLight.ensure(light, mGBuffer) == LLRenderTarget::EnsureResult::Built); REQUIRE(mBlurred.ensure({ .width = w, .height = h, .color = { GL_RG8 } }) == LLRenderTarget::EnsureResult::Built); @@ -320,11 +321,20 @@ namespace allightmapblur_gl } // renderDeferredLighting's two passes: across into the scratch, cleared white as the sun pass clears the map, - // and down from it back into the map, each over geometry alone. Through a single-precision scratch, the - // fragment passes keep between the axes what the dispatch keeps in shared memory. - void blurFragment(const ALLightMapBlur::Kernel& kernel, bool float_scratch = false) + // and down from it back into the map, each over geometry alone. In single precision, through a scratch and + // into a copy of the light map (mFloatLight), the fragment passes keep between the axes what the dispatch keeps + // in shared memory, and leave unrounded what its store rounds to 8 bits: an 8-bit target need not round as the + // store does, and AMD's driver hands one the colour at half precision, rounded toward zero, so about one texel + // in twenty comes out a step lower. + void blurFragment(const ALLightMapBlur::Kernel& kernel, bool in_float = false) { - LLRenderTarget& scratch = float_scratch ? mFloatScratch : mScratch; + LLRenderTarget& scratch = in_float ? mFloatScratch : mScratch; + LLRenderTarget& light = in_float ? mFloatLight : mLight; + if (in_float) + { + // The sky keeps the light map's texels. + ll_test::writeTexture(mFloatLight.getTexture(), mW, mH, GL_RGBA, GL_FLOAT, read(mLight).data()); + } scratch.bindTarget(); gGL.setClearColor(LLColor4::white); scratch.clear(GL_COLOR_BUFFER_BIT); @@ -340,7 +350,7 @@ namespace allightmapblur_gl scratch.flush(); bindDeferred(mFragment, scratch, kernel); - mLight.bindTarget(); + light.bindTarget(); mFragment.uniform2f(LLShaderMgr::DELTA, 0.f, 1.f); { LLGLDisable blend(GL_BLEND); @@ -348,7 +358,7 @@ namespace allightmapblur_gl mTriangle->setBuffer(); mTriangle->drawArrays(LLRender::TRIANGLES, 0, 3); } - mLight.flush(); + light.flush(); mFragment.unbind(); } @@ -527,6 +537,7 @@ namespace allightmapblur_gl LLRenderTarget mLight; LLRenderTarget mScratch; LLRenderTarget mFloatScratch; + LLRenderTarget mFloatLight; LLRenderTarget mComputeLight; LLRenderTarget mBlurred; LLPointer mTriangle; diff --git a/indra/alrender/tests/allightmapblur_gl_test.cpp b/indra/alrender/tests/allightmapblur_gl_test.cpp index 965b822e51..8ac02d4bc0 100644 --- a/indra/alrender/tests/allightmapblur_gl_test.cpp +++ b/indra/alrender/tests/allightmapblur_gl_test.cpp @@ -134,15 +134,18 @@ TEST_CASE_METHOD(Scene, "allightmapblur_gl: one dispatch blurs texel for texel a poisonBlurred(); REQUIRE(blurCompute(kernel)); - const std::vector fragment = read(mLight); + // The dispatch's store holds the 8 bits nearest the fragment passes' single precision, or at a tie, which their + // value lies within a rounding of at a few texels, either of the two. + const std::vector fragment = read(mFloatLight); const std::vector compute = read(mBlurred); + const auto nearest = [](F32 stored, F32 value) { return std::fabs(stored - value) * 255.f <= 0.5f + 1e-4f; }; S32 differ = 0; for (S32 y = 0; y < H; ++y) { for (S32 x = 0; x < W; ++x) { const size_t i = (size_t(y) * W + x) * 4; - if (compute[i] != fragment[i] || compute[i + 1] != fragment[i + 1]) + if (!nearest(compute[i], fragment[i]) || !nearest(compute[i + 1], fragment[i + 1])) { if (++differ <= 8) {