mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Pipe, MG_Remote, MG_Test, MG_IntegrationTest] (P7): accept empty texture levels in the respecify extent carrier
This commit is contained in:
@@ -104,6 +104,7 @@ add_executable(MobileGLIntegrationTest
|
||||
Scenarios/ImageFormatQualifierScenario.cpp
|
||||
Scenarios/NonCoreImageFormatScenario.cpp
|
||||
Scenarios/ImageSizeAfterRespecScenario.cpp
|
||||
Scenarios/EmptyTextureLevelScenario.cpp
|
||||
Scenarios/SsboDeclarationFormScenario.cpp
|
||||
Scenarios/Glsl420DeclarationScenario.cpp
|
||||
Scenarios/IoBlockNameCollisionScenario.cpp
|
||||
@@ -2862,6 +2863,13 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
mgl_itest_register_split_arms("${mglItestD2Scenario}.*" "")
|
||||
endforeach()
|
||||
|
||||
# P7 gate 5 (g5-msrbo): AN EMPTY TEXTURE LEVEL. glTexImage*D with a zero extent is legal GL
|
||||
# and dEQP's resetStateGLCore issues it for every target after every case; PH-4's extent
|
||||
# carrier refused it as ProtocolCorruption on the apply thread, so no KHR-GL case could
|
||||
# finish on a split arm. Backend-agnostic (the refusal was in MGPipeApplyResourceRespecify),
|
||||
# so the DirectGLES gated arms carry it here and the Magma gated lanes below carry it too.
|
||||
mgl_itest_register_split_arms("EmptyTextureLevelScenario.*" "")
|
||||
|
||||
# ProgramPipelineScenario BEYOND *StorageBlock*: the i1 block above already registers the two
|
||||
# storage-block cases. The other nine are pure name/state cases that draw nothing, and
|
||||
# ScenarioFixture's armed-lane rule (the client encoder's record ordinal must move) makes a
|
||||
@@ -3071,6 +3079,19 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm${mglItestArmLogTag}-${fmCase}.log")
|
||||
endforeach()
|
||||
# P7 gate 5 (g5-msrbo): the empty-level pair, on the gated Magma lanes as well - the CTS
|
||||
# arm gate 5 measures is inproc x DirectVulkan, and every one of its cases ends in the
|
||||
# empty-level reset this scenario reproduces.
|
||||
foreach(emptyLevelCase TheCtsStateResetDefinesEveryDefaultTargetEmpty
|
||||
ALevelDefinedEmptyThenRedefinedCarriesTheNewTexels)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.Fm."
|
||||
TEST_FILTER "EmptyTextureLevelScenario.${emptyLevelCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-emptylevel${mglItestArmLogTag}-${emptyLevelCase}.log")
|
||||
endforeach()
|
||||
# ---- P7 wave 2-B: THE TWO SHADER MIP ARMS, FORCED ------------------------------------
|
||||
#
|
||||
# WHY A KNOB AND NOT A FORMAT. The refusal these two entries cover
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/EmptyTextureLevelScenario.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// Scenario - A TEXTURE LEVEL DEFINED EMPTY IS LEGAL GL, AND IT MUST NOT END THE SESSION.
|
||||
//
|
||||
// glTexImage1D(width 0), glTexImage2D(0 x 0), glTexImage3D(0 x 0 x 0) define a level with no
|
||||
// texels. dEQP's glu::resetStateGLCore issues exactly that for EVERY texture target on EVERY unit
|
||||
// after EVERY case (gluStateReset.cpp), so a split arm that cannot carry an empty level cannot run
|
||||
// a single KHR-GL case to completion.
|
||||
//
|
||||
// P7 PH-4 (f90afcd1) put each per-level respecify's exact extent on the wire and made the server
|
||||
// refuse any zero component as a noncanonical carrier - Fatal{ProtocolCorruption,
|
||||
// "ResourceRespecify.ExtentCarrier"} on the apply thread, from the reset that follows the case
|
||||
// rather than the case itself. The two cases below are the reset's own sequence, then the
|
||||
// round trip that proves a level can go empty and come back with texels the server really staged.
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glcorearb.h>
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
class EmptyTextureLevelScenario : public ScenarioTest {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
if (!Ready()) return;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo);
|
||||
if (m_texture != 0) glDeleteTextures(1, &m_texture);
|
||||
m_fbo = m_texture = 0;
|
||||
while (glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
}
|
||||
|
||||
// Every target resetStateGLCore empties, in its order, on the default textures.
|
||||
static void EmptyEveryDefaultTarget() {
|
||||
glBindTexture(GL_TEXTURE_1D, 0);
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++face)
|
||||
glTexImage2D(face, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_1D_ARRAY, 0);
|
||||
glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_3D, 0);
|
||||
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glBindTexture(GL_TEXTURE_RECTANGLE, 0);
|
||||
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
}
|
||||
|
||||
GLuint m_texture = 0;
|
||||
GLuint m_fbo = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// dEQP's reset, verbatim in shape: every default texture target defined EMPTY at level 0,
|
||||
// twice (the reset runs after every case), then an ordinary clear + readback. Before the fix
|
||||
// the first glTexImage1D ended the session on every split arm; the readback is what proves
|
||||
// the session is still the one the case started with.
|
||||
TEST_F(EmptyTextureLevelScenario, TheCtsStateResetDefinesEveryDefaultTargetEmpty) {
|
||||
if (!Ready()) return;
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
EmptyEveryDefaultTarget();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
EmptyEveryDefaultTarget();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "an empty level definition is legal GL (pass " << pass << ")";
|
||||
}
|
||||
|
||||
HeadlessGL& gl = Gl();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glViewport(0, 0, gl.Width(), gl.Height());
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
std::array<GLubyte, 4> pixel{};
|
||||
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
// The default framebuffer's readback is not what this case is about (it reads zeros on some
|
||||
// DirectVulkan hosts, tools/cts/README.md) - what matters is that the call RETURNED, i.e.
|
||||
// the session that took the empty levels is still answering.
|
||||
gl.EndFrame();
|
||||
}
|
||||
|
||||
// A NAMED texture's level goes 4x4 (with texels) -> EMPTY -> 2x2 (with other texels), and the
|
||||
// 2x2 content must be what a framebuffer readback of that level returns. This is the staged
|
||||
// store's half: the empty definition must drop the 4x4 bytes and coverage (the coordinate
|
||||
// system was replaced), and must not stand in the way of the next non-empty definition
|
||||
// adopting its own bytes.
|
||||
TEST_F(EmptyTextureLevelScenario, ALevelDefinedEmptyThenRedefinedCarriesTheNewTexels) {
|
||||
if (!Ready()) return;
|
||||
glGenTextures(1, &m_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
std::vector<GLubyte> redTexels(4 * 4 * 4, 0);
|
||||
for (std::size_t i = 0; i < redTexels.size(); i += 4) {
|
||||
redTexels[i] = 255;
|
||||
redTexels[i + 3] = 255;
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, redTexels.data());
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // empty
|
||||
std::vector<GLubyte> greenTexels(2 * 2 * 4, 0);
|
||||
for (std::size_t i = 0; i < greenTexels.size(); i += 4) {
|
||||
greenTexels[i + 1] = 255;
|
||||
greenTexels[i + 3] = 255;
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, greenTexels.data());
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "4x4 -> empty -> 2x2 on one level is legal GL";
|
||||
|
||||
glGenFramebuffers(1, &m_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE));
|
||||
std::vector<GLubyte> readBack(2 * 2 * 4, 7);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, readBack.data());
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
EXPECT_EQ(readBack, greenTexels)
|
||||
<< "the level that went empty and came back must hold the 2x2 texels it was redefined with";
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -428,10 +428,15 @@ namespace MobileGL::MG_Pipe {
|
||||
inline constexpr Uint32 MGPipeRespecifiedDepthOf(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? 0u : static_cast<Uint32>(desc.BufSize);
|
||||
}
|
||||
// A ZERO COMPONENT IS CANONICAL. glTexImage1D(width 0), glTexImage2D(0 x 0) and friends are
|
||||
// legal GL that define an EMPTY level - dEQP's resetStateGLCore issues exactly that for every
|
||||
// texture target after every case - and the frontend reports the level's exact extent as
|
||||
// e.g. {0, 1, 1}. Requiring nonzero components made that legal call a session Fatal on every
|
||||
// split arm. The carrier's only noncanonical shape is a depth word past 32 bits; whether an
|
||||
// extent is USABLE is the staged store's question (an empty level has no byte bound, so any
|
||||
// resource_subdata against it is refused there).
|
||||
inline constexpr Bool MGPipeRespecifiedExtentCarrierIsCanonical(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ||
|
||||
((desc.BufSize >> 32) == 0 && MGPipeRespecifiedWidthOf(desc) != 0 &&
|
||||
MGPipeRespecifiedHeightOf(desc) != 0 && MGPipeRespecifiedDepthOf(desc) != 0);
|
||||
return MGPipeRespecifyIsWholeResource(desc) || (desc.BufSize >> 32) == 0;
|
||||
}
|
||||
|
||||
inline constexpr void MGPipeSetRespecifiedLevel(MGPResourceDesc& desc, Uint16 uploadTarget,
|
||||
|
||||
@@ -2081,7 +2081,7 @@ namespace MobileGL::MG_Pipe {
|
||||
if (!MGPipeRespecifyIsWholeResource(desc) &&
|
||||
!MGPipeRespecifiedExtentCarrierIsCanonical(desc)) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.ExtentCarrier\"} - "
|
||||
"a named image level must carry nonzero width/height/depth with no high BufSize bits");
|
||||
"a named image level's depth word must fit in 32 bits (no high BufSize bits)");
|
||||
}
|
||||
if (level == nullptr && desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer) &&
|
||||
(desc.BufOffset != 0 || desc.BufSize != 0)) {
|
||||
|
||||
@@ -376,6 +376,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// extent carried by that accepted operation (including noncanonical mutable mips and
|
||||
// null-data glTexImage*D). Its byte bound is established here, before any upload arrives.
|
||||
// An extent move replaces the coordinate system, so old bytes and coverage go with it.
|
||||
//
|
||||
// AN EMPTY LEVEL IS A DEFINITION TOO. A zero width, height or depth is legal GL
|
||||
// (glTexImage1D(width 0) is how dEQP's resetStateGLCore resets every texture target after
|
||||
// every case) and it replaces the level's coordinate system like any other: the old bytes
|
||||
// go, the level is defined at its exact empty extent - which is what the frontend's
|
||||
// GetMipmapTexelSize answers for it, so both roles agree - and it gets NO byte bound, so
|
||||
// a resource_subdata against it is RequireDeclaredLevel's refusal rather than a write
|
||||
// into storage that has no texels. Only a NEGATIVE component (a 32-bit carrier word past
|
||||
// INT_MAX) is malformed.
|
||||
void NoteLevelDefined(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent,
|
||||
Uint8 resourceTarget = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
Uint32 internalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8)) {
|
||||
@@ -383,10 +392,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
Uint64 byteBound = 0;
|
||||
const Bool supportsSubData = StagedTextureTargetSupportsSubData(resourceTarget);
|
||||
const Bool validExtent = extent.x() > 0 && extent.y() > 0 && extent.z() > 0;
|
||||
const Bool validExtent = extent.x() >= 0 && extent.y() >= 0 && extent.z() >= 0;
|
||||
const Bool emptyExtent = validExtent && (extent.x() == 0 || extent.y() == 0 || extent.z() == 0);
|
||||
if (MG_Util::GetSizedInternalFormatSizeInBytes(
|
||||
static_cast<TextureInternalFormat>(internalFormat)) == 0 || !validExtent ||
|
||||
(supportsSubData && !StagedTextureDeclaredLevelByteBound(
|
||||
(supportsSubData && !emptyExtent && !StagedTextureDeclaredLevelByteBound(
|
||||
resourceTarget, internalFormat, extent, m_deviceLimits, &byteBound))) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.NoteLevelDefined\"} - "
|
||||
@@ -407,7 +417,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
shadow.Extent = extent;
|
||||
shadow.InternalFormat = internalFormat;
|
||||
shadow.ResourceTarget = resourceTarget;
|
||||
shadow.DeclaredByteBound = byteBound; // zero for targets without glTexSubImage
|
||||
shadow.DeclaredByteBound = byteBound; // zero for targets without glTexSubImage and for an empty level
|
||||
shadow.Defined = true;
|
||||
m_any.store(true, std::memory_order_release);
|
||||
}
|
||||
@@ -591,7 +601,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"} - "
|
||||
"resource_subdata is not legal for the server-declared texture target "
|
||||
"(key=%llu, uploadTarget=%u, level=%u, target=%u)",
|
||||
"or empty level (key=%llu, uploadTarget=%u, level=%u, target=%u)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
static_cast<Uint32>(shadow.ResourceTarget));
|
||||
}
|
||||
|
||||
@@ -164,6 +164,65 @@ TEST(StagedTextureStoreTest, AMutableNoncanonicalLevelKeepsItsExactDeclaredExten
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 2));
|
||||
}
|
||||
|
||||
// AN EMPTY LEVEL IS A DEFINITION (P7 gate 5). glTexImage1D(width 0) / glTexImage2D(0 x 0) are
|
||||
// legal GL and dEQP's state reset issues them after every case; the frontend reports the level's
|
||||
// exact extent as {0,1,1} / {0,0,1}. The store must take it as a definition that REPLACES the
|
||||
// level (the old run and its coverage go), answer the exact empty extent, and give it no byte
|
||||
// bound - never a session Fatal. A negative component (a carrier word past INT_MAX) stays one.
|
||||
TEST(StagedTextureStoreTest, AnEmptyLevelIsADefinitionWithNoByteBound) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(42, 1));
|
||||
constexpr Uint16 kTex1DTarget = static_cast<Uint16>(TextureUploadTarget::Texture1D);
|
||||
const auto tex1D = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex1D);
|
||||
const auto rgba8 = static_cast<Uint32>(TextureInternalFormat::RGBA8);
|
||||
|
||||
store.NoteLevelDefined(key, kTex1DTarget, 0, IntVec3{4, 1, 1}, tex1D, rgba8);
|
||||
Vector<Uint8> bytes(16, 0x5A);
|
||||
store.Adopt(key, kTex1DTarget, 0, IntVec3{4, 1, 1}, bytes.data(), bytes.size());
|
||||
ASSERT_TRUE(store.IsCovered(key, kTex1DTarget, 0));
|
||||
|
||||
store.NoteLevelDefined(key, kTex1DTarget, 0, IntVec3{0, 1, 1}, tex1D, rgba8);
|
||||
EXPECT_TRUE(store.IsLevelDefined(key, kTex1DTarget, 0)) << "an empty level still exists";
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex1DTarget, 0), IntVec3(0, 1, 1))
|
||||
<< "the store must answer the frontend's own exact extent for an empty level";
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key, kTex1DTarget, 0), 0u) << "an empty level has no texels";
|
||||
EXPECT_EQ(store.LevelByteSize(key, kTex1DTarget, 0), 0u) << "the 4-texel run belongs to the old level";
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex1DTarget, 0));
|
||||
|
||||
// And back: the next non-empty definition takes its own bytes as usual.
|
||||
store.NoteLevelDefined(key, kTex1DTarget, 0, IntVec3{2, 1, 1}, tex1D, rgba8);
|
||||
Vector<Uint8> smaller(8, 0x3C);
|
||||
store.Adopt(key, kTex1DTarget, 0, IntVec3{2, 1, 1}, smaller.data(), smaller.size());
|
||||
EXPECT_TRUE(store.IsCovered(key, kTex1DTarget, 0));
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key, kTex1DTarget, 0), 8u);
|
||||
|
||||
// The 2D and 3D spellings of the same reset.
|
||||
const Uint64 key2D = Server::StagedTextureStore::KeyForHandle(TestHandle(43, 1));
|
||||
store.NoteLevelDefined(key2D, kTex2DTarget, 0, IntVec3{0, 0, 1},
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D), rgba8);
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key2D, kTex2DTarget, 0), IntVec3(0, 0, 1));
|
||||
const Uint64 key3D = Server::StagedTextureStore::KeyForHandle(TestHandle(44, 1));
|
||||
constexpr Uint16 kTex3DTarget = static_cast<Uint16>(TextureUploadTarget::Texture3D);
|
||||
store.NoteLevelDefined(key3D, kTex3DTarget, 0, IntVec3{0, 0, 0},
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex3D), rgba8);
|
||||
EXPECT_TRUE(store.IsLevelDefined(key3D, kTex3DTarget, 0));
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key3D, kTex3DTarget, 0), 0u);
|
||||
}
|
||||
|
||||
// The wire half of the same rule: the carrier's only noncanonical shape is a depth word past
|
||||
// 32 bits. A zero component is an empty level, which the store above takes.
|
||||
TEST(StagedTextureStoreTest, AZeroExtentCarrierIsCanonicalAndAHighDepthWordIsNot) {
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex1D);
|
||||
MG_Pipe::MGPipeSetRespecifiedLevel(desc, kTex2DTarget, 0, 0, 1, 1);
|
||||
EXPECT_TRUE(MG_Pipe::MGPipeRespecifiedExtentCarrierIsCanonical(desc))
|
||||
<< "glTexImage1D(width 0) is legal GL; its carrier must not be refused";
|
||||
MG_Pipe::MGPipeSetRespecifiedLevel(desc, kTex2DTarget, 0, 0, 0, 0);
|
||||
EXPECT_TRUE(MG_Pipe::MGPipeRespecifiedExtentCarrierIsCanonical(desc));
|
||||
desc.BufSize |= (Uint64{1} << 32);
|
||||
EXPECT_FALSE(MG_Pipe::MGPipeRespecifiedExtentCarrierIsCanonical(desc));
|
||||
}
|
||||
|
||||
TEST(StagedTextureStoreTest, MultisampleAndTextureBufferTargetsCannotAcceptStagedTexelBytes) {
|
||||
EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData(
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2DMS)));
|
||||
|
||||
Reference in New Issue
Block a user