[Test] (Readback): pin the one-face pixel pack buffer and a cube view's per-face layers

This commit is contained in:
2026-08-27 21:58:51 -04:00
parent 645a12d8bc
commit 2c3fc583d5
2 changed files with 83 additions and 0 deletions
+40
View File
@@ -2087,6 +2087,46 @@ TEST_F(TextureTest, GetTextureSubImageSelectsTheCubeFaceZOffsetNames) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
}
// glGetTexImage of ONE cube face packs one face, so a PIXEL_PACK_BUFFER holding one face is
// exactly the right size for it. The validator used to measure the bound PBO against all SIX
// faces' worth and refuse - INVALID_OPERATION for a buffer the copy that follows would have filled
// precisely. glGetTexImage passes no bufSize, which skips the destination-size branch but NOT the
// PBO one, so this is the only spelling where the six-face sizing was reachable at all.
TEST_F(TextureTest, GetTexImageOfOneCubeFacePacksIntoAOneFacePixelPackBuffer) {
constexpr GLsizei kEdge = 2;
constexpr SizeT kFaceBytes = static_cast<SizeT>(kEdge) * kEdge * 4;
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP, texture);
MG_Impl::GLImpl::TexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, kEdge, kEdge);
for (int face = 0; face < 6; ++face) {
Uint8 seed[kFaceBytes];
for (SizeT i = 0; i < kFaceBytes; ++i) seed[i] = static_cast<Uint8>(10 + face);
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, 0, 0, kEdge, kEdge, GL_RGBA,
GL_UNSIGNED_BYTE, seed);
}
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "seeding the six faces failed";
GLuint buffer = 0;
MG_Impl::GLImpl::GenBuffers(1, &buffer);
MG_Impl::GLImpl::BindBuffer(GL_PIXEL_PACK_BUFFER, buffer);
MG_Impl::GLImpl::BufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(kFaceBytes), nullptr, GL_STREAM_READ);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "creating the one-face pixel pack buffer failed";
MG_Impl::GLImpl::GetTexImage(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR)
<< "a pixel pack buffer sized for the one face this call packs was refused";
Uint8 packed[kFaceBytes] = {};
MG_Impl::GLImpl::GetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, static_cast<GLsizeiptr>(kFaceBytes), packed);
EXPECT_EQ(static_cast<int>(packed[0]), 15) << "the PBO holds face " << (static_cast<int>(packed[0]) - 10)
<< ", not -Z";
MG_Impl::GLImpl::BindBuffer(GL_PIXEL_PACK_BUFFER, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, TextureParameteriAndBindTextureUnitAreDirectStateAccess) {
GLuint texture = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
@@ -505,4 +505,47 @@ namespace {
ExpectSingleGlError(GL_INVALID_OPERATION);
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(view));
}
// ======================= which of the owner's layers a face names =======================
// A GL_TEXTURE_CUBE_MAP view over a LAYERED owner - a 2D array here, a cube-map ARRAY behaves
// identically - is the one shape where the face a target names cannot be carried by the choice
// of blob: the owner keeps every layer in ONE blob, so there is nothing for
// ToOwnerUploadTarget to choose between and the face has to land in the byte offset instead.
// It did not. The offset shifted by the view's layer origin alone, so all six face tokens read
// the view's FIRST layer-face - silently, with real texels from a real layer, on every path
// that answers out of the CPU shadow.
//
// The shadow is exactly what this exercises: the fixture's backend is not DirectVulkan, so the
// by-name readback takes the shadow arm rather than asking a backend. (DirectVulkan's own path
// resolves the face into a Vulkan baseArrayLayer and was always right, which is what made this
// a disagreement between the two backends rather than a uniform wrong answer.)
TEST_F(TextureViewTest, CubeMapViewOfAnArrayReadsTheFaceEachTokenNames) {
constexpr GLint kLayers = 8;
constexpr GLint kViewMinLayer = 2;
const GLuint storage = MakeImmutable2DArray(1, 1, kLayers);
// Every layer carries its own index, so a read that lands on the wrong one says which one
// answered instead of merely failing.
for (GLint layer = 0; layer < kLayers; ++layer) {
const Uint8 texel[] = {static_cast<Uint8>(10 + layer), 20, 30, 40};
MG_Impl::GLImpl::TexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
texel);
}
DrainPendingGlErrors();
const GLuint view = GenTexture();
MG_Impl::GLImpl::TextureView(view, GL_TEXTURE_CUBE_MAP, storage, GL_RGBA8, 0, 1, kViewMinLayer, 6);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the cube-map view over the array was refused";
for (GLint face = 0; face < 6; ++face) {
Uint8 output[4] = {};
MG_Impl::GLImpl::GetTextureSubImage(view, 0, 0, 0, face, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
sizeof(output), output);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "reading face " << face << " errored";
EXPECT_EQ(static_cast<GLint>(output[0]), 10 + kViewMinLayer + face)
<< "face " << face << " of a view based at layer " << kViewMinLayer << " answered with layer "
<< (static_cast<GLint>(output[0]) - 10);
}
}
} // namespace