mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Fix] (P13 W4b): the aliasing texture store follows glCopyImageSubData into a server-owned destination.
This commit is contained in:
@@ -10692,7 +10692,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_Pipe::MGPipeHandle destination, Uint32 destinationLevel,
|
||||
const IntVec3& destinationOrigin, const IntVec3& size) {
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
if (!store.CopiesIntoServerStorage()) return false;
|
||||
// P13 W4b: an aliasing store (monolith's record arm) follows too; see FollowCopy.
|
||||
if (!store.Holds()) return false;
|
||||
// The store's level key for an endpoint copy-image addresses as ONE image with its
|
||||
// layers on z: exactly one upload target (not a cube map, whose faces are six store
|
||||
// levels) and not a 1D array (the store keeps its layers as rows). A view's own levels
|
||||
|
||||
@@ -304,9 +304,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// in place: a run records the address it arrived at (minus its offset in the level) and
|
||||
// nothing is copied. The readers then ask exactly what they ask under split - is the
|
||||
// level covered, what are its bytes - and get the same answers from memory that is
|
||||
// already there. The two copy-only operations (FollowCopy, AdoptClientBoxes) stay
|
||||
// copy-only: this store never writes frontend memory, so a copy it cannot follow marks
|
||||
// the destination driver-written, which sends its readers to the driver.
|
||||
// already there. This store never writes frontend memory: a copy it follows
|
||||
// (FollowCopy) first takes the destination level into a server-owned copy, which is then
|
||||
// what AdoptClientBoxes lands the client's later boxes on - exactly the split store's
|
||||
// shape for that level - and a copy it cannot follow marks the destination
|
||||
// driver-written, which sends its readers to the driver.
|
||||
Bool Aliases() const { return !m_copies && MG_Config::RecordArmAliasesFrontend(); }
|
||||
// Does this store answer at all: a copying store, or an aliasing one.
|
||||
Bool Holds() const { return m_copies || Aliases(); }
|
||||
@@ -555,7 +557,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool FollowCopy(Uint64 srcKey, Uint16 srcTarget, Uint16 srcLevel, const IntVec3& srcOrigin,
|
||||
Uint64 dstKey, Uint16 dstTarget, Uint16 dstLevel, const IntVec3& dstOrigin,
|
||||
const IntVec3& size) {
|
||||
if (!m_copies) return false;
|
||||
if (!Holds()) return false;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_driverWritten.find(Bucketed(srcKey)) != m_driverWritten.end()) return false;
|
||||
const LevelShadow* src = FindLevel(srcKey, srcTarget, srcLevel);
|
||||
@@ -569,14 +571,22 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// Gathered first, so a copy within one level cannot read rows it has already written.
|
||||
const SizeT rowBytes = static_cast<SizeT>(size.x()) * texel;
|
||||
Vector<Uint8> rows(rowBytes * static_cast<SizeT>(size.y()) * static_cast<SizeT>(size.z()));
|
||||
const Uint8* srcBytes = LevelData(*src);
|
||||
for (Int z = 0; z < size.z(); ++z) {
|
||||
for (Int y = 0; y < size.y(); ++y) {
|
||||
std::memcpy(rows.data() + (static_cast<SizeT>(z) * size.y() + y) * rowBytes,
|
||||
src->Bytes.data() + TexelOffset(src->Extent, srcOrigin.x(), srcOrigin.y() + y,
|
||||
srcOrigin.z() + z, texel),
|
||||
srcBytes + TexelOffset(src->Extent, srcOrigin.x(), srcOrigin.y() + y,
|
||||
srcOrigin.z() + z, texel),
|
||||
rowBytes);
|
||||
}
|
||||
}
|
||||
if (dst->Alias != nullptr) {
|
||||
// P13 W4b, the aliasing mode: the destination's bytes are the frontend's level
|
||||
// shadow, which never sees the copy and which this store does not write. The level
|
||||
// becomes server-owned here, from the bytes it stood for, as under split.
|
||||
dst->Bytes.assign(dst->Alias, dst->Alias + static_cast<SizeT>(dst->DeclaredByteBound));
|
||||
dst->Alias = nullptr;
|
||||
}
|
||||
if (!LevelComplete(*dst)) {
|
||||
dst->Bytes.assign(static_cast<SizeT>(dst->DeclaredByteBound), Uint8{0});
|
||||
dst->Covered.clear();
|
||||
@@ -609,10 +619,13 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool AdoptClientBoxes(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent,
|
||||
Uint64 imageOffset, const void* bytes, SizeT byteSize,
|
||||
const ClientTexelBox* boxes, SizeT boxCount) {
|
||||
if (!m_copies || bytes == nullptr || (boxCount != 0 && boxes == nullptr)) return false;
|
||||
if (!Holds() || bytes == nullptr || (boxCount != 0 && boxes == nullptr)) return false;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
LevelShadow* shadow = FindLevelMutable(key, uploadTarget, level);
|
||||
if (shadow == nullptr || !shadow->HoldsFollowedCopy || !LevelComplete(*shadow) || shadow->Extent != extent) {
|
||||
// Alias is null on a level holding a followed copy (FollowCopy took it server-owned);
|
||||
// checked anyway, because the boxes below are written into Bytes.
|
||||
if (shadow == nullptr || !shadow->HoldsFollowedCopy || shadow->Alias != nullptr || !LevelComplete(*shadow) ||
|
||||
shadow->Extent != extent) {
|
||||
return false;
|
||||
}
|
||||
const SizeT texel = LevelTexelBytes(*shadow);
|
||||
|
||||
@@ -454,6 +454,64 @@ TEST(StagedTextureStoreTest, AFollowedCopyMovesTheWindowAndLeavesTheSourceAlone)
|
||||
}
|
||||
}
|
||||
|
||||
// P13 W4b: THE ALIASING STORE FOLLOWS TOO, WITHOUT WRITING FRONTEND MEMORY. On monolith's record
|
||||
// arm both levels are the frontend's own level shadows, held by address. The copy takes the
|
||||
// destination into a server-owned copy and moves the window there; the frontend bytes it stood for
|
||||
// stay exactly as they were, and a later client run lands only its boxes on the copy.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
TEST(StagedTextureStoreTest, AnAliasingStoreFollowsACopyIntoAServerOwnedDestination) {
|
||||
const auto savedTransport = MG_Config::Transport;
|
||||
const Bool savedArm = MG_Config::MonolithTakesRecordArm;
|
||||
MG_Config::Transport = MG_Config::TransportMode::Monolith;
|
||||
MG_Config::MonolithTakesRecordArm = true;
|
||||
struct Restore {
|
||||
MG_Config::TransportMode transport;
|
||||
Bool arm;
|
||||
~Restore() {
|
||||
MG_Config::Transport = transport;
|
||||
MG_Config::MonolithTakesRecordArm = arm;
|
||||
}
|
||||
} restore{savedTransport, savedArm};
|
||||
Server::StagedTextureStore store(/*copies=*/false);
|
||||
ASSERT_TRUE(store.Aliases());
|
||||
const Uint64 src = Server::StagedTextureStore::KeyForHandle(TestHandle(66, 1));
|
||||
const Uint64 dst = Server::StagedTextureStore::KeyForHandle(TestHandle(67, 1));
|
||||
const auto srcBytes = FollowPattern(0x00);
|
||||
auto dstFrontend = FollowPattern(0x80);
|
||||
const auto dstBefore = dstFrontend;
|
||||
store.NoteLevelDefined(src, kTex2DTarget, 0, kFollowExtent);
|
||||
store.Adopt(src, kTex2DTarget, 0, kFollowExtent, srcBytes.data(), srcBytes.size(), /*frontendShadow=*/true);
|
||||
store.NoteLevelDefined(dst, kTex2DTarget, 0, kFollowExtent);
|
||||
store.Adopt(dst, kTex2DTarget, 0, kFollowExtent, dstFrontend.data(), dstFrontend.size(), /*frontendShadow=*/true);
|
||||
ASSERT_EQ(store.RequireLevelBytes(dst, kTex2DTarget, 0, "unit"), dstFrontend.data()) << "the level is aliased";
|
||||
|
||||
ASSERT_TRUE(store.FollowCopy(src, kTex2DTarget, 0, IntVec3{1, 1, 0}, dst, kTex2DTarget, 0, IntVec3{2, 0, 0},
|
||||
IntVec3{2, 3, 1}));
|
||||
EXPECT_TRUE(store.LevelHoldsFollowedCopy(dst, kTex2DTarget, 0));
|
||||
EXPECT_EQ(dstFrontend, dstBefore) << "the copy wrote the frontend's level shadow";
|
||||
const Uint8* got = store.RequireLevelBytes(dst, kTex2DTarget, 0, "unit");
|
||||
ASSERT_NE(got, dstFrontend.data()) << "the destination is still aliased after a followed copy";
|
||||
for (Int y = 0; y < 4; ++y) {
|
||||
for (Int x = 0; x < 4; ++x) {
|
||||
const Bool copied = x >= 2 && y < 3;
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
const Uint8 want = copied ? srcBytes[At(x - 2 + 1, y + 1, c)] : dstBefore[At(x, y, c)];
|
||||
ASSERT_EQ(got[At(x, y, c)], want) << "destination texel (" << x << ", " << y << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The client then writes texel (0, 3) of its shadow: only that box lands on the copy.
|
||||
dstFrontend[At(0, 3, 0)] = 0x11;
|
||||
const Server::StagedTextureStore::ClientTexelBox box{0, 3, 0, 1, 1, 1};
|
||||
ASSERT_TRUE(store.AdoptClientBoxes(dst, kTex2DTarget, 0, kFollowExtent, 0, dstFrontend.data(), dstFrontend.size(),
|
||||
&box, 1));
|
||||
got = store.RequireLevelBytes(dst, kTex2DTarget, 0, "unit");
|
||||
EXPECT_EQ(got[At(0, 3, 0)], 0x11);
|
||||
EXPECT_EQ(got[At(2, 0, 0)], srcBytes[At(1, 1, 0)]) << "the client's box wiped the followed copy";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Every shape the store cannot address, and a source it can no longer vouch for, is REFUSED with
|
||||
// the destination untouched - the caller marks it driver-written instead. The monolith store
|
||||
// follows nothing at all.
|
||||
|
||||
Reference in New Issue
Block a user