diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 2b8b8b01..4592d792 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -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(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(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(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(kFaceBytes), packed); + EXPECT_EQ(static_cast(packed[0]), 15) << "the PBO holds face " << (static_cast(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); diff --git a/MobileGL/MG_Test/Texture/TextureViewTest.cpp b/MobileGL/MG_Test/Texture/TextureViewTest.cpp index f3085249..0729a654 100644 --- a/MobileGL/MG_Test/Texture/TextureViewTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureViewTest.cpp @@ -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(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(output[0]), 10 + kViewMinLayer + face) + << "face " << face << " of a view based at layer " << kViewMinLayer << " answered with layer " + << (static_cast(output[0]) - 10); + } + } } // namespace