mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Merge commit '01e00bc4' into codex/p5f-wave2
This commit is contained in:
@@ -2393,6 +2393,22 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_WIRE_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm-${fmSlot}.log")
|
||||
endforeach()
|
||||
foreach(fmCase ClipDistanceScenario.AnEnabledClipDistanceRemovesTheNegativeHalf
|
||||
ClipDistanceScenario.ADisabledClipDistanceRemovesNothing
|
||||
ClipDistanceScenario.TheEnablesAreIndependentPerDistance
|
||||
CopyImageLayeredScenario.ArrayToArrayCopiesEverySlice
|
||||
CopyImageLayeredScenario.ArrayToArrayHonoursDifferentLayerOffsets
|
||||
CopyImageLayeredScenario.VolumeToVolumeHonoursNonZeroZ
|
||||
CopyImageLayeredScenario.VolumeToVolumeAtNonZeroMipLevel
|
||||
CopyImageLayeredScenario.ArrayToVolumeCopiesEverySlice
|
||||
CopyImageLayeredScenario.VolumeToArrayCopiesEverySlice)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.Split.Fm."
|
||||
TEST_FILTER "${fmCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "integration-magma-split" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_WIRE_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm-${fmCase}.log")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# P5c ct (MG_Remote/CONTRACT-P5C.md §5): the two control records, split-only source for the
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <MG_Remote/Client/ClientSession.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Remote/Server/ServerSession.h>
|
||||
#include <MG_Remote/Server/StagedTextureStore.h>
|
||||
#include <MG_Remote/CapsCodec.h>
|
||||
#endif
|
||||
|
||||
@@ -1012,6 +1013,59 @@ namespace MobileGL::MG_Pipe {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Texture records can have a server consumer without the buffer ops table (Magma).
|
||||
// Their staged bytes still belong to the server before SEG_STAGE retires. Keep the
|
||||
// existing backend hooks authoritative when present, and use this fallback otherwise.
|
||||
void AdoptTextureWithoutBackendHook(const MGPResourceRecord& stored, const MGPSubData& upload,
|
||||
const void* bytes) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith || !bytes || upload.Blob.Size == 0) return;
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
const auto& desc = stored.Desc;
|
||||
store.Adopt(MG_Remote::Server::StagedTextureStore::KeyForHandle(upload.Res),
|
||||
MGPipeSubDataUploadTargetOf(upload.Target), upload.Level,
|
||||
MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, upload.Level),
|
||||
bytes, static_cast<SizeT>(upload.Blob.Size));
|
||||
}
|
||||
|
||||
void DefineTextureWithoutBackendHook(const MGPResourceDesc& desc, const MGPRespecifiedLevel* level) {
|
||||
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));
|
||||
};
|
||||
if (level) {
|
||||
define(MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level);
|
||||
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));
|
||||
};
|
||||
switch (static_cast<MGPipeResourceTarget>(desc.Target)) {
|
||||
case MGPipeResourceTarget::Tex1D: defineChain(TextureUploadTarget::Texture1D); break;
|
||||
case MGPipeResourceTarget::Tex2D: defineChain(TextureUploadTarget::Texture2D); break;
|
||||
case MGPipeResourceTarget::Tex3D: defineChain(TextureUploadTarget::Texture3D); break;
|
||||
case MGPipeResourceTarget::Tex1DArray: defineChain(TextureUploadTarget::Texture1DArray); break;
|
||||
case MGPipeResourceTarget::Tex2DArray: defineChain(TextureUploadTarget::Texture2DArray); break;
|
||||
case MGPipeResourceTarget::TexCube:
|
||||
for (Uint32 face = static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||
face <= static_cast<Uint32>(TextureUploadTarget::CubeMapNegativeZ); ++face)
|
||||
defineChain(static_cast<TextureUploadTarget>(face));
|
||||
break;
|
||||
case MGPipeResourceTarget::TexCubeArray: defineChain(TextureUploadTarget::CubeMapArray); break;
|
||||
case MGPipeResourceTarget::Tex2DMS: defineChain(TextureUploadTarget::Texture2DMultisample); break;
|
||||
case MGPipeResourceTarget::Tex2DMSArray: defineChain(TextureUploadTarget::Texture2DMultisampleArray); break;
|
||||
case MGPipeResourceTarget::TexRect: defineChain(TextureUploadTarget::TextureRectangle); break;
|
||||
case MGPipeResourceTarget::TexBuffer: defineChain(TextureUploadTarget::TextureBuffer); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// The texture half of resource_subdata, and it DISPATCHES TO NOBODY at GL-call time:
|
||||
// a texture write marks a level dirty and Espryt uploads it at its own sync point, out
|
||||
// of the accumulated set below. So the whole of this function is the gate, the
|
||||
@@ -1068,6 +1122,8 @@ namespace MobileGL::MG_Pipe {
|
||||
// monolith, so the monolith shape keeps P5's pointer-dropping expression exactly.
|
||||
if (g_resourceOps != nullptr && g_resourceOps->TextureSubData != nullptr) {
|
||||
g_resourceOps->TextureSubData(record.Res, record, bytes, regions);
|
||||
} else {
|
||||
AdoptTextureWithoutBackendHook(*stored, record, bytes);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
@@ -1413,6 +1469,12 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
void MGPipeApplierReleaseObjectRecords() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
(g_resourceOps == nullptr || g_resourceOps->TextureDestroy == nullptr)) {
|
||||
MG_Remote::Server::ServerStagedTexture().DropAll();
|
||||
}
|
||||
#endif
|
||||
// The served context is going away and this applier with it. Under split that is one
|
||||
// applier per served context; in the monolith there is one applier behind every
|
||||
// context, so nothing wires this - see PipeApply.h. The two serials advance here for
|
||||
@@ -1950,9 +2012,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// system. Textures only: a buffer's storage is the ops table's own Respecify hook, and
|
||||
// a renderbuffer has no levels.
|
||||
if (desc.Target != kMGPipeResourceTargetBuffer &&
|
||||
desc.Target != static_cast<Uint8>(MGPipeResourceTarget::Renderbuffer) && !metadataOnly &&
|
||||
g_resourceOps != nullptr && g_resourceOps->TextureRespecify != nullptr) {
|
||||
g_resourceOps->TextureRespecify(desc.Resource, desc, level);
|
||||
desc.Target != static_cast<Uint8>(MGPipeResourceTarget::Renderbuffer) && !metadataOnly) {
|
||||
if (g_resourceOps != nullptr && g_resourceOps->TextureRespecify != nullptr)
|
||||
g_resourceOps->TextureRespecify(desc.Resource, desc, level);
|
||||
else
|
||||
DefineTextureWithoutBackendHook(desc, level);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2160,9 +2224,12 @@ namespace MobileGL::MG_Pipe {
|
||||
// P5c (tx): with ONE exception - the staged-texture store is keyed by the handle, so
|
||||
// the death must reach it or a recycled slot's stale levels would answer for the
|
||||
// successor. This does not hand the texture to the buffer family's Destroy.
|
||||
if (static_cast<MGPipeKind>(handle.Kind) == MGPipeKind::Texture &&
|
||||
g_resourceOps != nullptr && g_resourceOps->TextureDestroy != nullptr) {
|
||||
g_resourceOps->TextureDestroy(handle.Handle);
|
||||
if (static_cast<MGPipeKind>(handle.Kind) == MGPipeKind::Texture) {
|
||||
if (g_resourceOps != nullptr && g_resourceOps->TextureDestroy != nullptr)
|
||||
g_resourceOps->TextureDestroy(handle.Handle);
|
||||
else if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
MG_Remote::Server::ServerStagedTexture().Drop(
|
||||
MG_Remote::Server::StagedTextureStore::KeyForHandle(handle.Handle));
|
||||
}
|
||||
#endif
|
||||
if (static_cast<MGPipeKind>(handle.Kind) != MGPipeKind::Buffer) return;
|
||||
|
||||
@@ -350,6 +350,74 @@ TEST(StagedTextureProductionTest, TextureSubDataThroughTheRealOpsTableCopiesAndS
|
||||
<< "a destroyed texture's staged levels must not answer for the slot's next owner";
|
||||
}
|
||||
|
||||
TEST(StagedTextureProductionTest, HooklessTextureConsumerOwnsBytesAndScopedStorageLifetime) {
|
||||
// A record-only texture consumer has no texture callbacks in the buffer ops table.
|
||||
// The empty fixture table admits records without installing any adoption callback.
|
||||
const MG_Pipe::MGPipeResourceOps emptyOps{};
|
||||
struct RestoreOps {
|
||||
const MG_Pipe::MGPipeResourceOps* Previous = MG_Pipe::MGPipeGetResourceOps();
|
||||
~RestoreOps() { MG_Pipe::MGPipeSetResourceOps(Previous); }
|
||||
} restore;
|
||||
MG_Pipe::MGPipeSetResourceOps(&emptyOps);
|
||||
auto& store = Server::ServerStagedTexture();
|
||||
const auto res = TestHandle(47, 2);
|
||||
const auto key = Server::StagedTextureStore::KeyForHandle(res);
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
desc.Width = 4;
|
||||
desc.Height = 4;
|
||||
desc.Depth = 1;
|
||||
desc.Levels = 3;
|
||||
MG_Pipe::MGPRespecifiedLevel level{};
|
||||
level.UploadTarget = MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget);
|
||||
level.Level = 0;
|
||||
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";
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
|
||||
Vector<Uint8> src(64, 0xAB);
|
||||
MG_Pipe::MGPSubData upload{};
|
||||
upload.Res = res;
|
||||
upload.Target = level.UploadTarget;
|
||||
upload.UnionBox = {0, 0, 0, 4, 4, 1};
|
||||
upload.Blob.Size = src.size();
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceSubData(upload, src.data(), nullptr));
|
||||
ASSERT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
std::fill(src.begin(), src.end(), Uint8{0xDD});
|
||||
EXPECT_EQ(store.RequireLevelBytes(key, kTex2DTarget, 0, "hookless_source_poison")[0], 0xAB);
|
||||
|
||||
// Defining another mip must retain bytes already accepted for the first mip.
|
||||
level.Level = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, &level));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 1), IntVec3(2, 2, 1));
|
||||
EXPECT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
desc.Width = 8;
|
||||
desc.Height = 8;
|
||||
desc.Immutable = 1;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, nullptr));
|
||||
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 0));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 0), IntVec3(8, 8, 1));
|
||||
EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 2), IntVec3(2, 2, 1));
|
||||
MG_Pipe::MGPHandleOnly death{};
|
||||
death.Handle = res;
|
||||
death.Kind = static_cast<Uint32>(MG_Pipe::MGPipeKind::Texture);
|
||||
MG_Pipe::MGPipeApplyResourceDestroy(death);
|
||||
EXPECT_FALSE(store.HasShadow(key));
|
||||
|
||||
// Context object release has the same ownership even without a backend hook.
|
||||
desc.Resource = TestHandle(47, 3);
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceCreate(desc));
|
||||
desc.Width = 16;
|
||||
ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, nullptr));
|
||||
const auto nextKey = Server::StagedTextureStore::KeyForHandle(desc.Resource);
|
||||
ASSERT_TRUE(store.HasShadow(nextKey));
|
||||
MG_Pipe::MGPipeApplierReleaseObjectRecords();
|
||||
EXPECT_FALSE(store.HasShadow(nextKey));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Before anything logs: MG_Util::Debug::InitFile() reads the variable once, on the first
|
||||
// write, and caches the FILE*. The name carries this process's pid, because
|
||||
|
||||
Reference in New Issue
Block a user