[Readback] encode signed and half-float depth texture results correctly

This commit is contained in:
2026-09-20 11:59:46 -04:00
parent 8eaebc9091
commit f5234b9e7e
3 changed files with 72 additions and 3 deletions
@@ -138,9 +138,16 @@
} else if (type == GL_FLOAT) {
const Float value = needDepth ? depth[i] : static_cast<Float>(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<Float>(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<Uint8>(value);
else if (pixelBytes == 2) { const Uint16 v = static_cast<Uint16>(value); Memcpy(dst + i * 2, &v, 2); }
else if (pixelBytes == 4) Memcpy(dst + i * 4, &value, 4);
@@ -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<Float>(s) : d;
Memcpy(dst, &value, sizeof(value)); break;
}
case GL_HALF_FLOAT: {
const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast<Float>(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) {
@@ -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 <algorithm>
#include <cmath>
#include <cstring>
#include <string>
@@ -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<Value> values(static_cast<size_t>(kWidth) * kHeight, static_cast<Value>(-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<long long>(values[0])
<< " expected=" << static_cast<long long>(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<GLushort> values(static_cast<size_t>(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;