From f5234b9e7e2d7ba76d49abf48dd645e61990e5d5 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 20 Sep 2026 11:59:46 -0400 Subject: [PATCH] [Readback] encode signed and half-float depth texture results correctly --- .../DirectGLES/WireTextureReadback.inc | 11 +++- .../Renderer/WireTextureReadback.inc | 6 +- .../DepthStencilReadbackMatrixScenario.cpp | 58 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc index f394cbc5..89ab83d7 100644 --- a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc +++ b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc @@ -138,9 +138,16 @@ } else if (type == GL_FLOAT) { const Float value = needDepth ? depth[i] : static_cast(stencil[i]); Memcpy(dst + i * pixelBytes, &value, sizeof(value)); + } else if (type == GL_HALF_FLOAT) { + const Uint16 value = MG_Util::EncodeFloatToHalfBits( + needDepth ? depth[i] : static_cast(stencil[i])); + Memcpy(dst + i * pixelBytes, &value, sizeof(value)); } else { - const Uint32 value = needDepth ? NormalizedDepthToUnsigned(depth[i], - pixelBytes == 1 ? 255.0 : pixelBytes == 2 ? 65535.0 : 4294967295.0) : stencil[i]; + const Bool signedType = type == GL_BYTE || type == GL_SHORT || type == GL_INT; + const Double maximum = signedType + ? (pixelBytes == 1 ? 127.0 : pixelBytes == 2 ? 32767.0 : 2147483647.0) + : (pixelBytes == 1 ? 255.0 : pixelBytes == 2 ? 65535.0 : 4294967295.0); + const Uint32 value = needDepth ? NormalizedDepthToUnsigned(depth[i], maximum) : stencil[i]; if (pixelBytes == 1) dst[i] = static_cast(value); else if (pixelBytes == 2) { const Uint16 v = static_cast(value); Memcpy(dst + i * 2, &v, 2); } else if (pixelBytes == 4) Memcpy(dst + i * 4, &value, 4); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc index e6496f5a..db6487ee 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc @@ -19,7 +19,7 @@ SizeT size = 0; switch (type) { case GL_BYTE: case GL_UNSIGNED_BYTE: size = 1; break; - case GL_SHORT: case GL_UNSIGNED_SHORT: size = 2; break; + case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_HALF_FLOAT: size = 2; break; case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: size = 4; break; case GL_UNSIGNED_INT_24_8: size = 4; break; case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: size = 8; break; @@ -37,6 +37,10 @@ const Float value = stencilOnly ? static_cast(s) : d; Memcpy(dst, &value, sizeof(value)); break; } + case GL_HALF_FLOAT: { + const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast(s) : d); + Memcpy(dst, &value, sizeof(value)); break; + } case GL_UNSIGNED_INT_24_8: { Uint32 depth24 = 0; if (sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp index 8bcc1a18..24c91944 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp @@ -35,6 +35,7 @@ // only checked "no GL error" would pass against a readback that never touched the buffer, // which is precisely how this whole cluster hid for so long. +#include #include #include #include @@ -536,6 +537,63 @@ namespace MGITest { Gl().EndFrame(); } + TEST_F(DepthStencilReadbackMatrixScenario, TextureDepthReadbackUsesSignedRangesAndHalfFloatEncoding) { + if (!Ready()) return; + DepthSource source = MakeTextureSource(GL_DEPTH_COMPONENT32F); + ASSERT_TRUE(SourceIsUsable()); + struct Sample { float depth; GLbyte byte; GLshort shortValue; GLint intValue; GLushort halfBits; }; + const Sample samples[] = { + {0.0f, 0, 0, 0, 0x0000}, + {0.5f, 64, 16384, 1073741824, 0x3800}, + {1.0f, 127, 32767, 2147483647, 0x3c00}, + }; + for (const auto& sample : samples) { + glBindFramebuffer(GL_FRAMEBUFFER, source.fbo); + ClearDepthStencil(GL_DEPTH_COMPONENT32F, sample.depth, 0); + ASSERT_EQ(FirstGLError(), 0u); + // GPU clear, then a texture read with the source FBO unbound: neither + // the upload shadow nor the currently bound FBO can supply the result. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, source.depthTexture); + const auto read = [&](GLenum type, auto expected) { + using Value = decltype(expected); + std::vector values(static_cast(kWidth) * kHeight, static_cast(-37)); + glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, type, values.data()); + EXPECT_EQ(FirstGLError(), 0u) << "depth=" << sample.depth << " type=" << type; + const auto bad = std::count_if(values.begin(), values.end(), [&](Value value) { return value != expected; }); + EXPECT_EQ(bad, 0) << "depth=" << sample.depth << " type=" << type + << " first=" << static_cast(values[0]) + << " expected=" << static_cast(expected); + }; + read(GL_BYTE, sample.byte); + read(GL_SHORT, sample.shortValue); + read(GL_INT, sample.intValue); + read(GL_HALF_FLOAT, sample.halfBits); + } + glBindTexture(GL_TEXTURE_2D, 0); + DestroySource(source); + Gl().EndFrame(); + } + + TEST_F(DepthStencilReadbackMatrixScenario, TextureStencilHalfFloatReadbackEncodesTheIndex) { + if (!Ready()) return; + DepthSource source = MakeTextureSource(GL_STENCIL_INDEX8); + ASSERT_TRUE(SourceIsUsable()); + ClearDepthStencil(GL_STENCIL_INDEX8, 0.0f, 5); + ASSERT_EQ(FirstGLError(), 0u); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, source.depthTexture); + std::vector values(static_cast(kWidth) * kHeight, 0xdead); + glGetTexImage(GL_TEXTURE_2D, 0, GL_STENCIL_INDEX, GL_HALF_FLOAT, values.data()); + EXPECT_EQ(FirstGLError(), 0u); + // Half-float 5.0 is 0x4500. Writing the raw index 0x0005 is a different value. + EXPECT_EQ(std::count_if(values.begin(), values.end(), [](GLushort value) { return value != 0x4500; }), 0) + << "first half word=" << std::hex << values[0]; + glBindTexture(GL_TEXTURE_2D, 0); + DestroySource(source); + Gl().EndFrame(); + } + // glCopyTexImage2D out of a depth attachment, then read the copy - verify_copy_tex_image. TEST_F(DepthStencilReadbackMatrixScenario, CopyTexImageFromADepthAttachmentSurvivesAReadBack) { if (!Ready()) return;