mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 05:53:23 +09:00
[MGPipe, MG_Remote] cut a texture level's staged run at the stage chunk
ONE RECORD'S BLOB IS STAGED WHOLE IN SEG_STAGE, a linear arena, so a texture
level larger than the segment's chunk budget was Fatal{RingOverrun, "SEG_STAGE"}
at the encoder rather than a split - and the CI traces hit it: the texture half of
resource_subdata stages the whole level shadow regardless of how little of it
changed, and a 512x128x33 GL_RGBA32F level is 34,603,008 bytes while a 192-cube
GL_RGBA16 level is 56,623,104, both against the default 32 MiB segment. Three
retrace cases aborted there: iterationt-in-world and iterationt-nodsa-in-world in
both backends, and iterationrp-in-world in DirectVulkan.
THE CLIENT now cuts such a level into WHOLE-WIDTH SLABS, one resource_subdata
each (TextureEmit.h's MGPipeForEachTextureSlab, driven at MGPipeStageChunkBytes -
the same cap PipeFill.cpp's buffer walks are cut at). A piece is a set of
consecutive rows of the level shadow, so its box names both its texels and its
byte run; whole slices are the coarsest cut, a slice bigger than the cap falls
back to whole rows and a row bigger than the cap to whole texels inside one row.
Every piece is emitted, dirty or not: the server assembles one level image out of
the runs, so a piece left out would leave a hole no texel read can answer. Each
piece carries at least one region (its clipped dirty rects, or its own box when
none reaches it) because RegionCount == 0 remains the whole-level spelling, and
each region's SrcOffset is rebased onto its own piece's run - MGPipeTypes.h has
always said SrcOffset is "into the blob" - while the strides stay the level's. A
level that FITS keeps the single whole-level record, field for field what it has
always been, which is what every existing gate is written against.
THE SERVER now assembles levels from those runs. StagedTextureStore::AdoptRun
places one run at an image offset, its coverage set is exactly the runs it has
adopted (StagedShadowStore's CoverageAdd, the buffer half's helpers), and only a
FULLY covered level satisfies IsCovered / RequireLevelBytes - a level with one
piece missing is as unreadable as one that never arrived, and it is the same
Fatal{StageSnapshotTooNarrow}. The whole-level spelling survives as Adopt, its
offset-0 replacement, and the two are told apart by the run's own LENGTH: the
whole-level run is the level's complete shadow whatever its box says, a piece's
run is exactly its box's byte extent. Both adoption call sites -
Ops_H_TextureSubData and PipeApply.cpp's hookless fallback, which is what Magma
takes - read the offset that way.
THE PROTOCOL IS UNCHANGED: no field moved, no caps bit was added. The one
compatibility statement is that a split level now crosses as several records of
one apply batch, so an OLD server (which replaces the level on every record) would
keep only the last piece - inproc runs both roles out of one library, so the
constraint is noted here rather than armed with a gate.
Tests. TextureEmit.ALevelTooLargeForTheStageChunkIsCutIntoSlabs drives the split
at MGPipeStageChunkBytesFor's clamp with both trace shapes (5 and 7 slabs), the
row-granularity and texel-granularity fallbacks, the three shapes that refuse to
cut, the region clipping/rebasing, and the piece no rect reaches.
StagedTextureStoreTest gains the multi-run assembly, the gap that must not read as
coverage, the partial-level Fatal by name, the run-offset discriminator, and two
production cases - the applier's hookless arm and DirectGLES's real TextureSubData
hook - that would put both halves at the front of the image if the offset were
dropped.
This commit is contained in:
@@ -2713,20 +2713,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const void* bytes, const MG_Pipe::MGPSubRegion* regions) {
|
||||
// The region set is the upload planner's shape and stays in the applier's
|
||||
// pending set; the store's coverage is the staged run itself
|
||||
// (StagedTextureStore.h's coverage ruling).
|
||||
(void)regions;
|
||||
// (StagedTextureStore.h's coverage ruling). It is ALSO what places that run in
|
||||
// the level image when the record carries a piece of the level rather than the
|
||||
// whole of it (fix A2), which is why it is read here now.
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
if (!store.CopiesIntoServerStorage()) return;
|
||||
// The applier's gate has already faulted every shape that reaches here without
|
||||
// bytes, and under split the codec declared the run's length (Blob.Size) -
|
||||
// TextureEmit.h:1285's "the bytes this record declares ARE the level shadow".
|
||||
// TextureEmit.h's "the bytes this record declares ARE the level shadow".
|
||||
if (bytes == nullptr || record.Blob.Size == 0) return;
|
||||
const auto* stored = PipeTextureRecordForHandle(res);
|
||||
if (stored == nullptr) return;
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(res);
|
||||
const Uint16 uploadTarget = MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target);
|
||||
const IntVec3 extent = MG_Remote::Server::StagedTextureUploadExtent(stored->Desc, record);
|
||||
store.Adopt(MG_Remote::Server::StagedTextureStore::KeyForHandle(res),
|
||||
MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target), record.Level, extent,
|
||||
bytes, static_cast<SizeT>(record.Blob.Size));
|
||||
const SizeT runBytes = static_cast<SizeT>(record.Blob.Size);
|
||||
// RegionCount == 0 IS the whole-level spelling - "the run is the level shadow",
|
||||
// beginning at the level's first byte - so it is adopted with the spelling that
|
||||
// replaces the level; every other record names a RUN of the level by its own box.
|
||||
if (record.RegionCount == 0) {
|
||||
store.Adopt(key, uploadTarget, record.Level, extent, bytes, runBytes);
|
||||
return;
|
||||
}
|
||||
store.AdoptRun(key, uploadTarget, record.Level, extent,
|
||||
MG_Remote::Server::StagedTextureRunImageOffset(record, regions), bytes,
|
||||
runBytes);
|
||||
}
|
||||
|
||||
void Ops_H_TextureRespecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
|
||||
@@ -8419,6 +8430,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// derived pointer, which is what the legacy arm sends - and one named
|
||||
// line says so. Gated on subRectEligible because bpp is 0 otherwise
|
||||
// and the regions are not read at all.
|
||||
//
|
||||
// FIX A2 IS THE SECOND LEGITIMATE DISAGREEMENT, and the fallback is
|
||||
// exactly right for it: a level too large to stage whole crosses as
|
||||
// several slab records (TextureEmit.h's MGPipeForEachTextureSlab) whose
|
||||
// regions carry offsets into THEIR OWN run rather than into the level
|
||||
// shadow. Every piece of the level is merged into this one entry, the
|
||||
// entry's rects are then read out of the SERVER'S WHOLE LEVEL IMAGE -
|
||||
// which is what the derived pointer names - and those piece-relative
|
||||
// offsets are precisely the ones that must not be used here.
|
||||
Bool pendingOffsetsAgree = true;
|
||||
if (pendingUpload != nullptr && subRectEligible) {
|
||||
for (const auto& region : pendingUpload->Regions) {
|
||||
@@ -8430,7 +8450,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_E_ONCE("MGPipe: texture %u level %u carries a region whose SrcOffset "
|
||||
"%llu is not its own origin (%d,%d,%d) at the level's pitch "
|
||||
"(%llu expected) - the carried offsets are dropped and every "
|
||||
"rect is read from the shadow instead",
|
||||
"rect is read from the shadow instead. Expected once for a "
|
||||
"level cut into stage-chunk pieces, whose regions index their "
|
||||
"own run; otherwise this names a record whose regions and "
|
||||
"origins disagree",
|
||||
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), static_cast<Uint>(level),
|
||||
static_cast<unsigned long long>(region.SrcOffset), region.X,
|
||||
region.Y, region.Z,
|
||||
|
||||
Reference in New Issue
Block a user