mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Remote, MG_Pipe, MG_Backend, MG_Test] (P7): repair F2 ID-49, byteless-level and R8 lane regressions
This commit is contained in:
@@ -1694,8 +1694,14 @@ void main() {
|
||||
for (Uint32 level = baseMipLevel + 1; level < requiredMipLevelCount; ++level) {
|
||||
// A level the shadow already tracks (an adopted base chain) keeps its bytes;
|
||||
// the generation made the GPU newer than either, which the mark says.
|
||||
// PH-4: a twin-address key never receives a staged run (no Adopt names one),
|
||||
// so these generated levels are declared BYTELESS - an Unknown format gives a
|
||||
// zero byte bound and skips the Tex2D device-limit rule the defaults would
|
||||
// apply to a 2D-array or 3D level's depth.
|
||||
store.NoteLevelDefined(key, static_cast<Uint16>(uploadTarget), static_cast<Uint16>(level),
|
||||
ComputeMipTexelSize(storageBaseTexelSize, level));
|
||||
ComputeMipTexelSize(storageBaseTexelSize, level),
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
static_cast<Uint32>(TextureInternalFormat::Unknown));
|
||||
store.MarkLevelGpuDirty(key, static_cast<Uint16>(uploadTarget), static_cast<Uint16>(level),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -1474,7 +1474,11 @@
|
||||
for (Uint32 level=base+1;level<base+count;++level) {
|
||||
const IntVec3 extent = MG_Remote::Server::StagedTextureMipExtent(
|
||||
record.Desc.Target, record.Desc.Width, record.Desc.Height, record.Desc.Depth, level);
|
||||
store.NoteLevelDefined(key,static_cast<Uint16>(upload),static_cast<Uint16>(level),extent);
|
||||
// PH-4: the record's own target and format, not NoteLevelDefined's Tex2D/RGBA8
|
||||
// defaults - a 2D-array or 3D level has depth > 1, which the Tex2D device-limit rule
|
||||
// refuses by name (TextureViewScenario.GenerateMipmapThroughAView on all Magma arms).
|
||||
store.NoteLevelDefined(key,static_cast<Uint16>(upload),static_cast<Uint16>(level),extent,
|
||||
record.Desc.Target,record.Desc.InternalFormat);
|
||||
store.MarkLevelGpuDirty(key,static_cast<Uint16>(upload),static_cast<Uint16>(level),true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,10 +429,28 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const Uint64 pixels = width * height;
|
||||
if (pixels > kUint64Max / bpp) return rejectReadback();
|
||||
const Uint64 tight = pixels * bpp;
|
||||
if (tight != info.DstSize || tight > m_maxReplyBytes ||
|
||||
// PH-3's bound is the SERVER's answer size: w*h*bpp against its own maxReplyBytes,
|
||||
// checked before the scratch grows. It is not a rule about DstSize.
|
||||
if (tight > m_maxReplyBytes ||
|
||||
tight > static_cast<Uint64>(std::numeric_limits<SizeT>::max())) {
|
||||
return rejectReadback();
|
||||
}
|
||||
// ID-49: THE REPLY CROSSES TIGHT, WHATEVER DstSize THE CLIENT SENT. The server reads with
|
||||
// neutral pack state into a w*h*bpp extent that IS the reply payload and the client
|
||||
// scatters it per its own GL_PACK_* state, so a DstSize that disagrees with the tight
|
||||
// extent (a client that sized its destination with pack padding) is logged and the tight
|
||||
// extent this side owns is read and posted - never the client's number, so a wrong
|
||||
// DstSize cannot make this a short read into uninitialised scratch. F2's first PH-3 draft
|
||||
// refused the mismatch instead, which broke the ID-49 control
|
||||
// (ServerLoopEglTest.AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClientSent); the
|
||||
// bound above is what PH-3 needs and it does not depend on DstSize.
|
||||
if (tight != info.DstSize) {
|
||||
MGLOG_E_ONCE("MG_Remote server: read_pixels DstSize %llu != tight w*h*bpp %llu "
|
||||
"(%ux%u, bpp %llu); reading the tight extent (ID-49)",
|
||||
static_cast<unsigned long long>(info.DstSize),
|
||||
static_cast<unsigned long long>(tight), info.Box.W, info.Box.H,
|
||||
static_cast<unsigned long long>(bpp));
|
||||
}
|
||||
|
||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("read_pixels");
|
||||
if (table == nullptr || table->GL.ReadPixels == nullptr) {
|
||||
|
||||
@@ -385,6 +385,16 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// 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.
|
||||
//
|
||||
// SO IS A LEVEL WHOSE FORMAT HAS NO UNCOMPRESSED SIZE HERE (F2 lane repair). A declared
|
||||
// TextureInternalFormat::Unknown level - the TextureView, TextureViewAlias and
|
||||
// TextureUploadShape split lanes all declare one, a 1x1x1 level of an Unknown-format
|
||||
// texture, before any upload - is legal and byteless in exactly the empty level's sense:
|
||||
// declared with a zero byte bound, so any run into it is refused by name
|
||||
// (StagedTextureStore.LevelBound) and the declaration itself is not a session Fatal. What
|
||||
// stays a named refusal here is a declaration this server cannot bound: a negative
|
||||
// component, or a known-format level past the server's own device limits (or whose byte
|
||||
// count overflows).
|
||||
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)) {
|
||||
@@ -394,9 +404,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const Bool supportsSubData = StagedTextureTargetSupportsSubData(resourceTarget);
|
||||
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 && !emptyExtent && !StagedTextureDeclaredLevelByteBound(
|
||||
const Bool sizedFormat = MG_Util::GetSizedInternalFormatSizeInBytes(
|
||||
static_cast<TextureInternalFormat>(internalFormat)) != 0;
|
||||
if (!validExtent ||
|
||||
(supportsSubData && sizedFormat && !emptyExtent && !StagedTextureDeclaredLevelByteBound(
|
||||
resourceTarget, internalFormat, extent, m_deviceLimits, &byteBound))) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.NoteLevelDefined\"} - "
|
||||
@@ -589,22 +600,34 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// coverage answer for such a level, not an error.
|
||||
void RequireDeclaredLevel(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget,
|
||||
Uint16 level, const IntVec3& extent) {
|
||||
if (!shadow.Defined || shadow.InternalFormat == 0) {
|
||||
// `Defined` alone, which only NoteLevelDefined sets. The first draft also refused
|
||||
// `InternalFormat == 0`, but 0 is TextureInternalFormat::R8, so every R8 upload read
|
||||
// as "arrived before the level was declared".
|
||||
if (!shadow.Defined) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - "
|
||||
"resource_subdata arrived before the server declared texture level "
|
||||
"(key=%llu, uploadTarget=%u, level=%u)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level);
|
||||
}
|
||||
if (!StagedTextureTargetSupportsSubData(shadow.ResourceTarget) ||
|
||||
shadow.DeclaredByteBound == 0) {
|
||||
if (!StagedTextureTargetSupportsSubData(shadow.ResourceTarget)) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"} - "
|
||||
"resource_subdata is not legal for the server-declared texture target "
|
||||
"or empty level (key=%llu, uploadTarget=%u, level=%u, target=%u)",
|
||||
"(key=%llu, uploadTarget=%u, level=%u, target=%u)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
static_cast<Uint32>(shadow.ResourceTarget));
|
||||
}
|
||||
if (shadow.DeclaredByteBound == 0) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelBound\"} - "
|
||||
"resource_subdata into a byteless declared level: its extent is empty or "
|
||||
"its format has no uncompressed size on this server "
|
||||
"(key=%llu, uploadTarget=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
shadow.Extent.x(), shadow.Extent.y(), shadow.Extent.z(),
|
||||
shadow.InternalFormat);
|
||||
}
|
||||
if (shadow.Extent == extent) return;
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - "
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
// a split-armed case has to arm that half too - registering an op table is the SERVER's arming.
|
||||
#include <MG_Remote/CapsCodec.h>
|
||||
#include <MG_Remote/Client/CapsMirror.h>
|
||||
// PH-2 (F2): the handle-keyed tables' backstop refusals die through MG_Pipe's session-fail seam.
|
||||
#include <MG_Pipe/PipeSessionFail.h>
|
||||
#include <cstdio>
|
||||
#endif
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
#include <MG_Util/Types.h>
|
||||
@@ -3519,6 +3522,19 @@ namespace {
|
||||
using FakeShaderCsoSlotTable = MobileGL::MG_Backend::DirectGLES::
|
||||
BackendSlotTable<FakeStateObject, FakeBackendObject, MobileGL::MG_Pipe::MGPipeKind::ShaderCso>;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// PH-2 (F2, ID-P7-54): on a disaggregated build the handle overload's backward-generation and
|
||||
// past-the-bound refusals are NAMED session faults through MG_Pipe's seam, not a quiet null
|
||||
// twin - the handle arrived in a peer's payload. The seam's no-hook default writes the line to
|
||||
// the log FILE only, so a death child installs this hook first to put the same line where the
|
||||
// death matcher reads it; the seam then logs and aborts exactly as it would have.
|
||||
void EchoPipeSessionFailToStderr(MobileGL::MG_Pipe::MGPipeFatalFamily, const char* line) {
|
||||
std::fputs(line, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
|
||||
// A log file path no other process and no other case can be writing to: the pid keeps two
|
||||
// SanityTest processes on one host apart, the counter keeps two cases in one process apart.
|
||||
std::filesystem::path UniqueScratchLogPath(const char* stem) {
|
||||
@@ -4333,10 +4349,21 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr
|
||||
// adoption the live twin at `second` is gone. The first is kept because a non-null there
|
||||
// would mean the table handed back the incumbent's own twin under the dead handle, which
|
||||
// is a third outcome and a worse one.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// PH-2: the refusal is a named session fault here, so it is asserted in a death child;
|
||||
// the live twin below is then the parent's, untouched by construction AND by assertion.
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
MG_Pipe::MGPipeInstallSessionFailHook(&EchoPipeSessionFailToStderr);
|
||||
(void)table.GetOrCreate(first);
|
||||
},
|
||||
"BackendSlotTable.Generation");
|
||||
#else
|
||||
auto& stale = table.GetOrCreate(first);
|
||||
EXPECT_EQ(stale, nullptr)
|
||||
<< "SamplerViewCso: a stale handle was answered with the LIVE twin at its slot (this "
|
||||
"assertion cannot tell a refusal from an adoption - the next one does)";
|
||||
#endif
|
||||
EXPECT_NE(table.FindByHandle(second), nullptr)
|
||||
<< "SamplerViewCso: the live twin was destroyed by a handle from its slot's past - "
|
||||
"the backward generation was ADOPTED rather than refused";
|
||||
@@ -5024,10 +5051,22 @@ TEST(DirectGLESSlotTable, AGenerationBehindTheLiveTwinIsRefusedRatherThanAdopted
|
||||
incumbent->marker = 0xC0FFEE;
|
||||
const FakeBackendObject* const raw = incumbent.get();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// PH-2 (F2): the backwards generation is a NAMED session fault on this build - the handle is a
|
||||
// peer's payload - so the refusal is asserted in a death child and the incumbent below is the
|
||||
// parent's, which the refused call never touched.
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
MobileGL::MG_Pipe::MGPipeInstallSessionFailHook(&EchoPipeSessionFailToStderr);
|
||||
(void)table.GetOrCreate(stale);
|
||||
},
|
||||
"BackendSlotTable.Generation");
|
||||
#else
|
||||
auto& answer = table.GetOrCreate(stale);
|
||||
EXPECT_EQ(answer, nullptr)
|
||||
<< "a backwards generation was handed a twin rather than refused; FindByHandle refuses "
|
||||
"the same input, so the two entry points disagreed";
|
||||
#endif
|
||||
|
||||
auto* const still = table.FindByHandle(current);
|
||||
ASSERT_NE(still, nullptr) << "the live twin's entry was retired by a handle from its past";
|
||||
@@ -5043,8 +5082,17 @@ TEST(DirectGLESSlotTable, AGenerationBehindTheLiveTwinIsRefusedRatherThanAdopted
|
||||
EXPECT_EQ(table.FindByHandle(current), nullptr) << "the predecessor's handle still resolves";
|
||||
|
||||
// And a slot past the table's bound is refused rather than resized to.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
MobileGL::MG_Pipe::MGPipeInstallSessionFailHook(&EchoPipeSessionFailToStderr);
|
||||
(void)table.GetOrCreate(MG_Pipe::MGPipeHandle{FakeSlotTable::kMaxHandleSlot, 1u});
|
||||
},
|
||||
"BackendSlotTable.HandleSlot");
|
||||
#else
|
||||
auto& absurd = table.GetOrCreate(MG_Pipe::MGPipeHandle{FakeSlotTable::kMaxHandleSlot, 1u});
|
||||
EXPECT_EQ(absurd, nullptr) << "an unbounded client slot decided a vector resize";
|
||||
#endif
|
||||
EXPECT_EQ(table.FindByHandle(MG_Pipe::MGPipeHandle{FakeSlotTable::kMaxHandleSlot, 1u}), nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -735,7 +735,9 @@ TEST(ServerLoopTest, TheAuditPoisonFillsExactlyTheStagedRunAfterTheApplierReturn
|
||||
create.Resource.Slot = 61;
|
||||
create.Resource.Gen = 1;
|
||||
create.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
create.InternalFormat = 1;
|
||||
// RGBA8, because the 64 staged bytes below are 4x4 texels of four bytes: PH-4 bounds the run
|
||||
// by the declared level's w*h*d*bpp, and the old `1` (R8Snorm, one byte) bounded it at 16.
|
||||
create.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8);
|
||||
create.Width = 4;
|
||||
create.Height = 4;
|
||||
create.Depth = 1;
|
||||
@@ -743,6 +745,16 @@ TEST(ServerLoopTest, TheAuditPoisonFillsExactlyTheStagedRunAfterTheApplierReturn
|
||||
create.Levels = 1;
|
||||
create.Samples = 1;
|
||||
ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::ResourceCreate, &create, sizeof(create)));
|
||||
// PH-4: a level exists for resource_subdata only once a respecify declared it (the emitter's
|
||||
// glTexImage*D always does). Declare level 0 at upload target 0 - the target the upload below
|
||||
// packs - at the 4x4x1 extent the upload's descriptor-derived extent will name.
|
||||
MG_Pipe::MGPResourceDesc respecify = create;
|
||||
respecify.HasDefinedContent = 1;
|
||||
MG_Pipe::MGPipeSetRespecifiedLevel(
|
||||
respecify,
|
||||
MG_Pipe::MGPipePackSubDataTarget(static_cast<Uint32>(MG_Pipe::MGPipeResourceTarget::Tex2D), 0u),
|
||||
0, 4, 4, 1);
|
||||
ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify)));
|
||||
|
||||
Vector<Uint8> texels(4 * 4 * 4, 0x5A);
|
||||
MG_Pipe::MGPSubData upload{};
|
||||
|
||||
@@ -75,6 +75,17 @@ namespace {
|
||||
return handle;
|
||||
}
|
||||
|
||||
// PH-4: resource_subdata lands only in a level a respecify DECLARED - the emitter's
|
||||
// glTexImage*D always sends one before the bytes. The production cases below used to adopt
|
||||
// into a level nothing had declared, which PH-4 refuses by name
|
||||
// (Fatal{ProtocolCorruption, "StagedTextureStore.LevelExtent"}), so each now declares level 0
|
||||
// of the Texture2D upload target at its 4x4x1 extent through the real applier entry point.
|
||||
void DeclareTex2DLevel0(const MG_Pipe::MGPResourceDesc& desc, Uint32 width, Uint32 height) {
|
||||
const MG_Pipe::MGPRespecifiedLevel level = MG_Pipe::MGPipeMakeRespecifiedLevel(
|
||||
MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget), 0, width, height, 1);
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, &level));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// =====================================================================================
|
||||
@@ -556,6 +567,54 @@ TEST(StagedTextureStoreTest, ALevelWithAPieceMissingIsRefusedAsAWholeLevelReadBy
|
||||
}
|
||||
#endif
|
||||
|
||||
// F2 LANE REPAIR, R8: TextureInternalFormat::R8 is enumerator 0, and PH-4's first draft read
|
||||
// `InternalFormat == 0` as "never declared" - so an R8 level a respecify HAD declared refused its
|
||||
// own upload as `StagedTextureStore.LevelExtent`. Red-once: put the `== 0` test back and the run
|
||||
// below dies by that name.
|
||||
TEST(StagedTextureStoreTest, AnR8LevelIsDeclaredAndTakesItsUpload) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(15, 1));
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{4, 4, 1},
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
static_cast<Uint32>(TextureInternalFormat::R8));
|
||||
ASSERT_EQ(store.LevelDeclaredByteBound(key, kTex2DTarget, 0), 16u) << "4x4 texels of one byte";
|
||||
Vector<Uint8> texels(16, 0x3C);
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, texels.data(), texels.size());
|
||||
EXPECT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
EXPECT_EQ(store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_r8")[15], 0x3C);
|
||||
}
|
||||
|
||||
// F2 LANE REPAIR, BYTELESS DECLARATIONS: a level whose format has no uncompressed size here
|
||||
// (TextureInternalFormat::Unknown - what the TextureView / TextureViewAlias / TextureUploadShape
|
||||
// split lanes declare) and an EMPTY level (glTexImage*D with a zero extent -
|
||||
// UnboundImageDescriptorScenario's incomplete default texture) are both legal DECLARATIONS: no
|
||||
// Fatal at the respecify, the level exists, and its byte bound is 0. A staged run into such a
|
||||
// level is then refused by its own name. Red-once: drop the LevelBound refusal and the same run
|
||||
// dies as `StagedTextureStore.CopyRunInto` instead (end 4 > bound 0), which the name check
|
||||
// below reads as red. ONE DEATH PER CASE, ServerLoopTest's ruling.
|
||||
#if !defined(_WIN32)
|
||||
TEST(StagedTextureStoreTest, ABytelessDeclaredLevelIsLegalAndRefusesARunByName) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(16, 1));
|
||||
const auto tex2D = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{1, 1, 1}, tex2D,
|
||||
static_cast<Uint32>(TextureInternalFormat::Unknown));
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 1, IntVec3{0, 0, 1}, tex2D,
|
||||
static_cast<Uint32>(TextureInternalFormat::RGBA8));
|
||||
ASSERT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 0));
|
||||
ASSERT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 1));
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key, kTex2DTarget, 0), 0u);
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key, kTex2DTarget, 1), 0u);
|
||||
|
||||
Vector<Uint8> texel(4, 0x5A);
|
||||
EXPECT_EXIT(store.AdoptRun(key, kTex2DTarget, 0, IntVec3{1, 1, 1}, 0, texel.data(), texel.size()),
|
||||
::testing::KilledBySignal(SIGABRT), ".*");
|
||||
const std::string log = ReadLog();
|
||||
EXPECT_NE(log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.LevelBound\"}"), std::string::npos)
|
||||
<< "the run died, but not as the byteless-level refusal; the log says: " << log;
|
||||
}
|
||||
#endif
|
||||
|
||||
// =====================================================================================
|
||||
// The production wiring - E-P5c gate #2 at unit scope
|
||||
// =====================================================================================
|
||||
@@ -582,11 +641,15 @@ TEST(StagedTextureProductionTest, TextureSubDataThroughTheRealOpsTableCopiesAndS
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
// RGBA8: the 64 bytes below are 4x4 texels of four bytes, and PH-4 bounds the run by the
|
||||
// declared level's w*h*d*bpp (a zero-initialised format is R8, one byte a texel).
|
||||
desc.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.Levels = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
DeclareTex2DLevel0(desc, 4, 4);
|
||||
|
||||
Vector<Uint8> src(64, 0xAB); // 4x4 texels, 4 bytes each
|
||||
MG_Pipe::MGPSubData rec{};
|
||||
@@ -649,6 +712,8 @@ TEST(StagedTextureProductionTest, HooklessTextureConsumerOwnsBytesAndScopedStora
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
// RGBA8 for the 64-byte 4x4 upload below (PH-4's w*h*d*bpp bound; zero would be R8).
|
||||
desc.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8);
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
@@ -672,8 +737,13 @@ TEST(StagedTextureProductionTest, HooklessTextureConsumerOwnsBytesAndScopedStora
|
||||
std::fill(src.begin(), src.end(), Uint8{0xDD});
|
||||
EXPECT_EQ(store.RequireLevelBytes(key, kTex2DTarget, 0, "hookless_source_poison")[0], 0xAB);
|
||||
|
||||
// Defining another mip must retain bytes already accepted for the first mip.
|
||||
// Defining another mip must retain bytes already accepted for the first mip. PH-4: the scope
|
||||
// carries that mip's EXACT extent (TextureEmit.h reads it off the frontend's level), so level 1
|
||||
// of a 4x4 base crosses as 2x2x1 rather than inheriting level 0's numbers.
|
||||
level.Level = 1;
|
||||
level.Width = 2;
|
||||
level.Height = 2;
|
||||
level.Depth = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, &level));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 1), IntVec3(2, 2, 1));
|
||||
EXPECT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
@@ -722,11 +792,13 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheHooklessApplierLandWhe
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8); // PH-4 bound: 4x4x4
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.Levels = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
DeclareTex2DLevel0(desc, 4, 4);
|
||||
|
||||
// A 4x4 RGBA8 level: 64 bytes, four bytes per texel, a 16-byte row pitch. The two runs are its
|
||||
// upper and lower halves, 32 bytes each.
|
||||
@@ -794,11 +866,13 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheRealTextureHookLandWhe
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8); // PH-4 bound: 4x4x4
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.Levels = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
DeclareTex2DLevel0(desc, 4, 4);
|
||||
|
||||
Vector<Uint8> upper(32, 0x77);
|
||||
Vector<Uint8> lower(32, 0x33);
|
||||
|
||||
Reference in New Issue
Block a user