mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Pipe, MG_Remote, MG_Backend] (P7): bind staged bytes to declared mip extents
This commit is contained in:
@@ -2734,12 +2734,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!store.CopiesIntoServerStorage()) return;
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(res);
|
||||
if (level != nullptr) {
|
||||
// ONE glTexImage*D redefined one level: it exists from here on, at the
|
||||
// derived extent (§1). NoteLevelDefined keeps a same-extent level's bytes.
|
||||
// ONE glTexImage*D redefined one level: its exact extent crossed with the
|
||||
// accepted scope carrier, including a legal noncanonical mutable mip.
|
||||
store.NoteLevelDefined(
|
||||
key, MG_Pipe::MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level,
|
||||
MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, level->Level));
|
||||
IntVec3{static_cast<Int>(level->Width), static_cast<Int>(level->Height),
|
||||
static_cast<Int>(level->Depth)},
|
||||
desc.Target, desc.InternalFormat);
|
||||
return;
|
||||
}
|
||||
// A whole-resource redefinition: every level's old coordinate system is gone.
|
||||
@@ -2754,7 +2755,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
store.NoteLevelDefined(
|
||||
key, static_cast<Uint16>(uploadTarget), static_cast<Uint16>(levelIndex),
|
||||
MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, levelIndex));
|
||||
desc.Depth, levelIndex),
|
||||
desc.Target, desc.InternalFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3009,17 +3011,33 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// refusal, because MOBILEGL_ASSERT compiles out at INFO and a resource that silently
|
||||
// stops being twinned is the failure mode the refusal exists to replace.
|
||||
if (res.Slot >= BackendBufferResourceTable::kMaxHandleSlot) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
|
||||
"buffer twin request named slot %u, past this table's %u bound",
|
||||
res.Slot, BackendBufferResourceTable::kMaxHandleSlot);
|
||||
#else
|
||||
MGLOG_E_ONCE("MGPipe: resource handle slot %u is past the backend table's %u bound - "
|
||||
"refusing to twin it",
|
||||
res.Slot, BackendBufferResourceTable::kMaxHandleSlot);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
const Uint32 liveGen = g_backendBufferResources.LiveGenAt(res.Slot);
|
||||
if (liveGen != 0 && liveGen > res.Gen) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
|
||||
"buffer twin request named generation %u at slot %u, behind live generation %u",
|
||||
res.Gen, res.Slot, liveGen);
|
||||
#else
|
||||
MGLOG_E_ONCE("MGPipe: resource handle {%u, %u} names a generation BEHIND the live twin's "
|
||||
"%u - refusing rather than dropping the incumbent's driver storage",
|
||||
res.Slot, res.Gen, liveGen);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
auto& twin = g_backendBufferResources.GetOrCreate(res);
|
||||
if (!twin) {
|
||||
@@ -4329,13 +4347,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool PipeTwinHandleIsAdoptable(Registry& registry, MG_Pipe::MGPipeHandle handle, const char* kindName) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return false;
|
||||
if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
|
||||
"%s handle slot %u is past this table's %u bound",
|
||||
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
|
||||
#else
|
||||
MGLOG_E_ONCE("MGPipe: %s handle slot %u is past the backend table's %u bound - "
|
||||
"refusing to twin it",
|
||||
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
const Uint32 liveGen = registry.LiveGenAt(handle.Slot);
|
||||
if (liveGen != 0 && liveGen > handle.Gen) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
|
||||
"%s handle {%u, %u} names generation behind live generation %u",
|
||||
kindName, handle.Slot, handle.Gen, liveGen);
|
||||
#else
|
||||
// SlotTables.h:301-321: forward is a recycle and resets the twin, BACKWARD is
|
||||
// refused, because adopting it would release the incumbent LIVE twin's driver
|
||||
// id and then stamp the slot back to the dead object's generation, after which
|
||||
@@ -4347,6 +4380,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
"object",
|
||||
kindName, handle.Slot, handle.Gen, liveGen);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -479,9 +479,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendPtr* GetOrCreateByHandle(MG_Pipe::MGPipeHandle handle) {
|
||||
if (!EsprytSlotTablesEnabled()) return nullptr;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
|
||||
if (handle.Slot >= SlotTable::kMaxHandleSlot) return nullptr;
|
||||
if (handle.Slot >= SlotTable::kMaxHandleSlot) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
|
||||
"GetOrCreateByHandle named slot %u, past this table's %u bound",
|
||||
handle.Slot, SlotTable::kMaxHandleSlot);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
const Uint32 liveGen = m_slotTable.LiveGenAt(handle.Slot);
|
||||
if (liveGen != 0 && liveGen > handle.Gen) return nullptr;
|
||||
if (liveGen != 0 && liveGen > handle.Gen) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
|
||||
"GetOrCreateByHandle named generation %u at slot %u, behind live generation %u",
|
||||
handle.Gen, handle.Slot, liveGen);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
return &m_slotTable.GetOrCreate(handle);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Pipe/PipeSessionFail.h>
|
||||
#endif
|
||||
|
||||
// Espryt 0b, the first Track H slice: the DENSE, {slot, gen}-keyed twin table that replaces
|
||||
// StateBackendObjectRegistry's UnorderedMap<StateObject*, Entry>.
|
||||
@@ -345,9 +348,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// already orders of magnitude past any real GL object count, while a corrupt 32-bit
|
||||
// slot asks for a four-billion-entry resize.
|
||||
if (handle.Slot >= kMaxHandleSlot) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// PH-2: handle.Slot is peer supplied on the disaggregated arm. Release builds
|
||||
// must publish a named protocol fault instead of silently returning m_nullTwin.
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
|
||||
"GetOrCreate(handle) named slot %u, past this table's %u bound",
|
||||
handle.Slot, kMaxHandleSlot);
|
||||
#else
|
||||
MOBILEGL_ASSERT(false, "GetOrCreate(handle) named slot %u, past this table's %u bound",
|
||||
handle.Slot, kMaxHandleSlot);
|
||||
return m_nullTwin;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Same arming as the minting overload, and for the same reason: twin creation is
|
||||
@@ -369,11 +382,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// below already gives the same input.
|
||||
Entry& entry = EntryAt(handle.Slot);
|
||||
if (entry.Live && entry.Gen > handle.Gen) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// PH-2: a stale peer generation must not silently shed the incumbent twin in a
|
||||
// release server. The funnel names the exact identity fault and notifies the peer.
|
||||
MG_Pipe::MGPipeSessionFail(
|
||||
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
|
||||
"GetOrCreate(handle) named generation %u at slot %u, behind live generation %u",
|
||||
handle.Gen, handle.Slot, entry.Gen);
|
||||
#else
|
||||
MOBILEGL_ASSERT(false,
|
||||
"GetOrCreate(handle) named generation %u at slot %u, which is BEHIND "
|
||||
"the live entry's %u - refusing rather than destroying the incumbent",
|
||||
handle.Gen, handle.Slot, entry.Gen);
|
||||
return m_nullTwin;
|
||||
#endif
|
||||
}
|
||||
if (entry.Live && entry.Gen != handle.Gen) entry.backend.reset();
|
||||
entry.Gen = handle.Gen;
|
||||
|
||||
@@ -901,6 +901,18 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPRespecifiedLevel key{};
|
||||
key.UploadTarget = packedTarget;
|
||||
key.Level = static_cast<Uint16>(firstLevel + i);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto* mipmap = MG_State::GLState::AsMipmapTexture(&texture);
|
||||
if (mipmap == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: a per-level texture respecify had no mipmap storage; it was not emitted");
|
||||
return;
|
||||
}
|
||||
const IntVec3 exactLevelExtent = mipmap->GetMipmapTexelSize(
|
||||
static_cast<TextureUploadTarget>(uploadTarget), key.Level);
|
||||
key.Width = static_cast<Uint32>(exactLevelExtent.x());
|
||||
key.Height = static_cast<Uint32>(exactLevelExtent.y());
|
||||
key.Depth = static_cast<Uint32>(exactLevelExtent.z());
|
||||
#endif
|
||||
accepted = RespecifyOnce(texture, handle, entry, desc, &key, viewOf, bufferHandle, bufOffset,
|
||||
bufSize);
|
||||
if (!accepted) break;
|
||||
|
||||
@@ -342,16 +342,19 @@ namespace MobileGL::MG_Pipe {
|
||||
// It exists because the applier ALREADY takes that scope - as a trailing
|
||||
// const MGPRespecifiedLevel* (PipeApply.h:792-795) - and MGPResourceDesc could not
|
||||
// express it, so it was the second of resource_respecify's two companions with no wire
|
||||
// carrier. The difference is not cosmetic: a null scope drops EVERY pending upload,
|
||||
// carrier. PH-4 extends that carrier with exact W/H/D for the named level, which the
|
||||
// server validates before staging bytes. The difference is not cosmetic: a null scope drops EVERY pending upload,
|
||||
// because every level's coordinate system has just been replaced, while a per-level
|
||||
// one drops only that key. Crossing without the scope would make every mutable
|
||||
// per-level glTexImage*D on the far side look like a whole-resource redefinition and
|
||||
// silently eat the texels of every other level - exactly the loss the server-side
|
||||
// pending-upload set exists to prevent.
|
||||
//
|
||||
// READ IT THROUGH THE THREE HELPERS BELOW, never by touching the fields: the
|
||||
// presence byte and the pair are one value in three pieces, and an open-coded reader
|
||||
// that forgets the presence byte reads level 0 of upload target 0 as a real scope.
|
||||
// READ IT THROUGH THE HELPERS BELOW, never by touching the fields: the presence byte,
|
||||
// target/level pair and exact per-level extent form one value. For a named image level,
|
||||
// BufOffset/BufSize temporarily carry W/H/D; the target makes this safe because a
|
||||
// texture-buffer range cannot legally name a mip level. The applier clears that carrier
|
||||
// before storing the descriptor.
|
||||
//
|
||||
// NOT STORAGE-DEFINING, and not metadata either: it does not describe the resource at
|
||||
// all, it describes what this CALL replaces. MGPipeResourceRespecifyNeedsAck and the
|
||||
@@ -378,7 +381,9 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint16 RespecifiedLevel;
|
||||
MGPipeHandle ViewOf; // storage owner for a texture view
|
||||
MGPipeHandle BufferForTexBuffer; // texture-buffer backing store
|
||||
Uint64 BufOffset, BufSize; // kWholeBuffer == ~0, resolved live
|
||||
// TexBuffer's backing range; on a named non-buffer mip respecify the same words carry
|
||||
// exact width/height/depth until the applier accepts and normalizes the descriptor.
|
||||
Uint64 BufOffset, BufSize;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPResourceDesc, 88);
|
||||
// The scope fields went into the two existing pads, so the descriptor did not grow and this
|
||||
@@ -401,9 +406,9 @@ namespace MobileGL::MG_Pipe {
|
||||
return desc.HasRespecifiedLevel == 0;
|
||||
}
|
||||
|
||||
// The single (uploadTarget, level) a per-level respecify replaces. Reading either half of a
|
||||
// whole-resource descriptor is a caller error; both answer 0 so that a misuse is at least
|
||||
// deterministic rather than whatever the pad happened to hold.
|
||||
// The single (uploadTarget, level, width, height, depth) a per-level respecify replaces.
|
||||
// Reading any component of a whole-resource descriptor is a caller error; helpers answer
|
||||
// zero so a misuse is deterministic rather than whatever the pad/range held.
|
||||
inline constexpr Uint16 MGPipeRespecifiedUploadTargetOf(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? Uint16(0) : desc.RespecifiedUploadTarget;
|
||||
}
|
||||
@@ -411,16 +416,49 @@ namespace MobileGL::MG_Pipe {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? Uint16(0) : desc.RespecifiedLevel;
|
||||
}
|
||||
|
||||
// The two writers. A producer sets the scope with one call so the presence byte cannot be
|
||||
// left behind, and clears it with the other; a descriptor built by value-initialization is
|
||||
// already whole-resource, which is the safe default and the only one P5 produces.
|
||||
// A producer sets the scope and its exact mutable-level extent with one call so the
|
||||
// presence byte cannot be left behind, and clears scope with the other. A descriptor built
|
||||
// by value-initialization is already whole-resource, which is the safe default.
|
||||
inline constexpr Uint32 MGPipeRespecifiedWidthOf(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? 0u : static_cast<Uint32>(desc.BufOffset >> 32);
|
||||
}
|
||||
inline constexpr Uint32 MGPipeRespecifiedHeightOf(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? 0u : static_cast<Uint32>(desc.BufOffset);
|
||||
}
|
||||
inline constexpr Uint32 MGPipeRespecifiedDepthOf(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ? 0u : static_cast<Uint32>(desc.BufSize);
|
||||
}
|
||||
inline constexpr Bool MGPipeRespecifiedExtentCarrierIsCanonical(const MGPResourceDesc& desc) {
|
||||
return MGPipeRespecifyIsWholeResource(desc) ||
|
||||
((desc.BufSize >> 32) == 0 && MGPipeRespecifiedWidthOf(desc) != 0 &&
|
||||
MGPipeRespecifiedHeightOf(desc) != 0 && MGPipeRespecifiedDepthOf(desc) != 0);
|
||||
}
|
||||
|
||||
inline constexpr void MGPipeSetRespecifiedLevel(MGPResourceDesc& desc, Uint16 uploadTarget,
|
||||
Uint16 level) {
|
||||
Uint16 level, Uint32 width = 0, Uint32 height = 0,
|
||||
Uint32 depth = 0) {
|
||||
desc.HasRespecifiedLevel = 1;
|
||||
desc.RespecifiedUploadTarget = uploadTarget;
|
||||
desc.RespecifiedLevel = level;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Non-buffer texture targets leave this descriptor range unused. A named-level
|
||||
// respecify repurposes it as exact W/H/D; TexBuffer cannot legally name a mip level.
|
||||
desc.BufOffset = (static_cast<Uint64>(width) << 32) | static_cast<Uint64>(height);
|
||||
desc.BufSize = depth;
|
||||
#else
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)depth;
|
||||
#endif
|
||||
}
|
||||
inline constexpr void MGPipeClearRespecifiedLevel(MGPResourceDesc& desc) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (desc.HasRespecifiedLevel != 0 &&
|
||||
desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer)) {
|
||||
desc.BufOffset = 0;
|
||||
desc.BufSize = 0;
|
||||
}
|
||||
#endif
|
||||
desc.HasRespecifiedLevel = 0;
|
||||
desc.RespecifiedUploadTarget = 0;
|
||||
desc.RespecifiedLevel = 0;
|
||||
|
||||
@@ -1138,18 +1138,22 @@ namespace MobileGL::MG_Pipe {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(desc.Resource);
|
||||
const auto define = [&](Uint16 target, Uint16 mip) {
|
||||
store.NoteLevelDefined(key, target, mip, MG_Remote::Server::StagedTextureMipExtent(
|
||||
desc.Target, desc.Width, desc.Height, desc.Depth, mip));
|
||||
const auto define = [&](Uint16 target, Uint16 mip, const IntVec3& extent) {
|
||||
store.NoteLevelDefined(key, target, mip, extent, desc.Target, desc.InternalFormat);
|
||||
};
|
||||
if (level) {
|
||||
define(MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level);
|
||||
define(MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level,
|
||||
IntVec3{static_cast<Int>(level->Width), static_cast<Int>(level->Height),
|
||||
static_cast<Int>(level->Depth)});
|
||||
return;
|
||||
}
|
||||
store.ResetLevels(key);
|
||||
if (!desc.Immutable || !desc.Levels) return;
|
||||
const auto defineChain = [&](TextureUploadTarget target) {
|
||||
for (Uint32 mip = 0; mip < desc.Levels; ++mip) define(static_cast<Uint16>(target), static_cast<Uint16>(mip));
|
||||
for (Uint32 mip = 0; mip < desc.Levels; ++mip)
|
||||
define(static_cast<Uint16>(target), static_cast<Uint16>(mip),
|
||||
MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width,
|
||||
desc.Height, desc.Depth, mip));
|
||||
};
|
||||
switch (static_cast<MGPipeResourceTarget>(desc.Target)) {
|
||||
case MGPipeResourceTarget::Tex1D: defineChain(TextureUploadTarget::Texture1D); break;
|
||||
@@ -1975,6 +1979,17 @@ namespace MobileGL::MG_Pipe {
|
||||
// ================================================================================
|
||||
|
||||
Bool MGPipeApplyResourceCreate(const MGPResourceDesc& desc) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (!MGPipeRespecifyIsWholeResource(desc)) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceCreate.RespecifyScope\"} - "
|
||||
"resource_create cannot carry a per-level respecify extent");
|
||||
}
|
||||
if (desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer) &&
|
||||
(desc.BufOffset != 0 || desc.BufSize != 0)) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceCreate.BufferRange\"} - "
|
||||
"only a texture-buffer resource may carry BufOffset/BufSize");
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(desc.Resource.Slot >= kMGPipeFirstAllocatableSlot,
|
||||
"resource_create named the reserved slot 0");
|
||||
if (desc.Resource.Slot < kMGPipeFirstAllocatableSlot) return false;
|
||||
@@ -2055,6 +2070,25 @@ namespace MobileGL::MG_Pipe {
|
||||
if (record == nullptr) return false;
|
||||
PinNoLiveHostWrites(*record, desc.Resource, "resource_respecify");
|
||||
PinRespecifyScopeCoversItsDeclaration(desc, level, desc.Resource, "resource_respecify");
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (level != nullptr &&
|
||||
(desc.Target == kMGPipeResourceTargetBuffer ||
|
||||
desc.Target == static_cast<Uint8>(MGPipeResourceTarget::Renderbuffer) ||
|
||||
desc.Target == static_cast<Uint8>(MGPipeResourceTarget::TexBuffer))) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.Target\"} - "
|
||||
"this resource target cannot name a texture mip level");
|
||||
}
|
||||
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");
|
||||
}
|
||||
if (level == nullptr && desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer) &&
|
||||
(desc.BufOffset != 0 || desc.BufSize != 0)) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.BufferRange\"} - "
|
||||
"a whole non-buffer resource may not carry BufOffset/BufSize");
|
||||
}
|
||||
#endif
|
||||
|
||||
// IS THIS A REDEFINITION AT ALL? Asked BEFORE the descriptor is replaced, because the
|
||||
// stored one is the only thing there is to compare against (ID-18 M4). See
|
||||
@@ -2069,6 +2103,14 @@ namespace MobileGL::MG_Pipe {
|
||||
// asking a frontend object for them. A metadata update replaces it too - that is how
|
||||
// the mask arrives - and by construction only the non-storage fields differ.
|
||||
record->Desc = desc;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (level != nullptr && desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer)) {
|
||||
// Scope and BufOffset/BufSize are transient respecify carriers, not resource state.
|
||||
MGPipeClearRespecifiedLevel(record->Desc);
|
||||
record->Desc.BufOffset = 0;
|
||||
record->Desc.BufSize = 0;
|
||||
}
|
||||
#endif
|
||||
// THE SERIAL MOVES EITHER WAY, and for a metadata update it is the entire publication:
|
||||
// the twin re-derives its storage flags from the new mask at its next sync and decides
|
||||
// for itself whether the backend needs a recreate.
|
||||
@@ -2107,14 +2149,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// glTexSubImage2D and the sync that consumes it; eating those texels there would be
|
||||
// C1's bug with a different trigger, and just as silent.
|
||||
//
|
||||
// - A NAMED LEVEL IS DROPPED WHETHER OR NOT THE DESCRIPTOR MOVED (P4a final review
|
||||
// C-1, refining wire's W11 clause). The level pointer is the CALLER's statement that
|
||||
// it reallocated that level, and the descriptor cannot contradict it: a non-base
|
||||
// level redefined at a new size moves no descriptor field at all (the descriptor
|
||||
// carries the base extent and the level count), so "identical storage fields" says
|
||||
// nothing about that level's coordinate system, and a box kept against the old
|
||||
// level would be uploaded past the end of the new one. The client's mask republish
|
||||
// passes null, so this arm can never eat a standing upload on its behalf.
|
||||
// - A NAMED LEVEL IS DROPPED WHETHER OR NOT THE base descriptor moved (P4a final
|
||||
// review C-1). The companion scope now carries that level's exact W/H/D as well as
|
||||
// its (uploadTarget, level) key, so the texture twin and staged-byte store update the
|
||||
// same coordinate system. A metadata-only mask republish passes null and therefore
|
||||
// cannot consume a standing upload on its behalf.
|
||||
//
|
||||
// A buffer never has a pending upload at all, so all three arms are inert for P3a's
|
||||
// half - which is also why a buffer is never classified as metadata-only (below).
|
||||
|
||||
@@ -1141,8 +1141,33 @@ namespace MobileGL::MG_Pipe {
|
||||
struct MGPRespecifiedLevel {
|
||||
Uint16 UploadTarget = 0;
|
||||
Uint16 Level = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P7 PH-4: mutable mip dimensions do not follow from the base descriptor. These fields
|
||||
// are carried by the split resource_respecify record; the pull helper remains 4 bytes.
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Depth = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
inline MGPRespecifiedLevel MGPipeMakeRespecifiedLevel(Uint16 uploadTarget, Uint16 level,
|
||||
Uint32 width = 0, Uint32 height = 0,
|
||||
Uint32 depth = 0) {
|
||||
MGPRespecifiedLevel value{};
|
||||
value.UploadTarget = uploadTarget;
|
||||
value.Level = level;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
value.Width = width;
|
||||
value.Height = height;
|
||||
value.Depth = depth;
|
||||
#else
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)depth;
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
||||
// THE THREE ACCEPTANCE RETURNS, AND WHY ALL THREE (ID-18 M3, clientfb review M3). D-D5
|
||||
// step 1 says the client clears a level's dirty flags "for the levels whose record the
|
||||
// applier ACCEPTED", and the emitter cannot answer that for itself: an `if constexpr` that
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
inline constexpr Uint64 kMGPipeWireMemberLayoutDigest = WireMemberLayoutDigest(
|
||||
kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember));
|
||||
// Bump when a fixed-layout pipe carrier keeps its bytes but changes their meaning. The
|
||||
// mutable-level extent carrier reuses BufOffset/BufSize only on named image respecifies.
|
||||
inline constexpr Uint32 kMGPipeResourceRespecifyExtentCarrierRevision = 1;
|
||||
|
||||
constexpr Uint64 WireCatalogueDigest() {
|
||||
Uint64 hash = 1469598103934665603ull;
|
||||
|
||||
@@ -476,7 +476,14 @@ namespace MobileGL::MG_Remote {
|
||||
inputs.ProgramArtifactsCodecVersion = MG_State::GLState::kProgramArtifactsCodecVersion;
|
||||
inputs.ProgramArtifactsSchema = MG_State::GLState::ProgramArtifactsSchemaFingerprint();
|
||||
inputs.OpCount = static_cast<Uint64>(MG_Pipe::MGPWireOp::kOpCount);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
inputs.ControlSchemaRevision =
|
||||
(static_cast<Uint64>(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) |
|
||||
MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision;
|
||||
#else
|
||||
// The pull build has no respecify extent carrier; preserve its pre-P7 wire fingerprint.
|
||||
inputs.ControlSchemaRevision = MOBILEGL_PROTOCOL_CONTROL_REVISION;
|
||||
#endif
|
||||
inputs.AbiVersion = MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR);
|
||||
inputs.PointerBits = sizeof(void*) * 8;
|
||||
const Uint32 endian = 1;
|
||||
|
||||
@@ -421,13 +421,17 @@ namespace MobileGL::MG_Remote::Client {
|
||||
"resource_subdata; a caller that still passes it has bytes nothing will "
|
||||
"carry");
|
||||
}
|
||||
// The scope rides in the descriptor's own pads (CONTRACT-P5 table 1 row 19b, LANDED)
|
||||
// and is written only through MGPipeSetRespecifiedLevel - three fields are one
|
||||
// value, and an open-coded writer that forgets the presence byte says "level 0 of
|
||||
// upload target 0" where it meant "the whole resource".
|
||||
// Scope and exact mutable mip extent travel together through the descriptor helper.
|
||||
// It temporarily reuses BufOffset/BufSize on non-buffer image targets; the server
|
||||
// checks and clears that carrier before persisting the resource descriptor.
|
||||
MG_Pipe::MGPResourceDesc record = *desc;
|
||||
if (level != nullptr) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeSetRespecifiedLevel(record, level->UploadTarget, level->Level,
|
||||
level->Width, level->Height, level->Depth);
|
||||
#else
|
||||
MG_Pipe::MGPipeSetRespecifiedLevel(record, level->UploadTarget, level->Level);
|
||||
#endif
|
||||
} else {
|
||||
MG_Pipe::MGPipeClearRespecifiedLevel(record);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "ServerSession.h"
|
||||
#include "../Transport/ReplySlot.h"
|
||||
#include "StagedTextureStore.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
@@ -87,6 +88,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
ReleaseFences();
|
||||
}
|
||||
m_backend = backend;
|
||||
if (backend != nullptr) {
|
||||
const auto& limits = backend->GetDynamicParameters();
|
||||
ServerStagedTexture().SetDeviceLimits(
|
||||
limits.MaxTextureSize, limits.Max3DTextureSize, limits.MaxCubeMapTextureSize,
|
||||
limits.MaxArrayTextureLayers, limits.MaxTextureBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
const MG_Backend::GlobalBackendFunctionsTable* ServerVerbSink::Table(const char* verb) const {
|
||||
|
||||
@@ -105,12 +105,14 @@
|
||||
// rather than left to StagedShadow.h's copy of the same include.
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_Util/Math/VectorTypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <limits>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
@@ -125,6 +127,97 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// TextureCubeMapArray shrink x and y; every other target shrinks all three. This is the
|
||||
// mip chain's definition (MG_State's IsMipmapCompleteForFilter applies the same split,
|
||||
// TextureObject.cpp:705-734), so two honest ends compute the same number.
|
||||
struct StagedTextureDeviceLimits {
|
||||
Int MaxTextureSize = 16384;
|
||||
Int MaxRectangleTextureSize = 16384;
|
||||
Int Max3DTextureSize = 2048;
|
||||
Int MaxCubeMapTextureSize = 16384;
|
||||
Int MaxArrayTextureLayers = 2048;
|
||||
Int MaxTextureBufferSize = 65536;
|
||||
};
|
||||
|
||||
// Multisample and buffer textures have no legal glTexSubImage producer. Keep their
|
||||
// definedness in the store, but never accept a byte-backed staged level for them.
|
||||
inline Bool StagedTextureTargetSupportsSubData(Uint8 resourceTarget) {
|
||||
switch (static_cast<MG_Pipe::MGPipeResourceTarget>(resourceTarget)) {
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1D:
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2D:
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex3D:
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1DArray:
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DArray:
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCube:
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCubeArray:
|
||||
case MG_Pipe::MGPipeResourceTarget::TexRect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline Bool StagedTextureExtentWithinDeviceLimits(Uint8 resourceTarget, const IntVec3& extent,
|
||||
const StagedTextureDeviceLimits& limits) {
|
||||
if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return false;
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(resourceTarget);
|
||||
switch (target) {
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1D:
|
||||
return extent.x() <= limits.MaxTextureSize && extent.y() == 1 && extent.z() == 1;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2D:
|
||||
return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxTextureSize &&
|
||||
extent.z() == 1;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexRect:
|
||||
return extent.x() <= limits.MaxRectangleTextureSize &&
|
||||
extent.y() <= limits.MaxRectangleTextureSize && extent.z() == 1;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex3D:
|
||||
return extent.x() <= limits.Max3DTextureSize && extent.y() <= limits.Max3DTextureSize &&
|
||||
extent.z() <= limits.Max3DTextureSize;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1DArray:
|
||||
return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxArrayTextureLayers &&
|
||||
extent.z() == 1;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DArray:
|
||||
return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxTextureSize &&
|
||||
extent.z() <= limits.MaxArrayTextureLayers;
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray:
|
||||
return false; // defined levels only; multisample subimage records are never legal
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCube:
|
||||
return extent.x() <= limits.MaxCubeMapTextureSize &&
|
||||
extent.y() <= limits.MaxCubeMapTextureSize && extent.z() == 1;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexCubeArray:
|
||||
return extent.x() <= limits.MaxCubeMapTextureSize &&
|
||||
extent.y() <= limits.MaxCubeMapTextureSize && extent.z() <= limits.MaxArrayTextureLayers;
|
||||
case MG_Pipe::MGPipeResourceTarget::TexBuffer:
|
||||
return false; // texture buffers have no glTexSubImage entry point
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The descriptor's InternalFormat is already the server's uncompressed storage fallback.
|
||||
// Compute the maximum byte coordinate of one declared level without trusting upload bytes.
|
||||
inline Bool StagedTextureDeclaredLevelByteBound(Uint8 resourceTarget, Uint32 internalFormat,
|
||||
const IntVec3& extent,
|
||||
const StagedTextureDeviceLimits& limits,
|
||||
Uint64* outBound) {
|
||||
if (outBound == nullptr || !StagedTextureTargetSupportsSubData(resourceTarget) ||
|
||||
!StagedTextureExtentWithinDeviceLimits(resourceTarget, extent, limits)) return false;
|
||||
const SizeT bytesPerTexel = MG_Util::GetSizedInternalFormatSizeInBytes(
|
||||
static_cast<TextureInternalFormat>(internalFormat));
|
||||
if (bytesPerTexel == 0) return false;
|
||||
const Uint64 x = static_cast<Uint64>(extent.x());
|
||||
const Uint64 y = static_cast<Uint64>(extent.y());
|
||||
const Uint64 z = static_cast<Uint64>(extent.z());
|
||||
const Uint64 bpp = static_cast<Uint64>(bytesPerTexel);
|
||||
constexpr Uint64 max = std::numeric_limits<Uint64>::max();
|
||||
if (x > max / y) return false;
|
||||
const Uint64 xy = x * y;
|
||||
if (xy > max / z) return false;
|
||||
const Uint64 texels = xy * z;
|
||||
if (texels > max / bpp) return false;
|
||||
const Uint64 bytes = texels * bpp;
|
||||
if (bytes > static_cast<Uint64>(std::numeric_limits<SizeT>::max())) return false;
|
||||
*outBound = bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Int StagedTextureShrinkingAxisCount(Uint8 pipeResourceTarget) {
|
||||
switch (static_cast<MG_Pipe::MGPipeResourceTarget>(pipeResourceTarget)) {
|
||||
case MG_Pipe::MGPipeResourceTarget::Tex1DArray:
|
||||
@@ -205,6 +298,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
explicit StagedTextureStore(Bool copies) : m_copies(copies) {}
|
||||
|
||||
Bool CopiesIntoServerStorage() const { return m_copies; }
|
||||
void SetDeviceLimits(Int maxTextureSize, Int max3DTextureSize, Int maxCubeMapTextureSize,
|
||||
Int maxArrayTextureLayers, Int maxTextureBufferSize) {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_deviceLimits = {maxTextureSize, std::min<Int>(16384, maxTextureSize), max3DTextureSize,
|
||||
maxCubeMapTextureSize, maxArrayTextureLayers, maxTextureBufferSize};
|
||||
}
|
||||
|
||||
// The wire handle the record carried, tagged so it can never alias a twin address.
|
||||
// Slot and Gen are the client allocator's identity for one live object, so a recycled
|
||||
@@ -236,20 +335,23 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (!m_copies) return nullptr;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
LevelShadow& shadow = m_shadows[key].Levels[PackLevel(uploadTarget, level)];
|
||||
RequireDeclaredLevel(shadow, key, uploadTarget, level, extent);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// A run can be the first piece of a redefined level. Old coverage
|
||||
// belongs to the previous coordinate system even when the new run
|
||||
// happens to touch its old range; retaining it invents bytes that
|
||||
// the current definition never supplied.
|
||||
// the current definition never supplied (B4). The redefinition itself
|
||||
// is NoteLevelDefined's, which drops the old bytes and coverage on an
|
||||
// extent move, and RequireDeclaredLevel above refuses a run whose
|
||||
// extent disagrees with that declaration (PH-4) - so this reset is the
|
||||
// belt that keeps B4's rule if that refusal is ever relaxed.
|
||||
if (!shadow.Defined || shadow.Extent != extent) {
|
||||
shadow.Bytes.clear();
|
||||
shadow.Covered.clear();
|
||||
}
|
||||
#endif
|
||||
shadow.Extent = extent;
|
||||
shadow.Defined = true;
|
||||
shadow.GpuDirty = false;
|
||||
return CopyRunInto(shadow, imageOffset, bytes, byteSize);
|
||||
return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize);
|
||||
}
|
||||
|
||||
// THE WHOLE-LEVEL SPELLING: the run is the level's COMPLETE current content, beginning at
|
||||
@@ -263,25 +365,39 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (!m_copies) return nullptr;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
LevelShadow& shadow = m_shadows[key].Levels[PackLevel(uploadTarget, level)];
|
||||
shadow.Extent = extent;
|
||||
shadow.Defined = true;
|
||||
RequireDeclaredLevel(shadow, key, uploadTarget, level, extent);
|
||||
shadow.GpuDirty = false;
|
||||
shadow.Bytes.clear();
|
||||
shadow.Covered.clear();
|
||||
return CopyRunInto(shadow, 0, bytes, byteSize);
|
||||
return CopyRunInto(shadow, key, uploadTarget, level, 0, bytes, byteSize);
|
||||
}
|
||||
|
||||
// A storage-defining respecify named this level: it EXISTS from here on, at this
|
||||
// extent (null-data glTexImage*D, a generated level). An extent move redefines the
|
||||
// level's coordinate system, so the bytes and the dirty mark of the old one go with
|
||||
// it; an extent-restating one keeps them, which is the same answer the driver gives
|
||||
// (a same-shape redefinition that carries no new upload leaves the old texels in
|
||||
// place - undefined content is allowed to be the old content).
|
||||
void NoteLevelDefined(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent) {
|
||||
// A storage-defining respecify named this level: it EXISTS from here on, at the exact
|
||||
// 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.
|
||||
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)) {
|
||||
if (!m_copies) return;
|
||||
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;
|
||||
if (MG_Util::GetSizedInternalFormatSizeInBytes(
|
||||
static_cast<TextureInternalFormat>(internalFormat)) == 0 || !validExtent ||
|
||||
(supportsSubData && !StagedTextureDeclaredLevelByteBound(
|
||||
resourceTarget, internalFormat, extent, m_deviceLimits, &byteBound))) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.NoteLevelDefined\"} - "
|
||||
"the accepted respecify has no valid server-bounded texture level "
|
||||
"(key=%llu, target=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
extent.x(), extent.y(), extent.z(), internalFormat);
|
||||
}
|
||||
LevelShadow& shadow = m_shadows[key].Levels[PackLevel(uploadTarget, level)];
|
||||
if (!shadow.Defined || shadow.Extent != extent) {
|
||||
if (!shadow.Defined || shadow.Extent != extent || shadow.InternalFormat != internalFormat ||
|
||||
shadow.ResourceTarget != resourceTarget || shadow.DeclaredByteBound != byteBound) {
|
||||
shadow.Bytes.clear();
|
||||
// The covered set is a statement about THIS coordinate system, so it goes with
|
||||
// the bytes it described.
|
||||
@@ -289,6 +405,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
shadow.GpuDirty = false;
|
||||
}
|
||||
shadow.Extent = extent;
|
||||
shadow.InternalFormat = internalFormat;
|
||||
shadow.ResourceTarget = resourceTarget;
|
||||
shadow.DeclaredByteBound = byteBound; // zero for targets without glTexSubImage
|
||||
shadow.Defined = true;
|
||||
m_any.store(true, std::memory_order_release);
|
||||
}
|
||||
@@ -340,8 +459,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (!m_copies) return nullptr;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
|
||||
if (shadow != nullptr && shadow->Defined && !shadow->Bytes.empty() &&
|
||||
StagedShadowStore::CoverageHas(shadow->Covered, 0, shadow->Bytes.size())) {
|
||||
if (shadow != nullptr && shadow->Defined && shadow->DeclaredByteBound != 0 &&
|
||||
shadow->Bytes.size() == shadow->DeclaredByteBound &&
|
||||
StagedShadowStore::CoverageHas(shadow->Covered, 0,
|
||||
static_cast<SizeT>(shadow->DeclaredByteBound))) {
|
||||
return shadow->Bytes.data();
|
||||
}
|
||||
// Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0).
|
||||
@@ -363,12 +484,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool IsCovered(Uint64 key, Uint16 uploadTarget, Uint16 level) const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
|
||||
if (shadow == nullptr || !shadow->Defined || shadow->Bytes.empty()) return false;
|
||||
// THE WHOLE LEVEL, not merely "some bytes": a level assembled out of stage-chunk
|
||||
// pieces is covered only once every piece has landed, and both readers of this
|
||||
// answer then read the image whole - the image-promotion merge and Magma's
|
||||
// unbacked-level readback.
|
||||
return StagedShadowStore::CoverageHas(shadow->Covered, 0, shadow->Bytes.size());
|
||||
if (shadow == nullptr || !shadow->Defined || shadow->DeclaredByteBound == 0 ||
|
||||
shadow->Bytes.size() != shadow->DeclaredByteBound) return false;
|
||||
// THE WHOLE LEVEL, not merely the currently assembled prefix: all runs must cover
|
||||
// the server-derived bound before a reader can treat the level as complete.
|
||||
return StagedShadowStore::CoverageHas(
|
||||
shadow->Covered, 0, static_cast<SizeT>(shadow->DeclaredByteBound));
|
||||
}
|
||||
// How many runs this level's coverage is made of: 1 for a whole-level adoption, one per
|
||||
// piece for a level cut into stage-chunk slabs. Diagnostics for the unit cases, so a
|
||||
@@ -406,6 +527,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (shadow == nullptr || !shadow->Defined) return 0;
|
||||
return shadow->Bytes.size();
|
||||
}
|
||||
Uint64 LevelDeclaredByteBound(Uint64 key, Uint16 uploadTarget, Uint16 level) const {
|
||||
if (!m_any.load(std::memory_order_acquire)) return 0;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const LevelShadow* shadow = FindLevel(key, uploadTarget, level);
|
||||
return shadow == nullptr || !shadow->Defined ? 0 : shadow->DeclaredByteBound;
|
||||
}
|
||||
Bool HasShadow(Uint64 key) const {
|
||||
if (!m_any.load(std::memory_order_acquire)) return false;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
@@ -436,6 +563,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// adopted for this level: a gap in it is a piece that never arrived, which is the
|
||||
// Fatal below (StagedShadowStore's coverage rules, same helpers).
|
||||
Vector<Range1D> Covered;
|
||||
Uint32 InternalFormat = 0;
|
||||
Uint8 ResourceTarget = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Count);
|
||||
Uint64 DeclaredByteBound = 0;
|
||||
Bool Defined = false;
|
||||
Bool GpuDirty = false;
|
||||
};
|
||||
@@ -447,15 +577,69 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// hold the run, place it, and widen the covered set by exactly its range. An empty run
|
||||
// (a level defined with no bytes, or a null pointer) covers nothing - emptiness is the
|
||||
// coverage answer for such a level, not an error.
|
||||
const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 imageOffset, const void* bytes,
|
||||
SizeT byteSize) {
|
||||
void RequireDeclaredLevel(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget,
|
||||
Uint16 level, const IntVec3& extent) {
|
||||
if (!shadow.Defined || shadow.InternalFormat == 0) {
|
||||
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) {
|
||||
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)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
static_cast<Uint32>(shadow.ResourceTarget));
|
||||
}
|
||||
if (shadow.Extent == extent) return;
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - "
|
||||
"resource_subdata extent disagrees with the server-declared texture level "
|
||||
"(key=%llu, uploadTarget=%u, level=%u, declared=%d,%d,%d, record=%d,%d,%d)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
shadow.Extent.x(), shadow.Extent.y(), shadow.Extent.z(),
|
||||
extent.x(), extent.y(), extent.z());
|
||||
}
|
||||
|
||||
// Check the run's exclusive end against the extent recorded by the server's prior
|
||||
// resource_respecify before any cast, coverage edit, or vector growth.
|
||||
const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level,
|
||||
Uint64 imageOffset, const void* bytes, SizeT byteSize) {
|
||||
if (bytes != nullptr && byteSize != 0) {
|
||||
constexpr Uint64 max = std::numeric_limits<Uint64>::max();
|
||||
const Uint64 runBytes = static_cast<Uint64>(byteSize);
|
||||
if (imageOffset > max - runBytes) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - "
|
||||
"texture run end overflows (handle=%llu, uploadTarget=%u, level=%u, "
|
||||
"offset=%llu, size=%llu)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
static_cast<unsigned long long>(imageOffset),
|
||||
static_cast<unsigned long long>(runBytes));
|
||||
}
|
||||
const Uint64 end = imageOffset + runBytes;
|
||||
if (end > shadow.DeclaredByteBound ||
|
||||
end > static_cast<Uint64>(std::numeric_limits<SizeT>::max())) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - "
|
||||
"texture run exceeds the server-declared level byte bound "
|
||||
"(handle=%llu, uploadTarget=%u, level=%u, offset=%llu, size=%llu, "
|
||||
"bound=%llu)",
|
||||
static_cast<unsigned long long>(key), uploadTarget, level,
|
||||
static_cast<unsigned long long>(imageOffset),
|
||||
static_cast<unsigned long long>(runBytes),
|
||||
static_cast<unsigned long long>(shadow.DeclaredByteBound));
|
||||
}
|
||||
const auto* raw = static_cast<const Uint8*>(bytes);
|
||||
const SizeT needed = static_cast<SizeT>(imageOffset) + byteSize;
|
||||
const SizeT begin = static_cast<SizeT>(imageOffset);
|
||||
const SizeT needed = static_cast<SizeT>(end);
|
||||
if (shadow.Bytes.size() < needed) shadow.Bytes.resize(needed, 0);
|
||||
std::memcpy(shadow.Bytes.data() + imageOffset, raw, byteSize);
|
||||
StagedShadowStore::CoverageAdd(shadow.Covered, static_cast<SizeT>(imageOffset),
|
||||
needed);
|
||||
std::memcpy(shadow.Bytes.data() + begin, raw, byteSize);
|
||||
StagedShadowStore::CoverageAdd(shadow.Covered, begin, needed);
|
||||
}
|
||||
m_any.store(true, std::memory_order_release);
|
||||
return shadow.Bytes.empty() ? nullptr : shadow.Bytes.data();
|
||||
@@ -470,6 +654,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
const Bool m_copies;
|
||||
mutable std::mutex m_mutex;
|
||||
StagedTextureDeviceLimits m_deviceLimits{};
|
||||
ska::flat_hash_map<Uint64, TextureShadow> m_shadows;
|
||||
// Read on every IsLevelGpuDirty / LevelExtentOrUndefined, so the monolith cost is one
|
||||
// acquire load of a never-written flag rather than a mutex and two hash lookups.
|
||||
|
||||
@@ -1671,14 +1671,17 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// resource_subdata records immediately after this one, which is what the texture
|
||||
// path already does (TextureEmit.h:1137).
|
||||
//
|
||||
// The SCOPE is contract table 1 row 19b's carrier, read only through the helpers:
|
||||
// the presence byte and the pair are one value in three pieces, and an open-coded
|
||||
// reader that forgets the byte reads level 0 of upload target 0 as a real scope.
|
||||
// The scope and exact mutable-level extent are one carrier value. Read each part
|
||||
// through the helpers; forgetting the presence byte would turn a whole-resource
|
||||
// respecify into level 0 of upload target 0.
|
||||
MGPRespecifiedLevel level{};
|
||||
const MGPRespecifiedLevel* scope = nullptr;
|
||||
if (!MGPipeRespecifyIsWholeResource(desc)) {
|
||||
level.UploadTarget = MGPipeRespecifiedUploadTargetOf(desc);
|
||||
level.Level = MGPipeRespecifiedLevelOf(desc);
|
||||
level = MGPipeMakeRespecifiedLevel(MGPipeRespecifiedUploadTargetOf(desc),
|
||||
MGPipeRespecifiedLevelOf(desc),
|
||||
MGPipeRespecifiedWidthOf(desc),
|
||||
MGPipeRespecifiedHeightOf(desc),
|
||||
MGPipeRespecifiedDepthOf(desc));
|
||||
scope = &level;
|
||||
}
|
||||
noteAcceptance(MGPipeApplyResourceRespecify(desc, nullptr, scope));
|
||||
|
||||
@@ -1422,9 +1422,9 @@ namespace {
|
||||
// The split shape, both halves in agreement: the carrier says (kTex2DUpload, 1) and the
|
||||
// trailing pointer the codec rebuilt from it says the same.
|
||||
MGPResourceDesc perLevel = Tex2DDesc(res, 4, 2, 91);
|
||||
MGPipeSetRespecifiedLevel(perLevel, kTex2DUpload, 1);
|
||||
MGPipeSetRespecifiedLevel(perLevel, kTex2DUpload, 1, 2, 2, 1);
|
||||
ASSERT_FALSE(MGPipeRespecifyIsWholeResource(perLevel));
|
||||
const MGPRespecifiedLevel covered{kTex2DUpload, 1};
|
||||
const MGPRespecifiedLevel covered = MGPipeMakeRespecifiedLevel(kTex2DUpload, 1, 2, 2, 1);
|
||||
EXPECT_TRUE(MGPipeApplyResourceRespecify(perLevel, nullptr, &covered));
|
||||
|
||||
EXPECT_TRUE(HasPendingUpload(res.Slot, kTex2DUpload, 0))
|
||||
@@ -1476,8 +1476,8 @@ namespace {
|
||||
for (const Drive& drive : drives) {
|
||||
const ChildResult child = RunInChild([&res, &drive]() {
|
||||
MGPResourceDesc perLevel = Tex2DDesc(res, 4, 2, 92);
|
||||
MGPipeSetRespecifiedLevel(perLevel, kTex2DUpload, 1);
|
||||
const MGPRespecifiedLevel named{drive.PointerTarget, drive.PointerLevel};
|
||||
MGPipeSetRespecifiedLevel(perLevel, kTex2DUpload, 1, 2, 2, 1);
|
||||
const MGPRespecifiedLevel named = MGPipeMakeRespecifiedLevel(drive.PointerTarget, drive.PointerLevel, 2, 2, 1);
|
||||
MGPipeApplyResourceRespecify(perLevel, nullptr,
|
||||
drive.HasPointer ? &named : nullptr);
|
||||
});
|
||||
|
||||
@@ -2149,14 +2149,14 @@ TEST(TextureEmit, ARespecifyOfOneLevelKeepsThePendingUploadsOfTheOthers) {
|
||||
|
||||
// glTexImage2D(level 0, data): the respecify names the level it defines, and the drain then
|
||||
// emits level 0's shape, which the applier accepts.
|
||||
const MGPRespecifiedLevel levelZero{kTex2D, 0};
|
||||
const MGPRespecifiedLevel levelZero = MGPipeMakeRespecifiedLevel(kTex2D, 0, 64, 64, 1);
|
||||
MGPipeApplyResourceRespecify(levelDesc(1, 0x8058u /*GL_RGBA8*/), nullptr, &levelZero);
|
||||
ASSERT_TRUE(MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{0, 0, 0, 64, 64, 1}, 0), texels));
|
||||
// A SECOND FACE OF THE SAME LEVEL, keyed the way the packed Target keys it (ID-12: high
|
||||
// byte = the cube-face upload target, low byte = the resource target), so what survives is
|
||||
// a SET and not one lucky entry - and so that the level number alone cannot be what matched.
|
||||
const Uint16 secondFace = MGPipePackSubDataTarget(kTex2D, 1u);
|
||||
const MGPRespecifiedLevel faceOfLevelZero{secondFace, 0};
|
||||
const MGPRespecifiedLevel faceOfLevelZero = MGPipeMakeRespecifiedLevel(secondFace, 0, 64, 64, 1);
|
||||
MGPipeApplyResourceRespecify(levelDesc(1, 0x8058u), nullptr, &faceOfLevelZero);
|
||||
MGPSubData otherFace = TextureUpload(texture, 0, MGPBox{0, 0, 0, 64, 64, 1}, 0);
|
||||
otherFace.Target = secondFace;
|
||||
@@ -2168,7 +2168,7 @@ TEST(TextureEmit, ARespecifyOfOneLevelKeepsThePendingUploadsOfTheOthers) {
|
||||
//
|
||||
// glTexImage2D(level 1, data): this redefines level 1 of the (kTex2D, *) face only, and the
|
||||
// level count moves 1 -> 2 with it.
|
||||
const MGPRespecifiedLevel levelOne{kTex2D, 1};
|
||||
const MGPRespecifiedLevel levelOne = MGPipeMakeRespecifiedLevel(kTex2D, 1, 32, 32, 1);
|
||||
MGPipeApplyResourceRespecify(levelDesc(2, 0x8058u), nullptr, &levelOne);
|
||||
|
||||
ASSERT_EQ(TextureRecordOf(10).PendingUploads.size(), 2u)
|
||||
@@ -2256,10 +2256,10 @@ TEST(TextureEmit, ARespecifyOfOneCubeFaceKeepsTheOtherFacesUploadOfTheSameLevel)
|
||||
// glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level 0, data), then the same for -X. Espryt
|
||||
// bails on both (a cube map with one face defined is not cube-complete), so both are still
|
||||
// owed when the next call arrives.
|
||||
const MGPRespecifiedLevel positiveXLevelZero{positiveX, 0};
|
||||
const MGPRespecifiedLevel positiveXLevelZero = MGPipeMakeRespecifiedLevel(positiveX, 0, 64, 64, 1);
|
||||
MGPipeApplyResourceRespecify(faceDesc(0x8058u /*GL_RGBA8*/), nullptr, &positiveXLevelZero);
|
||||
ASSERT_TRUE(MGPipeApplyResourceSubData(faceUpload(positiveX), texels));
|
||||
const MGPRespecifiedLevel negativeXLevelZero{negativeX, 0};
|
||||
const MGPRespecifiedLevel negativeXLevelZero = MGPipeMakeRespecifiedLevel(negativeX, 0, 64, 64, 1);
|
||||
MGPipeApplyResourceRespecify(faceDesc(0x8058u), nullptr, &negativeXLevelZero);
|
||||
ASSERT_TRUE(MGPipeApplyResourceSubData(faceUpload(negativeX), texels));
|
||||
ASSERT_EQ(TextureRecordOf(11).PendingUploads.size(), 2u)
|
||||
@@ -2498,12 +2498,12 @@ TEST(TextureEmit, ARespecifyThatRedefinesNoStorageCarriesTheStickyMaskAndKeepsTh
|
||||
// nothing about that level's coordinate system. Level 1's entry goes; level 0's stays.
|
||||
ASSERT_TRUE(MGPipeApplyResourceSubData(TextureUpload(texture, 1, MGPBox{0, 0, 0, 32, 32, 1}, 0), texels));
|
||||
ASSERT_EQ(TextureRecordOf(11).PendingUploads.size(), 2u);
|
||||
const MGPRespecifiedLevel levelOne{kTex2D, 1};
|
||||
const MGPRespecifiedLevel levelOne = MGPipeMakeRespecifiedLevel(kTex2D, 1, 32, 32, 1);
|
||||
ASSERT_TRUE(MGPipeApplyResourceRespecify(maskedAgain, nullptr, &levelOne));
|
||||
ASSERT_EQ(TextureRecordOf(11).PendingUploads.size(), 1u)
|
||||
<< "a level-scoped respecify on an unchanged descriptor did not drop the level it named";
|
||||
EXPECT_EQ(TextureRecordOf(11).PendingUploads[0].Level, 0u) << "it dropped the wrong level";
|
||||
const MGPRespecifiedLevel levelZero{kTex2D, 0};
|
||||
const MGPRespecifiedLevel levelZero = MGPipeMakeRespecifiedLevel(kTex2D, 0, 64, 64, 1);
|
||||
|
||||
// THE NEGATIVE CONTROL, in the same case: move ONE storage-defining field and the same call
|
||||
// is a redefinition again, which takes the level it names with it.
|
||||
|
||||
@@ -704,7 +704,7 @@ TEST_F(PipeWireCodecTest, KNeedsAckRespecifyCarriesItsRedefinitionScope) {
|
||||
ASSERT_TRUE(wire.PumpOne(&applied));
|
||||
|
||||
MGPResourceDesc respecify = create;
|
||||
MGPipeSetRespecifiedLevel(respecify, 0x0102u, 1u);
|
||||
MGPipeSetRespecifiedLevel(respecify, 0x0102u, 1u, 4u, 4u, 1u);
|
||||
EXPECT_FALSE(MGPipeRespecifyIsWholeResource(respecify));
|
||||
ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify)),
|
||||
kInvalidSeq);
|
||||
@@ -836,7 +836,7 @@ TEST_F(PipeWireCodecTest, ResourceDescPadsCrossToo) {
|
||||
desc.ArrayLayers = 6;
|
||||
desc.Levels = 3;
|
||||
desc.Samples = 1;
|
||||
MGPipeSetRespecifiedLevel(desc, 0x0304u, 2u);
|
||||
MGPipeSetRespecifiedLevel(desc, 0x0304u, 2u, 4u, 4u, 1u);
|
||||
|
||||
ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &desc, sizeof(desc)),
|
||||
kInvalidSeq);
|
||||
@@ -851,6 +851,9 @@ TEST_F(PipeWireCodecTest, ResourceDescPadsCrossToo) {
|
||||
EXPECT_FALSE(MGPipeRespecifyIsWholeResource(*crossed));
|
||||
EXPECT_EQ(MGPipeRespecifiedUploadTargetOf(*crossed), 0x0304u);
|
||||
EXPECT_EQ(MGPipeRespecifiedLevelOf(*crossed), 2u);
|
||||
EXPECT_EQ(MGPipeRespecifiedWidthOf(*crossed), 4u);
|
||||
EXPECT_EQ(MGPipeRespecifiedHeightOf(*crossed), 4u);
|
||||
EXPECT_EQ(MGPipeRespecifiedDepthOf(*crossed), 1u);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <MG_Remote/Wire/PipeWireCodec.h>
|
||||
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_State/GLState/TextureState/TextureEnum.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -101,7 +102,9 @@ namespace {
|
||||
template <class Payload, class Mutator>
|
||||
bool EncodeThenMutateHeaderAndPublish(Client::ClientSession& session, MobileGL::MG_Pipe::MGPWireOp op,
|
||||
const Payload& payload, Mutator&& mutate,
|
||||
std::uint64_t* outSeq = nullptr) {
|
||||
std::uint64_t* outSeq = nullptr,
|
||||
const Wire::WireTail* tails = nullptr,
|
||||
MobileGL::Uint32 tailCount = 0) {
|
||||
if (outSeq != nullptr) *outSeq = Wire::kInvalidSeq;
|
||||
auto* link = session.DataLink();
|
||||
if (link == nullptr || !link->Attached()) return false;
|
||||
@@ -109,7 +112,9 @@ namespace {
|
||||
if (!Wire::MGPipeWireRecordLayout(op, &payload, layout) ||
|
||||
layout.PayloadBytes != sizeof(payload)) return false;
|
||||
auto& encoder = session.Encoder();
|
||||
const std::uint64_t seq = encoder.EncodeRecord(op, &payload, sizeof(payload));
|
||||
const std::uint64_t seq = tailCount == 0
|
||||
? encoder.EncodeRecord(op, &payload, sizeof(payload))
|
||||
: encoder.EncodeRecord(op, &payload, sizeof(payload), tails, tailCount);
|
||||
if (seq == Wire::kInvalidSeq) return false;
|
||||
auto& commands = link->CommandsOut();
|
||||
const auto* arena = link->RecordArena();
|
||||
@@ -131,6 +136,53 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Payload>
|
||||
bool PublishRawRecordAndWait(Client::ClientSession& session, MobileGL::MG_Pipe::MGPWireOp op,
|
||||
const Payload& payload, std::uint64_t* outSeq = nullptr) {
|
||||
std::uint64_t seq = Wire::kInvalidSeq;
|
||||
if (!EncodeThenMutateHeaderAndPublish(
|
||||
session, op, payload, [](Transport::RingRecordHeader&, std::uint8_t*) {}, &seq)) {
|
||||
return false;
|
||||
}
|
||||
if (outSeq != nullptr) *outSeq = seq;
|
||||
return session.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached;
|
||||
}
|
||||
|
||||
MobileGL::MG_Pipe::MGPFramebufferState RawColorAttachmentFramebuffer(
|
||||
MobileGL::MG_Pipe::MGPipeHandle fbo, MobileGL::MG_Pipe::MGPipeHandle texture,
|
||||
MobileGL::Uint64 contentHash) {
|
||||
MobileGL::MG_Pipe::MGPFramebufferState state{};
|
||||
state.Fbo = fbo;
|
||||
state.Target = static_cast<MobileGL::Uint8>(
|
||||
MobileGL::MG_Pipe::MGPipeFramebufferTarget::Both);
|
||||
state.IsDefault = 0;
|
||||
state.Complete = 1;
|
||||
state.Width = state.Height = state.Layers = state.Samples = 1;
|
||||
state.FixedSampleLocations = 1;
|
||||
for (auto& drawBuffer : state.DrawBuffers) drawBuffer = -1;
|
||||
state.DrawBuffers[0] = 0;
|
||||
state.Color[0].Res = texture;
|
||||
state.Color[0].InternalFormat =
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
state.Color[0].Kind = MobileGL::MG_Pipe::kMGPipeSurfaceKindTexture;
|
||||
state.Color[0].UploadTarget =
|
||||
static_cast<MobileGL::Uint16>(MobileGL::TextureUploadTarget::Texture2D);
|
||||
state.Color[0].TextureTarget =
|
||||
static_cast<MobileGL::Uint16>(MobileGL::TextureTarget::Texture2D);
|
||||
state.ContentHash = contentHash;
|
||||
return state;
|
||||
}
|
||||
|
||||
MobileGL::MG_Pipe::MGPClear RawColorClear(MobileGL::MG_Pipe::MGPipeHandle fbo) {
|
||||
MobileGL::MG_Pipe::MGPClear clear{};
|
||||
clear.Fbo = fbo;
|
||||
clear.Kind = MobileGL::MG_Pipe::kMGPipeClearKindWhole;
|
||||
clear.DrawBufferIndex = -1;
|
||||
clear.BufferMask = GL_COLOR_BUFFER_BIT;
|
||||
clear.ValueClass = MobileGL::MG_Pipe::kMGPipeClearValueClassFloat;
|
||||
return clear;
|
||||
}
|
||||
|
||||
std::string ServerImage() {
|
||||
if (const char* explicitPath = std::getenv("MOBILEGL_TEST_SERVER_PATH")) {
|
||||
return explicitPath;
|
||||
@@ -343,6 +395,506 @@ TEST(ServerSpawnTest, RawPeerOversizedReadbackGetsErrorAndSessionContinues) {
|
||||
EXPECT_EQ(exitCode, 0);
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerTextureRunPastDeclaredLevelBoundIsNamedProtocolCorruption) {
|
||||
const std::string endpoint = Endpoint("bad-texture-run-bound");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-texture-run-bound", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = {73u, 1u};
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.ArrayLayers = 1;
|
||||
desc.Levels = 1;
|
||||
desc.Samples = 1;
|
||||
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
MobileGL::MG_Pipe::MGPResourceDesc respecify = desc;
|
||||
respecify.HasDefinedContent = 1;
|
||||
MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel(
|
||||
respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(respecify.Target),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2D)),
|
||||
0, 4, 4, 1);
|
||||
seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify,
|
||||
sizeof(respecify), nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
// Reserve the post-mutation 96-byte stage run, but declare an encoder-accepted 48-byte
|
||||
// 4x3 box from Y=1. The mutation changes only the declared run geometry/size.
|
||||
std::vector<std::uint8_t> staged(96, 0x5A);
|
||||
MobileGL::MG_Pipe::MGPSubData upload{};
|
||||
upload.Res = desc.Resource;
|
||||
upload.Target = MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2D));
|
||||
upload.Level = 0;
|
||||
upload.UnionBox = MobileGL::MG_Pipe::MGPBox{0, 1, 0, 4, 3, 1};
|
||||
upload.RegionCount = 1;
|
||||
upload.Blob = client.Encoder().StageBytes(staged.data(), staged.size());
|
||||
upload.Blob.Size = 48;
|
||||
upload.LevelWidth = 4;
|
||||
upload.LevelHeight = 4;
|
||||
upload.LevelDepth = 1;
|
||||
MobileGL::MG_Pipe::MGPSubRegion region{};
|
||||
region.Y = 1;
|
||||
region.W = 4;
|
||||
region.H = 3;
|
||||
region.D = 1;
|
||||
region.SrcOffset = 0;
|
||||
region.SrcRowStride = 16;
|
||||
region.SrcSliceStride = 64;
|
||||
const Wire::WireTail tail{®ion, sizeof(region)};
|
||||
std::uint64_t uploadSeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::ResourceSubData, upload,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) {
|
||||
auto* wireUpload = reinterpret_cast<MobileGL::MG_Pipe::MGPSubData*>(payloadBytes);
|
||||
wireUpload->Blob.Size = 96;
|
||||
auto* wireRegion = reinterpret_cast<MobileGL::MG_Pipe::MGPSubRegion*>(
|
||||
payloadBytes + sizeof(MobileGL::MG_Pipe::MGPSubData));
|
||||
wireRegion->SrcRowStride = 32;
|
||||
wireRegion->SrcSliceStride = 96;
|
||||
}, &uploadSeq, &tail, 1))
|
||||
<< "the peer failed to publish the encoder-accepted texture run";
|
||||
ASSERT_NE(uploadSeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK) << "server did not reject the out-of-level run promptly";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto boundFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"}");
|
||||
EXPECT_NE(boundFatal, std::string::npos)
|
||||
<< "the server did not reject the run at the declared level byte bound";
|
||||
EXPECT_EQ(firstProtocolFatal, boundFatal)
|
||||
<< "the run was refused before it reached the staged-level byte bound";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerTextureSubDataMustMatchTheAcceptedMutableLevelExtent) {
|
||||
const std::string endpoint = Endpoint("bad-texture-level-extent");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-texture-level-extent", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = {74u, 1u};
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.ArrayLayers = 1;
|
||||
desc.Levels = 1;
|
||||
desc.Samples = 1;
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
MobileGL::MG_Pipe::MGPResourceDesc respecify = desc;
|
||||
respecify.HasDefinedContent = 1;
|
||||
MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel(
|
||||
respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(respecify.Target),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2D)),
|
||||
0, 4, 4, 1);
|
||||
seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify,
|
||||
sizeof(respecify), nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
std::vector<std::uint8_t> staged(64, 0x5A);
|
||||
MobileGL::MG_Pipe::MGPSubData upload{};
|
||||
upload.Res = desc.Resource;
|
||||
upload.Target = MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2D));
|
||||
upload.Level = 0;
|
||||
upload.UnionBox = MobileGL::MG_Pipe::MGPBox{0, 0, 0, 4, 4, 1};
|
||||
upload.Blob = client.Encoder().StageBytes(staged.data(), staged.size());
|
||||
upload.LevelWidth = 4;
|
||||
upload.LevelHeight = 4;
|
||||
upload.LevelDepth = 1;
|
||||
std::uint64_t uploadSeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::ResourceSubData, upload,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) {
|
||||
auto* wireUpload = reinterpret_cast<MobileGL::MG_Pipe::MGPSubData*>(payloadBytes);
|
||||
// Still within the device limit and still contains the dirty box; the server must
|
||||
// refuse it because ResourceRespecify accepted 4x4 for this exact level.
|
||||
wireUpload->LevelWidth = 8;
|
||||
}, &uploadSeq))
|
||||
<< "the peer failed to publish the encoder-accepted texture record";
|
||||
ASSERT_NE(uploadSeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK) << "server did not reject a changed mutable-level extent promptly";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto extentFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"}");
|
||||
EXPECT_NE(extentFatal, std::string::npos)
|
||||
<< "server did not refuse the upload extent that disagreed with ResourceRespecify";
|
||||
EXPECT_EQ(firstProtocolFatal, extentFatal)
|
||||
<< "the record was refused before staged-level extent matching";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerMultisampleTextureSubDataIsNamedProtocolCorruption) {
|
||||
const std::string endpoint = Endpoint("bad-multisample-texture-subdata");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-multisample-texture-subdata", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = {75u, 1u};
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2DMS);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.ArrayLayers = 1;
|
||||
desc.Levels = 1;
|
||||
desc.Samples = 4;
|
||||
desc.Immutable = 1;
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
MobileGL::MG_Pipe::MGPResourceDesc respecify = desc;
|
||||
respecify.HasDefinedContent = 1;
|
||||
seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify,
|
||||
sizeof(respecify), nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
std::vector<std::uint8_t> staged(64, 0x5A);
|
||||
MobileGL::MG_Pipe::MGPSubData upload{};
|
||||
upload.Res = desc.Resource;
|
||||
upload.Target = MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2DMS),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2DMultisample));
|
||||
upload.Level = 0;
|
||||
upload.UnionBox = MobileGL::MG_Pipe::MGPBox{0, 0, 0, 4, 4, 1};
|
||||
upload.Blob = client.Encoder().StageBytes(staged.data(), staged.size());
|
||||
upload.LevelWidth = 4;
|
||||
upload.LevelHeight = 4;
|
||||
upload.LevelDepth = 1;
|
||||
std::uint64_t uploadSeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::ResourceSubData, upload,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t*) {}, &uploadSeq));
|
||||
ASSERT_NE(uploadSeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK) << "server accepted an illegal multisample subimage record";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto targetFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"}");
|
||||
EXPECT_NE(targetFatal, std::string::npos)
|
||||
<< "server did not refuse multisample bytes through the staged texture target gate";
|
||||
EXPECT_EQ(firstProtocolFatal, targetFatal)
|
||||
<< "multisample record was refused before the staged target gate";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerNoncanonicalMutableExtentCarrierIsRefused) {
|
||||
const std::string endpoint = Endpoint("bad-respecify-extent-carrier");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-respecify-extent-carrier", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = {76u, 1u};
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.ArrayLayers = 1;
|
||||
desc.Levels = 1;
|
||||
desc.Samples = 1;
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
MobileGL::MG_Pipe::MGPResourceDesc respecify = desc;
|
||||
respecify.HasDefinedContent = 1;
|
||||
MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel(
|
||||
respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget(
|
||||
static_cast<MobileGL::Uint32>(respecify.Target),
|
||||
static_cast<MobileGL::Uint32>(MobileGL::TextureUploadTarget::Texture2D)),
|
||||
0, 4, 4, 1);
|
||||
std::uint64_t respecifySeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, respecify,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) {
|
||||
auto* wireDesc = reinterpret_cast<MobileGL::MG_Pipe::MGPResourceDesc*>(payloadBytes);
|
||||
wireDesc->BufSize |= (std::uint64_t{1} << 32);
|
||||
}, &respecifySeq));
|
||||
ASSERT_NE(respecifySeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK) << "server accepted a noncanonical mip extent carrier";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto carrierFatal = log.find("Fatal{ProtocolCorruption, \"ResourceRespecify.ExtentCarrier\"}");
|
||||
EXPECT_NE(carrierFatal, std::string::npos)
|
||||
<< "server did not reject high BufSize bits in the mutable extent carrier";
|
||||
EXPECT_EQ(firstProtocolFatal, carrierFatal)
|
||||
<< "carrier was refused before the canonicality gate";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerWholeImageRespecifyCannotSmuggleBufferRangeFields) {
|
||||
const std::string endpoint = Endpoint("bad-whole-respecify-buffer-range");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-whole-respecify-buffer-range", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = {77u, 1u};
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.ArrayLayers = 1;
|
||||
desc.Levels = 1;
|
||||
desc.Samples = 1;
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
MobileGL::MG_Pipe::MGPResourceDesc respecify = desc;
|
||||
respecify.HasDefinedContent = 1;
|
||||
std::uint64_t respecifySeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, respecify,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) {
|
||||
auto* wireDesc = reinterpret_cast<MobileGL::MG_Pipe::MGPResourceDesc*>(payloadBytes);
|
||||
wireDesc->BufOffset = 1;
|
||||
}, &respecifySeq));
|
||||
ASSERT_NE(respecifySeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK) << "server accepted a buffer range on a whole image texture";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto rangeFatal = log.find("Fatal{ProtocolCorruption, \"ResourceRespecify.BufferRange\"}");
|
||||
EXPECT_NE(rangeFatal, std::string::npos)
|
||||
<< "server did not reject BufOffset/BufSize on the whole image descriptor";
|
||||
EXPECT_EQ(firstProtocolFatal, rangeFatal)
|
||||
<< "descriptor was refused before the canonical buffer-range gate";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerFramebufferAttachmentPastBackendSlotLimitIsNamedProtocolCorruption) {
|
||||
const std::string endpoint = Endpoint("bad-fbo-texture-slot");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-fbo-texture-slot", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
|
||||
const MobileGL::MG_Pipe::MGPipeHandle fbo{20u, 1u};
|
||||
const MobileGL::MG_Pipe::MGPipeHandle outOfRange{1u << 20, 1u};
|
||||
const auto framebuffer = RawColorAttachmentFramebuffer(fbo, outOfRange, 1);
|
||||
ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState,
|
||||
framebuffer));
|
||||
const auto clear = RawColorClear(fbo);
|
||||
std::uint64_t clearSeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t*) {}, &clearSeq));
|
||||
ASSERT_NE(clearSeq, Wire::kInvalidSeq);
|
||||
// This final clear is expected to kill the server, so there is no applied watermark to wait on.
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK)
|
||||
<< "server did not reject the FBO attachment whose texture slot exceeded BackendSlotTable's cap";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto slotFatal = log.find("Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"}");
|
||||
EXPECT_NE(slotFatal, std::string::npos) << "server did not name the backend texture slot bound";
|
||||
EXPECT_EQ(firstProtocolFatal, slotFatal)
|
||||
<< "the peer record was refused before the backend twin adoption guard";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, RawPeerFramebufferAttachmentBackwardGenerationIsNamedProtocolCorruption) {
|
||||
const std::string endpoint = Endpoint("bad-fbo-texture-generation");
|
||||
const std::string logBase = endpoint + ".log";
|
||||
ScopedEnvironment logPath("MOBILEGL_LOG_FILE_PATH", logBase);
|
||||
Session session;
|
||||
ScopedSessionCleanup cleanup{session};
|
||||
ASSERT_TRUE(Bring("bad-fbo-texture-generation", &session));
|
||||
ASSERT_EQ(Handshake(session), MOBILEGL_OK);
|
||||
auto& client = Client::ClientSessionInstance();
|
||||
|
||||
const MobileGL::MG_Pipe::MGPipeHandle texture{37u, 2u};
|
||||
MobileGL::MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = texture;
|
||||
desc.Target = static_cast<MobileGL::Uint8>(MobileGL::MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
desc.StorageKind = static_cast<MobileGL::Uint8>(MobileGL::TextureStorageType::Mipmap);
|
||||
desc.BindMask = MobileGL::MG_Pipe::kMGPipeBindRenderTarget;
|
||||
desc.InternalFormat = static_cast<MobileGL::Uint32>(MobileGL::TextureInternalFormat::RGBA8);
|
||||
desc.Width = desc.Height = desc.Depth = 1;
|
||||
desc.ArrayLayers = desc.Levels = desc.Samples = 1;
|
||||
MobileGL::Int32 status = Wire::ReplySink::kStatusError;
|
||||
auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
ASSERT_NE(seq, Wire::kInvalidSeq);
|
||||
ASSERT_EQ(status, Wire::ReplySink::kStatusOk);
|
||||
ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached);
|
||||
|
||||
const MobileGL::MG_Pipe::MGPipeHandle fbo{20u, 1u};
|
||||
auto current = RawColorAttachmentFramebuffer(fbo, texture, 1);
|
||||
ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState,
|
||||
current));
|
||||
const auto clear = RawColorClear(fbo);
|
||||
ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear));
|
||||
|
||||
// PipeApply accepts this attachment verbatim; the server's already-live {37,2} twin is
|
||||
// the only fact that lets the apply-side adoption guard identify the stale generation.
|
||||
current.Color[0].Res.Gen = 1;
|
||||
current.ContentHash = 2;
|
||||
ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState,
|
||||
current));
|
||||
std::uint64_t badClearSeq = Wire::kInvalidSeq;
|
||||
ASSERT_TRUE(EncodeThenMutateHeaderAndPublish(
|
||||
client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear,
|
||||
[](Transport::RingRecordHeader&, std::uint8_t*) {}, &badClearSeq));
|
||||
ASSERT_NE(badClearSeq, Wire::kInvalidSeq);
|
||||
|
||||
int exitCode = -1;
|
||||
const auto reaped = Server::ReapServer(session.server, 5000, &exitCode);
|
||||
if (reaped != MOBILEGL_OK) {
|
||||
client.Stop();
|
||||
if (session.server.pid > 0) {
|
||||
::kill(session.server.pid, SIGKILL);
|
||||
(void)Server::ReapServer(session.server, 5000, &exitCode);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reaped, MOBILEGL_OK)
|
||||
<< "server did not reject the FBO attachment whose generation moved backwards";
|
||||
EXPECT_EQ(exitCode, -SIGABRT);
|
||||
const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str());
|
||||
const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption");
|
||||
const auto generationFatal = log.find("Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"}");
|
||||
EXPECT_NE(generationFatal, std::string::npos)
|
||||
<< "server did not name the stale attachment generation";
|
||||
EXPECT_EQ(firstProtocolFatal, generationFatal)
|
||||
<< "the peer record was refused before the backend twin generation guard";
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
TEST(ServerSpawnTest, StartsAServerProcessAndHandshakesAcrossIt) {
|
||||
const int before = Server::CountOwnChildren();
|
||||
ASSERT_GE(before, 0) << "/proc unreadable; the process-tree gate cannot run here";
|
||||
|
||||
@@ -130,7 +130,13 @@ TEST(SessionHandshakeTest, TheAbiFingerprintChangesWhenAnyOfItsInputsDoes) {
|
||||
EXPECT_EQ(inputs.ProgramArtifactsCodecVersion, MG_State::GLState::kProgramArtifactsCodecVersion);
|
||||
EXPECT_EQ(inputs.ProgramArtifactsSchema, MG_State::GLState::ProgramArtifactsSchemaFingerprint());
|
||||
EXPECT_EQ(inputs.OpCount, static_cast<Uint64>(MG_Pipe::MGPWireOp::kOpCount));
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
EXPECT_EQ(inputs.ControlSchemaRevision,
|
||||
(static_cast<Uint64>(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) |
|
||||
MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision);
|
||||
#else
|
||||
EXPECT_EQ(inputs.ControlSchemaRevision, static_cast<Uint64>(MOBILEGL_PROTOCOL_CONTROL_REVISION));
|
||||
#endif
|
||||
EXPECT_EQ(inputs.PointerBits, sizeof(void*) * 8);
|
||||
const auto perturbed = [&](auto mutate) {
|
||||
auto copy = inputs;
|
||||
|
||||
@@ -92,6 +92,7 @@ TEST(StagedTextureStoreTest, TheSplitArmCopiesAndSurvivesTheSourceBeingPoisoned)
|
||||
|
||||
Vector<Uint8> staged(64, 0xAB);
|
||||
const IntVec3 extent{4, 4, 1};
|
||||
splitStore.NoteLevelDefined(key, kTex2DTarget, 0, extent);
|
||||
const Uint8* splitBase = splitStore.Adopt(key, kTex2DTarget, 0, extent, staged.data(), staged.size());
|
||||
const Uint8* monolithBase =
|
||||
monolithStore.Adopt(key, kTex2DTarget, 0, extent, staged.data(), staged.size());
|
||||
@@ -148,6 +149,32 @@ TEST(StagedTextureStoreTest, DefinednessIsTrackedAndAnExtentMoveDropsTheBytes) {
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 0), IntVec3(8, 8, 1));
|
||||
}
|
||||
|
||||
TEST(StagedTextureStoreTest, AMutableNoncanonicalLevelKeepsItsExactDeclaredExtentAndBound) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(41, 1));
|
||||
const IntVec3 noncanonical{3, 2, 1};
|
||||
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 2, noncanonical,
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D),
|
||||
static_cast<Uint32>(TextureInternalFormat::RGBA8));
|
||||
EXPECT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 2));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 2), noncanonical)
|
||||
<< "a null-data mutable mip lost the exact extent accepted by resource_respecify";
|
||||
EXPECT_EQ(store.LevelDeclaredByteBound(key, kTex2DTarget, 2), 24u);
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 2));
|
||||
}
|
||||
|
||||
TEST(StagedTextureStoreTest, MultisampleAndTextureBufferTargetsCannotAcceptStagedTexelBytes) {
|
||||
EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData(
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2DMS)));
|
||||
EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData(
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2DMSArray)));
|
||||
EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData(
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::TexBuffer)));
|
||||
EXPECT_TRUE(Server::StagedTextureTargetSupportsSubData(
|
||||
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D)));
|
||||
}
|
||||
|
||||
// Keys are independent, ResetLevels drops one resource's whole chain, Drop one key and
|
||||
// DropAll every one - the three events the contract names (respecify, destroy, context death)
|
||||
// each have their call site, and these are the answers those call sites rely on.
|
||||
@@ -155,11 +182,15 @@ TEST(StagedTextureStoreTest, ResetDropAndDropAllForgetExactlyWhatTheyName) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 a = Server::StagedTextureStore::KeyForHandle(TestHandle(5, 1));
|
||||
const Uint64 b = Server::StagedTextureStore::KeyForHandle(TestHandle(6, 1));
|
||||
Vector<Uint8> bytes(16, 0x22);
|
||||
Vector<Uint8> bytes4x4(64, 0x22);
|
||||
Vector<Uint8> bytes2x2(16, 0x22);
|
||||
|
||||
store.Adopt(a, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes.data(), bytes.size());
|
||||
store.Adopt(a, kTex2DTarget, 1, IntVec3{2, 2, 1}, bytes.data(), bytes.size());
|
||||
store.Adopt(b, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes.data(), bytes.size());
|
||||
store.NoteLevelDefined(a, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.Adopt(a, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes4x4.data(), bytes4x4.size());
|
||||
store.NoteLevelDefined(a, kTex2DTarget, 1, IntVec3{2, 2, 1});
|
||||
store.Adopt(a, kTex2DTarget, 1, IntVec3{2, 2, 1}, bytes2x2.data(), bytes2x2.size());
|
||||
store.NoteLevelDefined(b, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.Adopt(b, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes4x4.data(), bytes4x4.size());
|
||||
ASSERT_EQ(store.TrackedResources(), 2u);
|
||||
ASSERT_EQ(store.TrackedLevelCount(a), 2u);
|
||||
|
||||
@@ -167,7 +198,8 @@ TEST(StagedTextureStoreTest, ResetDropAndDropAllForgetExactlyWhatTheyName) {
|
||||
EXPECT_FALSE(store.HasShadow(a)) << "a whole-resource respecify forgets every level";
|
||||
EXPECT_TRUE(store.IsCovered(b, kTex2DTarget, 0));
|
||||
|
||||
store.Adopt(a, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes.data(), bytes.size());
|
||||
store.NoteLevelDefined(a, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.Adopt(a, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes4x4.data(), bytes4x4.size());
|
||||
store.Drop(a);
|
||||
EXPECT_EQ(store.TrackedResources(), 1u);
|
||||
EXPECT_TRUE(store.IsCovered(b, kTex2DTarget, 0));
|
||||
@@ -247,6 +279,7 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove
|
||||
Vector<Uint8> lower(32, 0x33);
|
||||
Vector<Uint8> upper(32, 0x77);
|
||||
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, extent);
|
||||
// THE SECOND RUN FIRST, deliberately: coverage is a SET and the placement comes from the
|
||||
// record, so the order the pieces land in cannot matter.
|
||||
EXPECT_EQ(monolithStore.AdoptRun(key, kTex2DTarget, 0, extent, 32, upper.data(), upper.size()),
|
||||
@@ -277,22 +310,20 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove
|
||||
// AND A GAP IS NOT COVERAGE: it is the missing-piece case, which is the one thing the covered
|
||||
// set exists to make visible instead of papered over.
|
||||
const Uint64 holed = Server::StagedTextureStore::KeyForHandle(TestHandle(12, 1));
|
||||
store.NoteLevelDefined(holed, kTex2DTarget, 0, extent);
|
||||
Vector<Uint8> part(16, 0x11);
|
||||
store.AdoptRun(holed, kTex2DTarget, 0, extent, 0, part.data(), part.size());
|
||||
store.AdoptRun(holed, kTex2DTarget, 0, extent, 32, part.data(), part.size());
|
||||
store.AdoptRun(holed, kTex2DTarget, 0, extent, 32, upper.data(), upper.size());
|
||||
EXPECT_EQ(store.LevelCoveredRunCount(holed, kTex2DTarget, 0), 2u)
|
||||
<< "two runs with a 16-byte gap merged into one";
|
||||
EXPECT_FALSE(store.IsCovered(holed, kTex2DTarget, 0)) << "a span across the gap reads covered";
|
||||
// AND WHAT THE CHECK CANNOT SEE, written down rather than left to be discovered: the store
|
||||
// knows no format, so it cannot know how many bytes a level SHOULD have. A leading run that
|
||||
// already reaches the image's high-water mark therefore reads as covered - the check is "the
|
||||
// range the reader asks for is covered", not "the level is the size the client meant", and no
|
||||
// reader runs between the pieces of one level anyway (they are records of ONE verb, and the
|
||||
// texture sync runs at the barrier that closes it).
|
||||
// A short leading run is not a complete level even if its current high-water mark ends at
|
||||
// that run. The store checks coverage against the server-derived byte bound, not its prefix.
|
||||
const Uint64 leading = Server::StagedTextureStore::KeyForHandle(TestHandle(14, 1));
|
||||
store.NoteLevelDefined(leading, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.AdoptRun(leading, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, part.data(), part.size());
|
||||
EXPECT_TRUE(store.IsCovered(leading, kTex2DTarget, 0))
|
||||
<< "a leading run is the whole image as far as this store can tell";
|
||||
EXPECT_FALSE(store.IsCovered(leading, kTex2DTarget, 0))
|
||||
<< "the leading prefix must not stand in for the declared level";
|
||||
EXPECT_EQ(store.LevelByteSize(leading, kTex2DTarget, 0), 16u);
|
||||
// Filling the gap merges all three into one run: adjacency, not proximity, is the rule.
|
||||
store.AdoptRun(holed, kTex2DTarget, 0, extent, 16, part.data(), part.size());
|
||||
@@ -308,23 +339,31 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// B4's rule under PH-4's declared extents: a level redefined at a new extent (the respecify's
|
||||
// NoteLevelDefined, which every run now needs before it lands) starts with no coverage, so the
|
||||
// first run of the new definition cannot be completed by the old definition's bytes. A run that
|
||||
// still carries the old extent is StagedTextureStore.LevelExtent's refusal (ServerSpawnTest's
|
||||
// raw-peer control), so the new-extent runs are the only ones that can land here.
|
||||
TEST(StagedTextureStoreTest, AdoptRunExtentMoveDoesNotInheritOldCoverage) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(15, 1));
|
||||
Vector<Uint8> oldRun(16, 0x44);
|
||||
Vector<Uint8> newRun(16, 0x99);
|
||||
Vector<Uint8> oldRun(64, 0x44); // the whole 4x4 RGBA8 level
|
||||
Vector<Uint8> newRun(64, 0x99); // the upper half of the 8x4 RGBA8 level
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, oldRun.data(), oldRun.size());
|
||||
ASSERT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{8, 4, 1}, 16, newRun.data(), newRun.size());
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{8, 4, 1});
|
||||
ASSERT_EQ(store.LevelDeclaredByteBound(key, kTex2DTarget, 0), 128u);
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{8, 4, 1}, 64, newRun.data(), newRun.size());
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 0))
|
||||
<< "the old level's [0,16) run was mistaken for bytes of the new extent";
|
||||
<< "the old level's [0,64) run was mistaken for bytes of the new extent";
|
||||
EXPECT_EQ(store.LevelCoveredRunCount(key, kTex2DTarget, 0), 1u);
|
||||
Vector<Uint8> newLower(16, 0xAA);
|
||||
Vector<Uint8> newLower(64, 0xAA);
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{8, 4, 1}, 0, newLower.data(), newLower.size());
|
||||
const Uint8* base = store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_extent_move");
|
||||
ASSERT_NE(base, nullptr);
|
||||
EXPECT_EQ(base[0], 0xAAu);
|
||||
EXPECT_EQ(base[16], 0x99u);
|
||||
EXPECT_EQ(base[64], 0x99u);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -390,7 +429,8 @@ TEST(StagedTextureStoreTest, TheRunImageOffsetIsReadOffTheRecordsOwnRunAndBox) {
|
||||
TEST(StagedTextureStoreTest, ATexelReadOutsideTheStagedCoverageIsFatalByName) {
|
||||
Server::StagedTextureStore store(/*copies=*/true);
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(8, 1));
|
||||
Vector<Uint8> bytes(16, 0x33);
|
||||
Vector<Uint8> bytes(64, 0x33);
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.Adopt(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, bytes.data(), bytes.size());
|
||||
|
||||
// In coverage: no Fatal, asserted first so the death below cannot be a function that
|
||||
@@ -434,6 +474,8 @@ TEST(StagedTextureStoreTest, ALevelWithAPieceMissingIsRefusedAsAWholeLevelReadBy
|
||||
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(13, 1));
|
||||
Vector<Uint8> part(16, 0x55);
|
||||
Vector<Uint8> whole(64, 0x22);
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{4, 4, 1});
|
||||
store.NoteLevelDefined(key, kTex2DTarget, 1, IntVec3{4, 4, 1});
|
||||
// TWO RUNS WITH A HOLE BETWEEN THEM, which is what a missing piece looks like from here: the
|
||||
// image's high-water mark is the level's size and the covered set does not reach across it.
|
||||
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, part.data(), part.size());
|
||||
@@ -553,9 +595,8 @@ TEST(StagedTextureProductionTest, HooklessTextureConsumerOwnsBytesAndScopedStora
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.Levels = 3;
|
||||
MG_Pipe::MGPRespecifiedLevel level{};
|
||||
level.UploadTarget = MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget);
|
||||
level.Level = 0;
|
||||
MG_Pipe::MGPRespecifiedLevel level = MG_Pipe::MGPipeMakeRespecifiedLevel(
|
||||
MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget), 0, 4, 4, 1);
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, &level));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 0), IntVec3(4, 4, 1))
|
||||
<< "null-data glTexImage defines the level even though no upload follows";
|
||||
|
||||
Reference in New Issue
Block a user