mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Merge] (P8): integrate wave 2 package SE - the spawn/tcp default framebuffer publishes its surface to the client, the CPU mip arm declines on a driver-written base, D's case 3 runs on the Espryt split arms, the unmigrated-emulation name list is linted against its call sites, and the P8 notes' citations point at their content again
This commit is contained in:
@@ -1470,6 +1470,13 @@ jobs:
|
||||
python3 scripts/ci/split_coverage.py --self-test
|
||||
python3 scripts/ci/split_coverage.py build-split
|
||||
|
||||
# P8-SE: PipeCatalogueTest's unmigrated-emulation list equals the MGPipeUnmigratedEmulation
|
||||
# call sites in the tree, one site per name. A source lint: no build needed.
|
||||
- name: Unmigrated-emulation list against its call sites (P8-SE)
|
||||
run: |
|
||||
python3 scripts/ci/unmigrated_emulation_sites.py --self-test
|
||||
python3 scripts/ci/unmigrated_emulation_sites.py
|
||||
|
||||
- name: Unit tests on the split runtime
|
||||
working-directory: build-split
|
||||
env:
|
||||
|
||||
@@ -13020,16 +13020,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return s_forced;
|
||||
}
|
||||
|
||||
// WireTextureReadback.inc (included further down) defines it: E's extent-scoped stand-in for a
|
||||
// driver that refuses to read a colour level back, asked here too so a host reaches the store
|
||||
// route below (P8-SE).
|
||||
static Bool WireTextureReadbackRefusedForTesting(const IntVec3& extent);
|
||||
|
||||
// The base level as tight RGB floats: the driver's texels first, the staged store second.
|
||||
//
|
||||
// P8-SE: THE STORE ANSWERS ONLY FOR A TEXTURE THE DRIVER NEVER WROTE (P8-E's sticky mark). A
|
||||
// render target's level 0 lives on the driver; the store still holds whatever crossed before
|
||||
// the draw, and filtering it would hand the chain stale texels. `driverWrittenBefore` is the
|
||||
// mark as it stood BEFORE this generation noted its own write (GenerateMipmap's split arm),
|
||||
// because that note is unconditional and would otherwise decline every store read. Anything
|
||||
// the store cannot stand for is a named decline, `declined` says why.
|
||||
static Bool ReadThreeChannelFloatLevelOnServer(MG_Pipe::MGPipeHandle mipRes,
|
||||
const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture,
|
||||
TextureTarget textureTarget, TextureUploadTarget face, Uint32 level,
|
||||
const IntVec3& extent, Vector<Float>& rgb) {
|
||||
const IntVec3& extent, Bool driverWrittenBefore,
|
||||
Vector<Float>& rgb, const char*& declined) {
|
||||
const auto format = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
|
||||
const SizeT texels = static_cast<SizeT>(extent.x()) * static_cast<SizeT>(extent.y());
|
||||
Vector<Uint8> bytes;
|
||||
if (ReadTextureLevelTight(backendTexture->GetBackendTextureId(), textureTarget, face, format,
|
||||
if (!WireTextureReadbackRefusedForTesting(extent) &&
|
||||
ReadTextureLevelTight(backendTexture->GetBackendTextureId(), textureTarget, face, format,
|
||||
static_cast<GLint>(level), extent, backendTexture->RequiresImageBindableStorage(),
|
||||
GL_RGB, GL_FLOAT, bytes) &&
|
||||
bytes.size() == texels * 3 * sizeof(Float)) {
|
||||
@@ -13037,17 +13051,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Memcpy(rgb.data(), bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
// A view's staged bytes live under its storage owner's key, in the owner's levels; a view
|
||||
// whose level the driver cannot read has no answer here.
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) return false;
|
||||
auto& store = MG_Remote::Server::ServerStagedTexture();
|
||||
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(mipRes);
|
||||
const auto faceCode = static_cast<Uint16>(face);
|
||||
const auto levelCode = static_cast<Uint16>(level);
|
||||
if (!store.CopiesIntoServerStorage() || !store.IsCovered(key, faceCode, levelCode)) return false;
|
||||
const Bool half = format == TextureInternalFormat::RGB16F;
|
||||
const SizeT componentBytes = half ? sizeof(Uint16) : sizeof(Float);
|
||||
if (store.LevelByteSize(key, faceCode, levelCode) != texels * 3 * componentBytes) return false;
|
||||
// A view's staged bytes live under its storage owner's key, in the owner's levels; a view
|
||||
// whose level the driver cannot read has no answer here.
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) {
|
||||
declined = "a view has no staged level of its own";
|
||||
} else if (!store.CopiesIntoServerStorage()) {
|
||||
declined = "this arm keeps no staged store";
|
||||
} else if (driverWrittenBefore) {
|
||||
declined = "the driver has written the texture since its bytes crossed";
|
||||
} else if (!store.IsCovered(key, faceCode, levelCode)) {
|
||||
declined = "not every byte of the level crossed";
|
||||
} else if (store.LevelByteSize(key, faceCode, levelCode) != texels * 3 * componentBytes) {
|
||||
declined = "the staged level is not the size of the level";
|
||||
}
|
||||
if (declined != nullptr) return false;
|
||||
const Uint8* staged = store.RequireLevelBytes(key, faceCode, levelCode, "generate-mipmap-server-cpu");
|
||||
rgb.resize(texels * 3);
|
||||
for (SizeT i = 0; i < texels * 3; ++i) {
|
||||
@@ -13068,7 +13091,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static void GenerateThreeChannelFloatMipmapOnServer(MG_Pipe::MGPipeHandle mipRes,
|
||||
const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture,
|
||||
const char* why) {
|
||||
Bool driverWrittenBefore, const char* why) {
|
||||
const auto& desc = record.Desc;
|
||||
const auto format = static_cast<TextureInternalFormat>(desc.InternalFormat);
|
||||
const auto textureTarget = TextureTargetForPipeResourceTarget(desc.Target);
|
||||
@@ -13103,12 +13126,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
IntVec3 extent = MG_Remote::Server::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
|
||||
desc.Depth, window.Base);
|
||||
Vector<Float> level;
|
||||
const char* declined = nullptr;
|
||||
if (!ReadThreeChannelFloatLevelOnServer(mipRes, record, backendTexture, textureTarget, face, window.Base,
|
||||
extent, level)) {
|
||||
MGLOG_E_ONCE("MGPipe: generate-mipmap-server-cpu declines texture {%u, %u}: base level %u is "
|
||||
"neither readable from the driver nor covered by the staged store; levels %u..%u are "
|
||||
"left as they are",
|
||||
mipRes.Slot, mipRes.Gen, window.Base, window.Base + 1, window.End - 1);
|
||||
extent, driverWrittenBefore, level, declined)) {
|
||||
MGLOG_E_ONCE("MGPipe: generate-mipmap-server-cpu declines texture {%u, %u}: base level %u could not "
|
||||
"be read back from the driver and the staged store cannot stand for it (%s); levels "
|
||||
"%u..%u are left as they are - DECLINED (generate-mipmap-store-declined)",
|
||||
mipRes.Slot, mipRes.Gen, window.Base, declined != nullptr ? declined : "?",
|
||||
window.Base + 1, window.End - 1);
|
||||
return;
|
||||
}
|
||||
for (Uint32 dst = window.Base + 1; dst < window.End; ++dst) {
|
||||
@@ -13178,9 +13203,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// client's level shadows. P8-B2 retired it (GenerateThreeChannelFloatMipmapOnServer reads the
|
||||
// server's own level), so no arm here reads client memory any more; the WaitClass flip itself
|
||||
// is a protocol change and is left to the integrator.
|
||||
//
|
||||
// `driverWrittenBefore`: P8-E's driver-written mark on the texture's staged store as it stood
|
||||
// before this call noted its own write - the CPU arm's store route reads it (P8-SE).
|
||||
static void GenerateMipmapByRecord(GLenum target, MG_Pipe::MGPipeHandle mipRes,
|
||||
const MG_Pipe::MGPipeResourceRecord& record,
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture) {
|
||||
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture,
|
||||
Bool driverWrittenBefore) {
|
||||
const auto format = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
|
||||
const auto textureTarget = TextureTargetForPipeResourceTarget(record.Desc.Target);
|
||||
const Bool threeChannelFloat =
|
||||
@@ -13199,7 +13228,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
if (threeChannelFloat && ForcedServerCpuMipmap()) {
|
||||
GenerateThreeChannelFloatMipmapOnServer(mipRes, record, backendTexture,
|
||||
GenerateThreeChannelFloatMipmapOnServer(mipRes, record, backendTexture, driverWrittenBefore,
|
||||
"forced by MGITEST_ESPRYT_FORCE_CPU_MIPMAP");
|
||||
return;
|
||||
}
|
||||
@@ -13229,7 +13258,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
refused = g_GLESFuncs.glGetError();
|
||||
}
|
||||
if (refused == GL_NO_ERROR) return;
|
||||
GenerateThreeChannelFloatMipmapOnServer(mipRes, record, backendTexture, "the driver refused glGenerateMipmap");
|
||||
GenerateThreeChannelFloatMipmapOnServer(mipRes, record, backendTexture, driverWrittenBefore,
|
||||
"the driver refused glGenerateMipmap");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -13256,6 +13286,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// for GenerateMipmap move to kWaitNone.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const auto mipRes = MG_Pipe::MGPipeApplier().VerbMipRes;
|
||||
// P8-SE: whether the staged store still stands for the texture is read BEFORE the note
|
||||
// below sets the mark, or the CPU arm's store route could never answer at all.
|
||||
const Bool driverWrittenBefore = MG_Remote::Server::ServerStagedTexture().IsDriverWritten(
|
||||
MG_Remote::Server::StagedTextureStore::KeyForHandle(mipRes));
|
||||
// Every arm GenerateMipmapByRecord takes writes the levels above the base on the
|
||||
// driver only (P8-B2's CPU arm uploads its levels there too), so the note is
|
||||
// unconditional.
|
||||
@@ -13268,7 +13302,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
mipRes.Slot, mipRes.Gen);
|
||||
return;
|
||||
}
|
||||
GenerateMipmapByRecord(target, mipRes, *record, backendTexture);
|
||||
GenerateMipmapByRecord(target, mipRes, *record, backendTexture, driverWrittenBefore);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -13545,14 +13579,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// ordinary correct path and must still run.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return;
|
||||
#endif
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P4a (D-M). glCopyImageSubData's CPU-shadow mirror copies the source level's shadow
|
||||
// rows into the DESTINATION's shadow so a later readback of the destination sees what
|
||||
// the GPU copy put there. Both shadows are the client's, so this is the clearest case
|
||||
// in the family of an emulation that cannot survive a split; ROADMAP puts the move
|
||||
// itself in P8 and this names the site until then.
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("copy-image-shadow-mirror");
|
||||
#endif
|
||||
// P4a (D-M) named this site `copy-image-shadow-mirror` with MGPipeUnmigratedEmulation.
|
||||
// P8-SE removed the call: the return above means no transport reaches it, and on a
|
||||
// monolith arm the call was a no-op (PipeCatalogueTest's list and the lint
|
||||
// scripts/ci/unmigrated_emulation_sites.py follow).
|
||||
|
||||
const auto srcUploadTarget = srcEndpoint.Texture->GetUploadTargets()[0];
|
||||
const auto dstUploadTarget = dstEndpoint.Texture->GetUploadTargets()[0];
|
||||
@@ -15293,10 +15323,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Session::Fail entirely, which is the funnel the census gate watches.
|
||||
//
|
||||
// THE FUNNEL KEEPS ITS TEETH FOR THE OTHER SITES: MGPipeUnmigratedEmulation still
|
||||
// names generate-mipmap-storage, generate-mipmap-cpu-fallback and
|
||||
// copy-image-shadow-mirror, on monolith arms only. Only this call site is retired here,
|
||||
// and only because its own premise did not hold (texture-remint-pull went the same way
|
||||
// in P9 W2, and P8-B the two split generate-mipmap names, for the same reason).
|
||||
// names generate-mipmap-storage and generate-mipmap-cpu-fallback, on monolith arms only
|
||||
// (P8-SE dropped copy-image-shadow-mirror, a site no transport reached). Only this call
|
||||
// site is retired here, and only because its own premise did not hold (texture-remint-pull
|
||||
// went the same way in P9 W2, and P8-B the two split generate-mipmap names, likewise).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (shadow == nullptr) {
|
||||
MGLOG_E_ONCE("GetTexImage: no level shadow on this side for target=0x%x level=%d, and under an "
|
||||
@@ -16198,7 +16228,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// resizing depth/stencil without colour would report the default framebuffer incomplete.
|
||||
static void PublishDefaultFramebufferDepthStencilFormat() {
|
||||
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P8-SE: THE PLACEHOLDER IS ONLY THE MONOLITH ARM'S TARGET. With a transport the answer is
|
||||
// posted (OnSurfaceChanged, below) and the client writes its own placeholder; a spawn or tcp
|
||||
// server process has no frontend at all, so pDefaultFramebufferInfo is null there. Gating on
|
||||
// it returned before the post, the client kept Init.cpp's GL_DEPTH32F_STENCIL8 guess for a
|
||||
// D24S8 pbuffer, and a blit into a buffer allocated from that guess lost depth and stencil
|
||||
// (notes/p8/SE.md). Inproc only passed because client and server share the process.
|
||||
if (!g_GLESFuncs.glGetFramebufferAttachmentParameteriv) return;
|
||||
if (!defaultFBOInfo && MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged == nullptr) return;
|
||||
#else
|
||||
if (!defaultFBOInfo || !g_GLESFuncs.glGetFramebufferAttachmentParameteriv) return;
|
||||
#endif
|
||||
|
||||
// GL_DEPTH / GL_STENCIL are the default framebuffer's spellings; a user framebuffer
|
||||
// would need GL_DEPTH_ATTACHMENT / GL_STENCIL_ATTACHMENT and answers GL_INVALID_ENUM
|
||||
@@ -16279,8 +16320,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
stencilFormat = TextureInternalFormat::StencilIndex8;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// Null in a spawn / tcp server (above); only the monolith arm below writes through them.
|
||||
auto* depthTexture = defaultFBOInfo ? defaultFBOInfo->depthAttachment.get() : nullptr;
|
||||
auto* stencilTexture = defaultFBOInfo ? defaultFBOInfo->stencilAttachment.get() : nullptr;
|
||||
#else
|
||||
auto* depthTexture = defaultFBOInfo->depthAttachment.get();
|
||||
auto* stencilTexture = defaultFBOInfo->stencilAttachment.get();
|
||||
#endif
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged != nullptr) {
|
||||
// P5c ev (CONTRACT-P5C §4.2): with an active transport the attachments are CLIENT
|
||||
|
||||
@@ -4864,6 +4864,44 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# P8-SE (docs/Disaggregated/notes/p8/SE.md): Espryt split residuals.
|
||||
if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
# The default framebuffer's depth/stencil format reaches a spawn / tcp client now (the server
|
||||
# process has no frontend placeholder, and the publish used to return on it before posting),
|
||||
# so the case A left as `pending-fix` joins the three Espryt arms.
|
||||
mgl_itest_register_split_arms(
|
||||
"DepthStencilReadbackAttachmentShapeScenario.DefaultFramebufferDepthStencilFormatIsBlitCompatible" "")
|
||||
# B2's CPU mip arm x P8-E's driver-written mark: the forced-CPU knob plus E's refused-readback
|
||||
# knob at the base level's extent (16x16), both read by the SERVER - so inproc and spawn only,
|
||||
# and their own label as B's `CpuMip.` entries have (the tcp arm's server is the shared
|
||||
# TcpServer.Start fixture, and the refusal there would reach every 16x16 colour readback).
|
||||
foreach(mglItestArm IN ITEMS Split Spawn)
|
||||
string(TOUPPER "${mglItestArm}" mglItestArmUpper)
|
||||
string(TOLOWER "${mglItestArm}" mglItestArmLower)
|
||||
foreach(cpuMipCase Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName
|
||||
Rgb16fUploadedBaseWithARefusedReadbackIsFilteredFromTheStore)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectGLES.${mglItestArm}.CpuMipRefused."
|
||||
TEST_FILTER "GenerateMipmapServerScenario.${cpuMipCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES
|
||||
LABELS "integration-gpu\\;integration-cpu-mip-${mglItestArmLower}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_GLES_${mglItestArmUpper}_ENVIRONMENT}\;MGITEST_ESPRYT_FORCE_CPU_MIPMAP=1\;MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=16x16\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p8se-cpu-mip-refused-${mglItestArmLower}-${cpuMipCase}.log")
|
||||
endforeach()
|
||||
endforeach()
|
||||
# PipeCatalogueTest's unmigrated-emulation list against the call sites in the tree (a source
|
||||
# lint, so the unit lane runs it wherever the split build's unit tests run; CI also runs it
|
||||
# beside split_coverage.py).
|
||||
add_test(NAME PipeCatalogue.UnmigratedEmulationSitesSelfTest
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/scripts/ci/unmigrated_emulation_sites.py" --self-test)
|
||||
add_test(NAME PipeCatalogue.UnmigratedEmulationSitesMatchTheList
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/scripts/ci/unmigrated_emulation_sites.py"
|
||||
"${CMAKE_SOURCE_DIR}")
|
||||
set_tests_properties(PipeCatalogue.UnmigratedEmulationSitesSelfTest
|
||||
PipeCatalogue.UnmigratedEmulationSitesMatchTheList PROPERTIES LABELS unit TIMEOUT 60)
|
||||
endif()
|
||||
|
||||
# =====================================================================================
|
||||
# P7 PACKAGE L: THE INFORMATIONAL MAGMA CENSUS - TIER 2 OF TWO
|
||||
# =====================================================================================
|
||||
|
||||
@@ -174,10 +174,10 @@ set(MGL_P8A_KNOB_ENV_NoViewportArrayEmulation "MOBILEGL_ESPRYT_FORCE_VIEWPORT_AR
|
||||
set(MGL_P8A_KNOB_FILTER_WidenedPacked16 "CopyImagePacked16Scenario.*")
|
||||
set(MGL_P8A_KNOB_ENV_WidenedPacked16 "MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE=1")
|
||||
# ForcedDs keeps the spelling the existing Split/Spawn DepthStencilReadbackMatrix block uses. The
|
||||
# default-framebuffer case is left out on every arm: red on spawn and tcp with or without the
|
||||
# knob (pending-fix row in the exemption table).
|
||||
# default-framebuffer case was left out here while it was red on spawn and tcp; P8-SE fixed it
|
||||
# (notes/p8/SE.md) and it runs under the knob on every arm like the rest.
|
||||
set(MGL_P8A_KNOB_FILTER_ForcedDs
|
||||
"DepthStencilReadbackScenario.*:DepthStencilReadbackAttachmentShapeScenario.*-DepthStencilReadbackAttachmentShapeScenario.DefaultFramebufferDepthStencilFormatIsBlitCompatible")
|
||||
"DepthStencilReadbackScenario.*:DepthStencilReadbackAttachmentShapeScenario.*")
|
||||
set(MGL_P8A_KNOB_ENV_ForcedDs "MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION=1")
|
||||
# The Matrix scenario's Tcp copy moves HERE from the ForcedDs block (same names): that block
|
||||
# put the knob in the client's environment only, so its Tcp arm ran the native readback path.
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
// Espryt's server-side CPU filter, which a host driver never needs: llvmpipe generates both
|
||||
// formats natively. Under the knob the server log must name that arm, or the entry proved nothing.
|
||||
//
|
||||
// THE CPU ARM'S STORE ROUTE (P8-SE). When the driver cannot read the base level back, the server's
|
||||
// CPU arm filters the staged store's copy of it - but only for a texture the driver never wrote
|
||||
// (P8-E's sticky mark), because a render target's level 0 exists only on the driver and the store
|
||||
// still holds what crossed before the draw. The `CpuMipRefused.` entries add
|
||||
// MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=16x16 (P8-E's knob, also read by the SERVER) to the
|
||||
// forced-CPU knob: a render-target base must decline by name (generate-mipmap-store-declined) and
|
||||
// never filter the stale store, and an uploaded-only base must still be filtered from the store.
|
||||
//
|
||||
// MONOLITH DEFECTS, recorded for dev (ID-P8-3) and skipped by name on the monolith arm they live
|
||||
// on unless MGITEST_RUN_MONOLITH_MIP_DEFECTS=1 is set (which is how each was shown red):
|
||||
// M1 Espryt: the RGB16F / RGB32F CPU filter reads the frontend's level shadow, which a GPU
|
||||
@@ -68,6 +76,10 @@ namespace MGITest {
|
||||
constexpr const char* kForceCpuMipmap = "MGITEST_ESPRYT_FORCE_CPU_MIPMAP";
|
||||
constexpr const char* kCpuMipmapMarker = "generate-mipmap-server-cpu";
|
||||
constexpr const char* kRunMonolithDefects = "MGITEST_RUN_MONOLITH_MIP_DEFECTS";
|
||||
// P8-SE: E's server-read knob; the entries set it to kEdge x kEdge, the base level's extent.
|
||||
constexpr const char* kRefuseReadback = "MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT";
|
||||
constexpr const char* kStoreDeclinedMarker = "generate-mipmap-store-declined";
|
||||
constexpr const char* kStoreFilteredMarker = "filtering the staged store's copy";
|
||||
|
||||
constexpr const char* kVertexSource = R"(#version 430 core
|
||||
void main() {
|
||||
@@ -123,6 +135,7 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
|
||||
m_wire = SplitRuntimeSkipReason().empty();
|
||||
m_espryt = Gl().BackendName() == "DirectGLES";
|
||||
m_forcedCpu = std::getenv(kForceCpuMipmap) != nullptr;
|
||||
m_refusedReadback = std::getenv(kRefuseReadback) != nullptr;
|
||||
std::string error;
|
||||
m_sampleProgram = CompileProgram(kVertexSource, kSampleSource, &error);
|
||||
ASSERT_NE(m_sampleProgram, 0u) << error;
|
||||
@@ -368,6 +381,84 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
|
||||
// upload, so the chain must come from the GPU's level 0 and never from the uploaded texels.
|
||||
void RenderTargetBase(const Format& format) {
|
||||
if (MonolithDefect(true, false)) GTEST_SKIP() << kM1;
|
||||
PaintSentinelBaseAsRenderTarget(format);
|
||||
if (HasFatalFailure() || IsSkipped()) return;
|
||||
Generate();
|
||||
const int last = 4;
|
||||
for (int level = 0; level < last; ++level)
|
||||
ExpectHalves(SampleLevel(level, last), format, kBottom, kTop, 0, 0,
|
||||
std::string(format.name) + " render-target base, level " + std::to_string(level));
|
||||
ExpectCpuArmNamed();
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
}
|
||||
|
||||
// ---- P8-SE: the CPU arm's store route under a refused readback ----------------------
|
||||
|
||||
// Only the `CpuMipRefused.` entries carry both server knobs; anywhere else the store route
|
||||
// is unreachable on a host (its driver reads every level back) and the case says so.
|
||||
void SkipUnlessTheStoreRouteIsArmed() {
|
||||
if (m_wire && m_espryt && m_forcedCpu && m_refusedReadback) return;
|
||||
GTEST_SKIP() << "the store route needs Espryt's split server with " << kForceCpuMipmap << " and "
|
||||
<< kRefuseReadback << "=" << kEdge << "x" << kEdge << " (the CpuMipRefused. entries)";
|
||||
}
|
||||
|
||||
// The render-target base again, with the driver refusing to read level 0 back: the store
|
||||
// holds the uploaded sentinel, the driver the drawn halves, and P8-E's mark says so. The
|
||||
// arm must decline by name - a stale chain of sentinel texels is the defect.
|
||||
void RefusedReadbackOfADrawnBase(const Format& format) {
|
||||
SkipUnlessTheStoreRouteIsArmed();
|
||||
if (IsSkipped()) return;
|
||||
PaintSentinelBaseAsRenderTarget(format);
|
||||
if (HasFatalFailure() || IsSkipped()) return;
|
||||
Generate();
|
||||
const std::string log = PipeStatsWindow::ReadServerLogSince(m_mark);
|
||||
EXPECT_NE(log.find(kStoreDeclinedMarker), std::string::npos)
|
||||
<< "the base level was drawn and its readback refused, but the server log after glGenerateMipmap "
|
||||
"has no '" << kStoreDeclinedMarker << "' line";
|
||||
EXPECT_EQ(log.find(kStoreFilteredMarker), std::string::npos)
|
||||
<< "the CPU arm filtered the staged store's copy of a level the driver has since drawn over";
|
||||
const int last = 4;
|
||||
ExpectHalves(SampleLevel(0, last), format, kBottom, kTop, 0, 0,
|
||||
std::string(format.name) + " drawn base (untouched by the decline)");
|
||||
for (int level = 1; level < last; ++level) {
|
||||
const Image image = SampleLevel(level, last);
|
||||
const Rgba8 got = image.At(0, 0);
|
||||
EXPECT_FALSE(std::abs(got.r - ToByte(kSentinel[0])) <= 2 && std::abs(got.g - ToByte(kSentinel[1])) <= 2 &&
|
||||
std::abs(got.b - ToByte(kSentinel[2])) <= 2)
|
||||
<< format.name << " level " << level << " is the uploaded sentinel " << got
|
||||
<< ": the chain was filtered from the stale staged store";
|
||||
}
|
||||
ExpectCpuArmNamed();
|
||||
FirstGLError();
|
||||
}
|
||||
|
||||
// The control: a base that only ever crossed as an upload IS what the store holds, so a
|
||||
// refused readback is answered from the store and the chain is right. This is what tells
|
||||
// the check above from "the store route always declines" - the generation's own
|
||||
// driver-write note must not count against the level it reads.
|
||||
void RefusedReadbackOfAnUploadedBase(const Format& format) {
|
||||
SkipUnlessTheStoreRouteIsArmed();
|
||||
if (IsSkipped()) return;
|
||||
NewTexture();
|
||||
DefineLevel(format, 0, kEdge, Halves(format, kEdge, kBottom, kTop, 0, 0));
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "define level 0";
|
||||
Generate();
|
||||
const std::string log = PipeStatsWindow::ReadServerLogSince(m_mark);
|
||||
EXPECT_NE(log.find(kStoreFilteredMarker), std::string::npos)
|
||||
<< "the uploaded base's readback was refused, but the server log has no '" << kStoreFilteredMarker
|
||||
<< "' line: the store route did not answer";
|
||||
EXPECT_EQ(log.find(kStoreDeclinedMarker), std::string::npos)
|
||||
<< "the store route declined a level that only ever crossed as an upload";
|
||||
const int last = 4;
|
||||
for (int level = 1; level < last; ++level)
|
||||
ExpectHalves(SampleLevel(level, last), format, kBottom, kTop, 0, 0,
|
||||
std::string(format.name) + " uploaded base, store-filtered level " +
|
||||
std::to_string(level));
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
}
|
||||
|
||||
// Level 0 uploaded as the sentinel, then drawn over as a render target (bottom / top halves).
|
||||
void PaintSentinelBaseAsRenderTarget(const Format& format) {
|
||||
NewTexture();
|
||||
DefineLevel(format, 0, kEdge, Flat(format, kEdge, kSentinel, 0));
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
@@ -390,18 +481,12 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "draw into level 0";
|
||||
Generate();
|
||||
const int last = 4;
|
||||
for (int level = 0; level < last; ++level)
|
||||
ExpectHalves(SampleLevel(level, last), format, kBottom, kTop, 0, 0,
|
||||
std::string(format.name) + " render-target base, level " + std::to_string(level));
|
||||
ExpectCpuArmNamed();
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
}
|
||||
|
||||
bool m_wire = false;
|
||||
bool m_espryt = false;
|
||||
bool m_forcedCpu = false;
|
||||
bool m_refusedReadback = false;
|
||||
GLuint m_sampleProgram = 0;
|
||||
GLuint m_paintProgram = 0;
|
||||
GLuint m_vao = 0;
|
||||
@@ -457,4 +542,14 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
|
||||
RenderTargetBase(kRgb32F);
|
||||
}
|
||||
|
||||
// P8-SE: the CPU arm's store route (header; the CpuMipRefused. entries).
|
||||
TEST_F(GenerateMipmapServerScenario, Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName) {
|
||||
if (!Ready()) return;
|
||||
RefusedReadbackOfADrawnBase(kRgb16F);
|
||||
}
|
||||
TEST_F(GenerateMipmapServerScenario, Rgb16fUploadedBaseWithARefusedReadbackIsFilteredFromTheStore) {
|
||||
if (!Ready()) return;
|
||||
RefusedReadbackOfAnUploadedBase(kRgb16F);
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
@@ -296,13 +296,16 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
|
||||
EXPECT_EQ(delta.barriers, 2u) << "one INDIRECT_COMMAND_READ barrier per freshly written store";
|
||||
}
|
||||
|
||||
// The COUNT word a compute shader wrote. Magma only: Espryt reads that word from a
|
||||
// server shadow GPU writes do not update, on both arms (OPEN-QUESTIONS 15, P8-C's
|
||||
// package), so its answer here is a known divergence rather than this package's claim.
|
||||
// The COUNT word a compute shader wrote. Espryt reads that word on the CPU from the
|
||||
// server's staged copy of the parameter buffer; P8-C refreshes that copy after a shader
|
||||
// write (SplitHostBytesForCpuRead), so its split arms answer here too (P8-SE). Espryt's
|
||||
// MONOLITH arm still reads the frontend shadow without a sync (OPEN-QUESTIONS 15, C.md's
|
||||
// monolith defects), left to P13 (ID-P8-13).
|
||||
TEST_F(WireIndirectDrawScenario, ComputeWrittenCountWordIsTheOneTheGpuReads) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma) {
|
||||
GTEST_SKIP() << "Espryt reads the count word from its shadow on both arms (OQ15, P8-C)";
|
||||
if (!m_magma && m_transport == "monolith") {
|
||||
GTEST_SKIP() << "Espryt's monolith arm reads the count word from the frontend shadow without "
|
||||
"a sync (OQ15; recorded for P13, ID-P8-13)";
|
||||
}
|
||||
Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, std::vector<ElementsCommand>{Elements(0), Elements(3), Elements(2)});
|
||||
Fill(m_parameter, GL_PARAMETER_BUFFER, std::vector<std::uint32_t>{3});
|
||||
|
||||
@@ -1423,7 +1423,9 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) {
|
||||
// gate rather than a surprise three phases later.
|
||||
//
|
||||
// The names are pinned here rather than counted in the backend, because the count alone cannot
|
||||
// say WHICH one was lost. The purity gate greps the count; this says what the count is of.
|
||||
// say WHICH one was lost. P8-SE: scripts/ci/unmigrated_emulation_sites.py holds this list against
|
||||
// the `MGPipeUnmigratedEmulation("...")` call sites in the tree - equal sets, one site per name -
|
||||
// because nothing did before and the list drifted twice unseen (P8-B, and P8-SE's dead name).
|
||||
TEST(PipeCatalogue, EveryUnmigratedEmulationIsNamedOnce) {
|
||||
// Every one of these is an emulation that reads or writes CLIENT memory a split server
|
||||
// would not have: a CPU shadow mirror, a CPU mipmap fallback and a shadow-conversion
|
||||
@@ -1440,16 +1442,18 @@ TEST(PipeCatalogue, EveryUnmigratedEmulationIsNamedOnce) {
|
||||
// had: "generate-mipmap-cpu-filter", the split arm's RGB16F/RGB32F refusal in
|
||||
// GenerateMipmapByRecord. P8-B2 retired it (the server filters its own level,
|
||||
// GenerateThreeChannelFloatMipmapOnServer), and P8-B1 retired the split arm's two
|
||||
// "generate-mipmap-storage" sites (the record's plan is the window). What is left is these
|
||||
// four names, one call site each, and every one of them is on a MONOLITH arm - the split
|
||||
// arms no longer reach an unmigrated emulation (notes/p8/B.md).
|
||||
// "generate-mipmap-storage" sites (the record's plan is the window). Every name left is on a
|
||||
// MONOLITH arm - the split arms no longer reach an unmigrated emulation (notes/p8/B.md).
|
||||
//
|
||||
// THREE, NOT FOUR (P8-SE). "copy-image-shadow-mirror" sat after the mirror's own
|
||||
// `Transport != Monolith` return, so no transport ever reached it and on monolith it was a
|
||||
// no-op; the call and the name went together (notes/p8/SE.md).
|
||||
const char* const kNames[] = {
|
||||
"copy-image-shadow-mirror", // the glCopyImageSubData CPU-shadow mirror
|
||||
"generate-mipmap-storage", // EnsureGenerateMipmapStorageAllocated (monolith grow)
|
||||
"generate-mipmap-cpu-fallback", // GenerateThreeChannelFloatMipmapOnCpu
|
||||
"get-tex-image-shadow", // GetTexImageViaShadowConversion
|
||||
};
|
||||
EXPECT_EQ(std::size(kNames), 4u);
|
||||
EXPECT_EQ(std::size(kNames), 3u);
|
||||
// No duplicates: two sites sharing a name would make the grepped count and this list
|
||||
// disagree in the one direction nobody would notice.
|
||||
for (SizeT i = 0; i < std::size(kNames); ++i) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
- 门改为「归一化后 monolith ⊆ 门控 split ∪ 豁免表」,由 `scripts/ci/split_coverage.py` 强制;`test.yml` 在 parity 步之后跑它(含 `--self-test`)。
|
||||
- 当前读数:DirectGLES monolith 669 例 = 门控 split 581 + 豁免 88,缺 0;DirectVulkan 669 = 门控 202 + 仅信息层 435 + 豁免 32,缺 0。
|
||||
- 按 gtest 名只在 monolith 的 DirectGLES 用例是 335(审计的 396 按 TEST_F 计,含它自己列出的约 45 个已带尾登记在 split 的「命名假象」);去掉机制过滤器(`MobileGL/MG_IntegrationTest/CMakeLists.txt:792`)的 31 例后 304 例进普查。
|
||||
- 按 gtest 名只在 monolith 的 DirectGLES 用例是 335(审计的 396 按 TEST_F 计,含它自己列出的约 45 个已带尾登记在 split 的「命名假象」);去掉机制过滤器(`MobileGL/MG_IntegrationTest/CMakeLists.txt:796`)的 31 例后 304 例进普查。
|
||||
- 本包新登记:246 个 ambient 用例 × 三臂;5 条 server 旋钮车道共 21 例 × 三臂(tcp 各有自己的 supervisor);撤 `ClientSideIndices` 排除(d1 块三臂 + 5 个 multi-draw tier);GuiBatch 随 ambient 登记。
|
||||
- 审计 (c′) 的「进程内 peek」类实测为 0:P4aFinalFix / P4aSeamAudit 在 spawn / tcp 上 white-box 读取自行降级,public-GL 半边照跑,三臂全绿,已登记。
|
||||
- DoublePrecision 12 例在同环境的 loopback tcp 上绿,已登记;`DEBTS.md` 的 fp64 行说的是 server 环境与 client 不同(`MOBILEGL_ADVERTISE_FP64`),本机没测到那一形。
|
||||
@@ -34,9 +34,9 @@
|
||||
| `WidenedPacked16.` | `MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE=1` | 3 | `MobileGL/ConfigLoader.cpp:264` | 绿(无 arming 例,走的是默认臂) | 绿 |
|
||||
| `ForcedDs.` | `MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION=1` | 9(tcp 另含 Matrix 14) | `MobileGL/ConfigLoader.cpp:249` | 绿(无 arming 例,走的是原生回读) | 绿 |
|
||||
|
||||
- 共享 fixture 不能带这些旋钮:会改掉所有 tcp 例(视口仿真关掉即 ViewportArray 正例红)。先例 `MobileGL/MG_IntegrationTest/CMakeLists.txt:2164` 只对三臂本来都设的开关成立。
|
||||
- 共享 fixture 不能带这些旋钮:会改掉所有 tcp 例(视口仿真关掉即 ViewportArray 正例红)。先例 `MobileGL/MG_IntegrationTest/CMakeLists.txt:2168` 只对三臂本来都设的开关成立。
|
||||
- supervisor 端口 = lane 端点端口 + `MOBILEGL_ITEST_TCP_KNOB_PORT_OFFSET`(默认 1000)+ 车道序号;fixture 名 `mobilegl-tcp-p8a-<lane>`;条目仍持 `RESOURCE_LOCK mobilegl-tcp`(parity / junit_tally 的 tcp 锁规则)。
|
||||
- 既有 `DirectGLES.Tcp.ForcedDs.DepthStencilReadbackMatrixScenario.*` 14 例原先旋钮只在 client 环境(server 跑原生回读);同名条目移到 `ForcedDs` supervisor,旧块只留 Split / Spawn(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2648`)。
|
||||
- 既有 `DirectGLES.Tcp.ForcedDs.DepthStencilReadbackMatrixScenario.*` 14 例原先旋钮只在 client 环境(server 跑原生回读);同名条目移到 `ForcedDs` supervisor,旧块只留 Split / Spawn(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2652`)。
|
||||
- 5 个旋钮都是 Espryt 的,车道不上 DirectVulkan。
|
||||
|
||||
## 4. 豁免表 `scripts/data/split_coverage_exemptions.txt`
|
||||
@@ -58,9 +58,9 @@
|
||||
| `split_coverage.py` 按名 | 从 `P8Coverage.cmake` 删 `GuiBatchScenario.*` 一行,重 cmake + ninja | `split_coverage.py build-split` | rc 1,`missing 9`,逐个点名 `DirectGLES.GuiBatchScenario.*` |
|
||||
| 过期行规则 | `elif needed == 0` → `elif False` | `--self-test` | rc 1,「a stale exemption reds: rc 0 (want 1)」 |
|
||||
| 旋钮 supervisor | 5 个 fixture 的 `ENVIRONMENT` 去掉旋钮,重 cmake | 三条车道的 `DirectGLES.Tcp.` 条目(15 例) | 红 4(§3 三车道的 arming / 负控);恢复后 15 绿 |
|
||||
| `ClientSideIndices` 守住 client 拼平 | `MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h:51` 由清 `kDrawHasUserIndices` 改为置位 | `ctest -R ClientSideIndicesBatchMatchesUnrolledDraws` | Split / Spawn / Tcp 与 5 个 tier、Magma 各 split 臂共 14 例 abort:client `Fatal{ProtocolCorruption, "EncodeRecord.tailBytes"} got=0 expected=32`(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1107`);monolith 两后端绿 |
|
||||
| `ClientSideIndices` 守住 client 拼平 | `MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h:51` 由清 `kDrawHasUserIndices` 改为置位 | `ctest -R ClientSideIndicesBatchMatchesUnrolledDraws` | Split / Spawn / Tcp 与 5 个 tier、Magma 各 split 臂共 14 例 abort:client `Fatal{ProtocolCorruption, "EncodeRecord.tailBytes"} got=0 expected=32`(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1108`);monolith 两后端绿 |
|
||||
|
||||
- 第 4 行不是 brief 预期的 `PipeApplier.cpp:972-975` 闩:client 已无产生 user-index span 的代码,只把第 51 行删掉(不清位)全绿;而 server 解码在进 sink 前对 `NumDraws != 1` 的 span 报 `ProtocolCorruption "DrawVbo.userIndices.shape"`(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:622-623`,调用 `:2412`),所以 `CLIENT_INDICES` 闩对任何 peer 都不可达;`+INSTANCED` 闩(`MobileGL/MG_Remote/Server/PipeApplier.cpp:976`)仍可达。交 P8-F。
|
||||
- 第 4 行不是 brief 预期的 `PipeApplier.cpp:975-978` 闩:client 已无产生 user-index span 的代码,只把第 51 行删掉(不清位)全绿;而 server 解码在进 sink 前对 `NumDraws != 1` 的 span 报 `ProtocolCorruption "DrawVbo.userIndices.shape"`(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:623-624`,调用 `:2413`),所以 `CLIENT_INDICES` 闩对任何 peer 都不可达;`+INSTANCED` 闩(`MobileGL/MG_Remote/Server/PipeApplier.cpp:979`)仍可达。交 P8-F。
|
||||
- 车道耗时(本树,`-j 6`,七棵树同时在跑):
|
||||
|
||||
| 车道 | 前 | 后 |
|
||||
@@ -70,19 +70,19 @@
|
||||
| integration-tcp | 348 例 101 s | 626 例 248 s |
|
||||
| integration-magma-all-split / spawn / tcp(信息层) | — | 556 / 556 / 558 例,45 / 57 / 199 s |
|
||||
|
||||
- Magma 门控车道本包未加;新登记经 tier-2 回放自动进入 `integration-magma-all-*`(`MobileGL/MG_IntegrationTest/CMakeLists.txt:4724`)。
|
||||
- Magma 门控车道本包未加;新登记经 tier-2 回放自动进入 `integration-magma-all-*`(`MobileGL/MG_IntegrationTest/CMakeLists.txt:4952`)。
|
||||
|
||||
## 6. 发现
|
||||
|
||||
| 项 | 臂 / 后端 | 证据 | 去向 |
|
||||
|---|---|---|---|
|
||||
| `DepthStencilReadbackAttachmentShapeScenario.DefaultFramebufferDepthStencilFormatIsBlitCompatible`:默认 framebuffer 的深度 blit 出来是 0 | Espryt spawn + tcp(inproc 绿;Magma 三臂绿;带不带 FORCE_DS 都红) | 普查 junit;server 是 pbuffer 128×96,EGL config 要了 depth 24 / stencil 8(`MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:16146`),根因未追到 | pending-fix;需集成方加 `DEBTS.md` 行(不属 B / C / D / E) |
|
||||
| tcp 共享 supervisor 没有 Vulkan ICD 与 iterationRP 修复开关(`MobileGL/MG_IntegrationTest/CMakeLists.txt:344-347` 只进 client 环境) | Magma tcp(tier 2 / tier 3) | `DirectVulkan.Tcp{,.Full}.IterationRPProgram203Scenario` 差 1 texel,Split / Spawn 绿 | 已修(测试接线):`TcpServer.Start` 带上 `MGL_ITEST_VULKAN_ENV` 中非 common 的项(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2159-2165`);IterationRP 全部 78 条绿 |
|
||||
| `DepthStencilReadbackAttachmentShapeScenario.DefaultFramebufferDepthStencilFormatIsBlitCompatible`:默认 framebuffer 的深度 blit 出来是 0 | Espryt spawn + tcp(inproc 绿;Magma 三臂绿;带不带 FORCE_DS 都红) | 普查 junit;server 是 pbuffer 128×96,EGL config 要了 depth 24 / stencil 8(`MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:16498`),根因未追到 | pending-fix;需集成方加 `DEBTS.md` 行(不属 B / C / D / E) |
|
||||
| tcp 共享 supervisor 没有 Vulkan ICD 与 iterationRP 修复开关(`MobileGL/MG_IntegrationTest/CMakeLists.txt:348-351` 只进 client 环境) | Magma tcp(tier 2 / tier 3) | `DirectVulkan.Tcp{,.Full}.IterationRPProgram203Scenario` 差 1 texel,Split / Spawn 绿 | 已修(测试接线):`TcpServer.Start` 带上 `MGL_ITEST_VULKAN_ENV` 中非 common 的项(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2163-2169`);IterationRP 全部 78 条绿 |
|
||||
| `CLIENT_INDICES` server 闩不可达 | 两后端 | §5 | P8-F |
|
||||
| monolith 缺陷 | — | 本包未发现 | — |
|
||||
|
||||
## 7. 给集成方
|
||||
|
||||
- `P8Coverage.cmake` 的 include(`MobileGL/MG_IntegrationTest/CMakeLists.txt:4671-4674`)必须在 tier-2 回放与 `SplitLogPaths` 的 `configure_file`(`:5079`)之前;解冲突时不要移到它们下面。
|
||||
- `P8Coverage.cmake` 的 include(`MobileGL/MG_IntegrationTest/CMakeLists.txt:4826-4829`)必须在 tier-2 回放与 `SplitLogPaths` 的 `configure_file`(`:5307`)之前;解冲突时不要移到它们下面。
|
||||
- `gate.sh` hygiene 可加:`python3 scripts/ci/split_coverage.py --self-test && python3 scripts/ci/split_coverage.py build-split`。
|
||||
- 其他包新登记 split 用例后,对应的 pending-fix / 缺口行会被脚本判为过期,需同提交删行。
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
|
||||
| 名 | 基线(split 臂) | 本包之后 | 位置 |
|
||||
|---|---|---|---|
|
||||
| `generate-mipmap-storage`(split 检查) | 描述符层数少于「从 level 0 起的整链」即 `Fatal{UnmigratedEmulation}`:MAX_LEVEL 偏小、`glTexStorage2D` 层数不足(合法 GL) | 删;生成窗口 = 记录的计划 | `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:11644` |
|
||||
| `generate-mipmap-cpu-filter` | RGB16F / RGB32F 生成一律 Fatal | 删;先原生 `glGenerateMipmap`,驱动拒绝或旋钮强制时 server 从自己的层做 CPU 滤波 | `DirectGLES.cpp:12953`、`:13066` |
|
||||
| `generate-mipmap-storage`(split 检查) | 描述符层数少于「从 level 0 起的整链」即 `Fatal{UnmigratedEmulation}`:MAX_LEVEL 偏小、`glTexStorage2D` 层数不足(合法 GL) | 删;生成窗口 = 记录的计划 | `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:11759` |
|
||||
| `generate-mipmap-cpu-filter` | RGB16F / RGB32F 生成一律 Fatal | 删;先原生 `glGenerateMipmap`,驱动拒绝或旋钮强制时 server 从自己的层做 CPU 滤波 | `DirectGLES.cpp:13091`、`:13209` |
|
||||
|
||||
- split 臂的 `UnmigratedEmulation` 可达名清零;树里剩 4 名(`copy-image-shadow-mirror`、`generate-mipmap-storage` 的 monolith 生长、`generate-mipmap-cpu-fallback`、`get-tex-image-shadow`),都只在 monolith 臂上。
|
||||
- `PipeCatalogueTest.cpp:1446` 名单保持 4 名,注释补记漂移(它从未列过 `cpu-filter`)与本包的两处退役。
|
||||
- `PipeCatalogueTest.cpp:1451` 名单保持 4 名,注释补记漂移(它从未列过 `cpu-filter`)与本包的两处退役。
|
||||
- 主机(llvmpipe)上 RGB16F(原生存储)与 RGB32F(加宽为 RGBA32F)都由原生 `glGenerateMipmap` 完成;CPU 臂只在旋钮下走到(日志核实:非强制条目 0 行 `generate-mipmap-server-cpu`)。
|
||||
|
||||
## 2. 设计
|
||||
|
||||
| 项 | 做法 | 位置 |
|
||||
|---|---|---|
|
||||
| B1 窗口 | `Base = VerbMipBaseLevel`;`End = min(VerbMipLevelCount, Desc.Levels, MaxLevel+1, Base + 基准层的整链)`,与 Magma 的裁剪同式(`WireFramebuffer.inc:1356`);client 已按同一窗口定义层(`EmitTables.cpp:384-390`) | `DirectGLES.cpp:11644` |
|
||||
| B1 两条 blit 链 | 深度 / R11F 的记录臂改为只跑 `[Base+1, End)`,不再从 level 1 跑到描述符末尾(BASE_LEVEL=1 时会用 level 0 覆盖 base) | `DirectGLES.cpp:12408`、`:12425` |
|
||||
| B1 死臂 | `EnsureGenerateMipmapStorageAllocated(texture)` 的 transport 臂删除:split 下 `GenerateMipmap` 在它之前已经返回 | `DirectGLES.cpp:11666` |
|
||||
| B2 原生 | 三通道浮点也先走原生 `glGenerateMipmap`;错误不转给应用,改走 CPU 臂 | `DirectGLES.cpp:13114` |
|
||||
| B2 CPU 臂 | 源 = 驱动上的 base 层(`ReadTextureLevelTight`,RGB/FLOAT);驱动读不回(不可附着的存储)才用 staged store,且 store 须整层覆盖;都没有则具名拒绝(`MGLOG_E_ONCE`,不 Fatal);2×2 盒滤波(monolith 同式);按驱动存储的通道数 `glTexSubImage2D`(RGB 或 RGBA,FLOAT);支持 2D 与立方体,其余目标具名拒绝 | `DirectGLES.cpp:12909`、`:12953` |
|
||||
| B2 旋钮 | `MGITEST_ESPRYT_FORCE_CPU_MIPMAP=1`,server 读(`MGITEST_MAGMA_FORCE_SHADER_MIPMAP` 先例);命中打一行 `generate-mipmap-server-cpu`(INFO,每进程一次) | `DirectGLES.cpp:12902` |
|
||||
| B1 窗口 | `Base = VerbMipBaseLevel`;`End = min(VerbMipLevelCount, Desc.Levels, MaxLevel+1, Base + 基准层的整链)`,与 Magma 的裁剪同式(`WireFramebuffer.inc:1358`);client 已按同一窗口定义层(`EmitTables.cpp:384-390`) | `DirectGLES.cpp:11759` |
|
||||
| B1 两条 blit 链 | 深度 / R11F 的记录臂改为只跑 `[Base+1, End)`,不再从 level 1 跑到描述符末尾(BASE_LEVEL=1 时会用 level 0 覆盖 base) | `DirectGLES.cpp:12523`、`:12540` |
|
||||
| B1 死臂 | `EnsureGenerateMipmapStorageAllocated(texture)` 的 transport 臂删除:split 下 `GenerateMipmap` 在它之前已经返回 | `DirectGLES.cpp:11781` |
|
||||
| B2 原生 | 三通道浮点也先走原生 `glGenerateMipmap`;错误不转给应用,改走 CPU 臂 | `DirectGLES.cpp:13258` |
|
||||
| B2 CPU 臂 | 源 = 驱动上的 base 层(`ReadTextureLevelTight`,RGB/FLOAT);驱动读不回(不可附着的存储)才用 staged store,且 store 须整层覆盖;都没有则具名拒绝(`MGLOG_E_ONCE`,不 Fatal);2×2 盒滤波(monolith 同式);按驱动存储的通道数 `glTexSubImage2D`(RGB 或 RGBA,FLOAT);支持 2D 与立方体,其余目标具名拒绝 | `DirectGLES.cpp:13036`、`:13091` |
|
||||
| B2 旋钮 | `MGITEST_ESPRYT_FORCE_CPU_MIPMAP=1`,server 读(`MGITEST_MAGMA_FORCE_SHADER_MIPMAP` 先例);命中打一行 `generate-mipmap-server-cpu`(INFO,每进程一次) | `DirectGLES.cpp:13017` |
|
||||
| B2 client 影子 | 任何臂都不读 client 影子 | — |
|
||||
|
||||
未改:`PipeCalls.def:260` 的 `GenerateMipmap` 仍是 `kWaitApplied`。P5e 裁定 14 留着它的唯一理由(CPU 滤波读 client 影子)已消失;翻成 `kWaitNone` 是协议改动,交集成方。
|
||||
@@ -33,20 +33,20 @@
|
||||
|
||||
| 用例 | 形状 | 行 |
|
||||
|---|---|---|
|
||||
| `PackedFloat` / `Depth` × `MutableMaxLevelBelowTheChain` | 可变,定义 0..2,MAX_LEVEL=2;level 3 须未定义 | `GenerateMipmapServerScenario.cpp:294` |
|
||||
| `PackedFloat` / `Depth` × `StorageWithFewerLevelsThanTheChain` | `glTexStorage2D` 3 层(整链 5) | `GenerateMipmapServerScenario.cpp:313` |
|
||||
| `PackedFloat` / `Depth` × `MutableBaseLevelOneKeepsTheLevelsOutsideItsWindow` | 定义 0..3,BASE=1、MAX=2;level 0/3 不变,level 4 未定义 | `GenerateMipmapServerScenario.cpp:328` |
|
||||
| `Rgb16f` / `Rgb32f` × `UploadedBaseGeneratesItsChain` | 仅上传 level 0 | `GenerateMipmapServerScenario.cpp:353` |
|
||||
| `Rgb16f` / `Rgb32f` × `RenderTargetBaseGeneratesFromTheGpuWrittenLevel` | 上传哨兵色后把 level 0 当渲染目标绘制(Complementary colortex2 形状) | `GenerateMipmapServerScenario.cpp:369` |
|
||||
| `PackedFloat` / `Depth` × `MutableMaxLevelBelowTheChain` | 可变,定义 0..2,MAX_LEVEL=2;level 3 须未定义 | `GenerateMipmapServerScenario.cpp:307` |
|
||||
| `PackedFloat` / `Depth` × `StorageWithFewerLevelsThanTheChain` | `glTexStorage2D` 3 层(整链 5) | `GenerateMipmapServerScenario.cpp:326` |
|
||||
| `PackedFloat` / `Depth` × `MutableBaseLevelOneKeepsTheLevelsOutsideItsWindow` | 定义 0..3,BASE=1、MAX=2;level 0/3 不变,level 4 未定义 | `GenerateMipmapServerScenario.cpp:341` |
|
||||
| `Rgb16f` / `Rgb32f` × `UploadedBaseGeneratesItsChain` | 仅上传 level 0 | `GenerateMipmapServerScenario.cpp:366` |
|
||||
| `Rgb16f` / `Rgb32f` × `RenderTargetBaseGeneratesFromTheGpuWrittenLevel` | 上传哨兵色后把 level 0 当渲染目标绘制(Complementary colortex2 形状) | `GenerateMipmapServerScenario.cpp:382` |
|
||||
|
||||
| 臂 | 登记 | Espryt | Magma(对照) |
|
||||
|---|---|---|---|
|
||||
| monolith | 环境发现 `DirectGLES.` / `DirectVulkan.` | 4 绿 + 6 按名跳过(M1、M2) | 7 绿 + 3 按名跳过(M3) |
|
||||
| Split / Spawn / Tcp | `MobileGL/MG_IntegrationTest/CMakeLists.txt:3355`;Magma 门控块 `MobileGL/MG_IntegrationTest/CMakeLists.txt:4059` | 10 × 3 绿 | 10 × 3 绿 |
|
||||
| 强制 CPU(Split / Spawn) | `MobileGL/MG_IntegrationTest/CMakeLists.txt:3356-3372`,标签 `integration-cpu-mip-{split,spawn}` | 4 × 2 绿,server 日志须有 `generate-mipmap-server-cpu`(`GenerateMipmapServerScenario.cpp:282`) | — |
|
||||
| Split / Spawn / Tcp | `MobileGL/MG_IntegrationTest/CMakeLists.txt:3442`;Magma 门控块 `MobileGL/MG_IntegrationTest/CMakeLists.txt:4174` | 10 × 3 绿 | 10 × 3 绿 |
|
||||
| 强制 CPU(Split / Spawn) | `MobileGL/MG_IntegrationTest/CMakeLists.txt:3443-3459`,标签 `integration-cpu-mip-{split,spawn}` | 4 × 2 绿,server 日志须有 `generate-mipmap-server-cpu`(`GenerateMipmapServerScenario.cpp:295`) | — |
|
||||
| A/B inproc(门的 `integration-gpu[inproc]` 环境) | 同环境发现 | 10 绿 | 10 绿 |
|
||||
|
||||
- 强制 CPU 臂不上 tcp:tcp 的 server 是共享的 `TcpServer.Start` 夹具(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2154`),旋钮放那里会让所有 tcp 条目都走 CPU 臂、原生臂在 tcp 上失去覆盖;CPU 臂本身与传输无关(spawn 已证跨进程)。与 P9 `RemintFallback.` 同形,不需要 parity 例外行。
|
||||
- 强制 CPU 臂不上 tcp:tcp 的 server 是共享的 `TcpServer.Start` 夹具(`MobileGL/MG_IntegrationTest/CMakeLists.txt:2168`),旋钮放那里会让所有 tcp 条目都走 CPU 臂、原生臂在 tcp 上失去覆盖;CPU 臂本身与传输无关(spawn 已证跨进程)。与 P9 `RemintFallback.` 同形,不需要 parity 例外行。
|
||||
|
||||
## 4. red-once
|
||||
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
| # | 后端 | 缺陷 | 位置 | red-once 读数 |
|
||||
|---|---|---|---|---|
|
||||
| M1 | Espryt | RGB16F / RGB32F CPU 滤波读前端层影子;level 0 被 GPU 写过后影子陈旧 | `DirectGLES.cpp:12812`(调用 `:13171`) | level 1..3 = 哨兵色 `rgba(255,0,255,255)`,两格式 |
|
||||
| M2 | Espryt | R11F / 深度:存储生长从 level 0 算整链,定义 MAX_LEVEL 之外的层;两条 blit 链从 level 1 跑到末尾,无视 BASE_LEVEL / MAX_LEVEL | `DirectGLES.cpp:11592`、`:12465`、`:12495` | MAX_LEVEL=2 时 level 3 宽 2(应 0);BASE=1 时 level 1..3 全被 level 0 覆盖,level 4 被定义 |
|
||||
| M3 | Magma | 无 BLIT 的颜色格式(lavapipe 上 `B10G11R11_UFLOAT`,VkFormat 122)直接跳过生成,只打 `MGLOG_W_ONCE`;wire 臂自 P7 有着色器臂,monolith 没有 | `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:12226` | 三个 PackedFloat 用例生成层读到 `rgba(0,0,0,255)` |
|
||||
| M1 | Espryt | RGB16F / RGB32F CPU 滤波读前端层影子;level 0 被 GPU 写过后影子陈旧 | `DirectGLES.cpp:12927`(调用 `:13320`) | level 1..3 = 哨兵色 `rgba(255,0,255,255)`,两格式 |
|
||||
| M2 | Espryt | R11F / 深度:存储生长从 level 0 算整链,定义 MAX_LEVEL 之外的层;两条 blit 链从 level 1 跑到末尾,无视 BASE_LEVEL / MAX_LEVEL | `DirectGLES.cpp:11707`、`:12580`、`:12610` | MAX_LEVEL=2 时 level 3 宽 2(应 0);BASE=1 时 level 1..3 全被 level 0 覆盖,level 4 被定义 |
|
||||
| M3 | Magma | 无 BLIT 的颜色格式(lavapipe 上 `B10G11R11_UFLOAT`,VkFormat 122)直接跳过生成,只打 `MGLOG_W_ONCE`;wire 臂自 P7 有着色器臂,monolith 没有 | `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:12229` | 三个 PackedFloat 用例生成层读到 `rgba(0,0,0,255)` |
|
||||
|
||||
- M1 修法指向:monolith 也改为回读驱动层(P9 dev `568090f0` 的 `AdoptDriverLevelIntoShadow` 同路)。M2:monolith 两条链与生长改用 `ComputeMipmapGenerationRange` 窗口。M3:monolith 复用 wire 臂的着色器 mip。都动 pull 构建的 `.text`,按 G1 在 dev 修。
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
## 1. 结论
|
||||
|
||||
- 前提成立(red-once 见 §4):split 下 server 的 R-11 暂存影子只由上传 / adopt 填,server 上的 GPU 写不进影子;CPU 读者读到旧字节,或因无影子直接拒绝。原 7 例在基线上 Espryt split / spawn / tcp 与 inproc A/B 全红。
|
||||
- 修在 server,不动 client、不动 monolith:写者给 server 孪生体打标(`serverGpuWritten`,`MobileGL/MG_Backend/DirectGLES/Managers.h:1059`),读者消费字节前经 `SplitHostBytesForCpuRead`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:3570`)从 server 自己的 GL buffer 整 store 回读。
|
||||
- XFB 不打标:两条捕获路径本来就把字节拿在手里(给 client 的 writeback),直接写进影子(`StageServerWrittenRange`,`MobileGL/MG_Backend/DirectGLES/Managers.cpp:3626`)。
|
||||
- 修在 server,不动 client、不动 monolith:写者给 server 孪生体打标(`serverGpuWritten`,`MobileGL/MG_Backend/DirectGLES/Managers.h:1059`),读者消费字节前经 `SplitHostBytesForCpuRead`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:3595`)从 server 自己的 GL buffer 整 store 回读。
|
||||
- XFB 不打标:两条捕获路径本来就把字节拿在手里(给 client 的 writeback),直接写进影子(`StageServerWrittenRange`,`MobileGL/MG_Backend/DirectGLES/Managers.cpp:3651`)。
|
||||
- 懒刷新:只在读者真正消费字节时回读;原生 indirect 不从 CPU 取字段时不回读,Create / Flywheel 式每帧 compute 剔除 + 原生 indirect 不付回读(`MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:8555`)。
|
||||
- 顺带关掉 P3a M-3 声明的偏差(fp64 顶点收窄读旧字节)在 split 臂上的一半;monolith 臂不动。
|
||||
- monolith 两后端共 5 处缺陷,skip 按名、记给 dev(§6,ID-P8-3);IndirectCount 的 monolith 按 OQ15 不改。
|
||||
@@ -21,7 +21,7 @@
|
||||
| indirect 命令字段 | `DirectGLES.cpp:8549` | CPU 路径全取;原生路径只在程序读 `gl_BaseVertex`、或无 `mg_IndirectParams` 视图而读 `gl_BaseInstance` 时取 |
|
||||
| 无影子的 indirect 命令 buffer | `DirectGLES.cpp:487` | 被 GPU 写过就取第一份(原来 decline,`indirect_command_bytes`) |
|
||||
| XFB scatter 的捕获前字节 | `DirectGLES.cpp:2037` | 刷新 |
|
||||
| fp64 顶点收窄 | `MobileGL/MG_Backend/DirectGLES/Managers.cpp:6671` | 刷新,并让 memo 失效(GPU 写不动 serial) |
|
||||
| fp64 顶点收窄 | `MobileGL/MG_Backend/DirectGLES/Managers.cpp:6696` | 刷新,并让 memo 失效(GPU 写不动 serial) |
|
||||
|
||||
| 写者 | 位置 | 效果 |
|
||||
|---|---|---|
|
||||
@@ -35,11 +35,11 @@
|
||||
- 刷新顺序同 `Ops_H_Readback`:`FlushPendingRangesFrom` 先落排队上传 → `DrainResidentWritesNow` → 整 store `glMapBufferRange(READ)` → `MGL_SERVER_STAGED_ADOPT`(覆盖变整段,孤儿 store 也就有了第一份)→ serial 戳 + epoch bump。
|
||||
- T0 store:影子就是 server 对 client AHB 的一致映射;有标记或有排队常驻写时 `DrainResidentWritesNow` + `glFinish`(DEBTS 的 P11 B2 行的 split 半)。
|
||||
- 刷新与写影子只在复制型暂存 store(split)下生效(`CopiesIntoServerStorage`):monolith 的 store 保留调用方指针,映射一解就悬空。
|
||||
- 全部新代码在 `MOBILEGL_BUILD_DISAGGREGATED` 下;`ReadsBaseInstance`(`Managers.h:3107`)同。
|
||||
- 全部新代码在 `MOBILEGL_BUILD_DISAGGREGATED` 下;`ReadsBaseInstance`(`Managers.h:3124`)同。
|
||||
|
||||
## 3. 场景
|
||||
|
||||
`GpuWrittenDrawInputScenario`(`MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp`,split-only 源,登记 `MobileGL/MG_IntegrationTest/CMakeLists.txt:3343`)12 例:GPU 只写一次,暂存影子里是画不出正确结果的毒值。
|
||||
`GpuWrittenDrawInputScenario`(`MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp`,split-only 源,登记 `MobileGL/MG_IntegrationTest/CMakeLists.txt:3461`)12 例:GPU 只写一次,暂存影子里是画不出正确结果的毒值。
|
||||
|
||||
| 例 | 写者 | 读者 |
|
||||
|---|---|---|
|
||||
@@ -85,9 +85,9 @@
|
||||
|---|---|---|---|
|
||||
| Espryt | `*IndirectCount` 读前端影子不调 `SyncGpuWrites`(OQ15) | `DirectGLES.cpp:9688-9689`、`:9852-9853` | count 读 0,什么都不画 |
|
||||
| Espryt | 原生 indirect 的 `gl_BaseVertex` 从前端影子取(单进程臂 `ResolveIndirectCommandBytes` 只 `SyncPersistentMappedRange`) | `DirectGLES.cpp:521`、`:8640` | 几何对、颜色红 |
|
||||
| Espryt | fp64 收窄的 handle 臂在 monolith 下读前端影子(P3a M-3 声明的偏差,monolith 半) | `Managers.cpp:6650` | 画在屏外 |
|
||||
| Magma | `MultiDrawArraysIndirectCount` 的 CPU 臂不调 `SyncGpuWrites` | `MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:729` | count 读 0 |
|
||||
| Magma | draw 录制中途调 `SyncGpuWrites`(restart 重写、GL_DOUBLE 顶点转换):提交并等待结束了当前帧命令缓冲,draw 接着往已结束的缓冲里录 | `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:4510`、`:4176` | SIGSEGV(lavapipe 工作线程 / `vkCmdBindVertexBuffers`);校验层 `VUID-vkCmdBindIndexBuffer-commandBuffer-recording` |
|
||||
| Espryt | fp64 收窄的 handle 臂在 monolith 下读前端影子(P3a M-3 声明的偏差,monolith 半) | `Managers.cpp:6675` | 画在屏外 |
|
||||
| Magma | `MultiDrawArraysIndirectCount` 的 CPU 臂不调 `SyncGpuWrites` | `MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:749` | count 读 0 |
|
||||
| Magma | draw 录制中途调 `SyncGpuWrites`(restart 重写、GL_DOUBLE 顶点转换):提交并等待结束了当前帧命令缓冲,draw 接着往已结束的缓冲里录 | `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:4513`、`:4179` | SIGSEGV(lavapipe 工作线程 / `vkCmdBindVertexBuffers`);校验层 `VUID-vkCmdBindIndexBuffer-commandBuffer-recording` |
|
||||
|
||||
- skip 只在 `transportName == "monolith"` 的对应后端生效,理由串带出处。
|
||||
|
||||
@@ -99,4 +99,4 @@
|
||||
- `Ops_H_Readback`(client 发起的回读)没有顺带刷新 server 影子(可省一次回读,未做)。
|
||||
- 同一 draw 既写 X(SSBO)又拿 X 当 EBO:标记在 `SyncNeccessaryBuffers` 里打,restart / rebase 读者在 draw 执行前就刷新并清标记,写发生在清之后,X 留成未标记。monolith 同形(`MarkShaderStorageBuffersGpuWritten` 先标,`SyncGpuWrites` 在 draw 前清),是对齐不是倒退。
|
||||
- DEBTS 的 P11 B2 行(handle 臂读采纳 / T0 映射前不落常驻写):split 半由 `SplitHostBytesForCpuRead` 的 T0 臂做了、主机不可验证;单进程半归 dev。
|
||||
- Magma 的 `VulkanRenderer.cpp:3683`(最大索引扫描)与 §6 最后一行同模式,未验证。
|
||||
- Magma 的 `VulkanRenderer.cpp:3686`(最大索引扫描)与 §6 最后一行同模式,未验证。
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
## 1. 结论
|
||||
|
||||
- wire 臂的 `glDraw*Indirect` / `glMultiDraw*Indirect[Count]` 改为从 wire store 自己的 `VkBuffer` 发 `vkCmdDraw[Indexed]Indirect[Count]`:`VulkanRenderer::DrawWireIndirectNative`(`MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc:620`)。
|
||||
- wire 臂的 `glDraw*Indirect` / `glMultiDraw*Indirect[Count]` 改为从 wire store 自己的 `VkBuffer` 发 `vkCmdDraw[Indexed]Indirect[Count]`:`VulkanRenderer::DrawWireIndirectNative`(`MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc:627`)。
|
||||
- `DrawWireIndirect`(`MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:381`)先走原生;CPU 展开只留给设备发不出的 COUNT 形态(与 monolith 同条件,§3)。
|
||||
- create-indirect × Magma × split:indirect 引起的 host 等待 321 → 0,等待时间 8.07 s → 0.013 s(剩下 6 次是应用回读,§2);SSIM 各臂不变。
|
||||
- 新增 barrier:着色器写过的 store 在下一次 indirect 读前补一个 `INDIRECT_COMMAND_READ` barrier,每次写一次(`WireDraw.inc:686-698`)。create-indirect 的 322 次 cull→draw 里 320 次没有 `glMemoryBarrier`,旧 CPU 路径靠整 GPU 等待碰巧有序,原生路径靠这个 barrier。
|
||||
- 新增 barrier:着色器写过的 store 在下一次 indirect 读前补一个 `INDIRECT_COMMAND_READ` barrier,每次写一次(`WireDraw.inc:693-705`)。create-indirect 的 322 次 cull→draw 里 320 次没有 `glMemoryBarrier`,旧 CPU 路径靠整 GPU 等待碰巧有序,原生路径靠这个 barrier。
|
||||
|
||||
## 2. 测量(主机,`MOBILEGL_PIPE_STATS=1 MOBILEGL_PIPE_STATS_PERIOD=1`,server 日志 `windr[...]` 求和)
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
|---|---|---|
|
||||
| 没绑 `GL_DRAW_INDIRECT_BUFFER`:命令在 client 内存 | `DirectVulkan.cpp:684`、`:825`、`:884` | client 在兼容档拒绝(`MobileGL/MG_Remote/Client/EmitTables.cpp:730` `CLIENT_COMMANDS`);store 为空则具名拒绝 `IndirectBufferUnbound` |
|
||||
| `glMultiDrawArraysIndirectCount`:总在 CPU 读 count 字 | `DirectVulkan.cpp:720-757` | 原生 `vkCmdDrawIndirectCount`(`MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:15942` 加载,只在 disaggregated 构建) |
|
||||
| `glMultiDrawElementsIndirectCount`:无 `VK_KHR_draw_indirect_count`,或 maxdrawcount > 1 且无 `multiDrawIndirect` → CPU 循环 | `VulkanRenderer.cpp:13421-13444` | 同条件(`WireDraw.inc:623-628`)返回 false,走原 CPU 展开(`DirectVulkan.cpp:390-413`,计入 `wixp`) |
|
||||
| 无 `multiDrawIndirect` 或 stride 不是 4 的倍数:逐条原生 indirect,仍由 GPU 读 | `VulkanRenderer.cpp:13510`、`:13615` | 同(`WireDraw.inc:725-733`);stride 非 4 倍数在 GL 是 INVALID_VALUE,wire 上具名拒绝 `IndirectCommandRange` |
|
||||
| `glMultiDrawElementsIndirectCount`:无 `VK_KHR_draw_indirect_count`,或 maxdrawcount > 1 且无 `multiDrawIndirect` → CPU 循环 | `VulkanRenderer.cpp:13421-13444` | 同条件(`WireDraw.inc:630-635`)返回 false,走原 CPU 展开(`DirectVulkan.cpp:390-413`,计入 `wixp`) |
|
||||
| 无 `multiDrawIndirect` 或 stride 不是 4 的倍数:逐条原生 indirect,仍由 GPU 读 | `VulkanRenderer.cpp:13510`、`:13615` | 同(`WireDraw.inc:732-740`);stride 非 4 倍数在 GL 是 INVALID_VALUE,wire 上具名拒绝 `IndirectCommandRange` |
|
||||
| client 顶点数组 + arrays indirect:CPU 读命令定上传范围,draw 仍原生 | `VulkanRenderer.cpp:13576` | 不可达:client 把 client 数组拷成自有 buffer |
|
||||
| `GL_LINE_LOOP`:indirect 不改写,退化为 LINE_STRIP | `MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp:27` | 同 |
|
||||
| uint8 索引无扩展 / 非固定 restart 索引 / 非对齐偏移:整段索引在 CPU 改写,命令仍 GPU 读 | 两臂都在各自的索引上传里 | `WireDraw.inc:391`,索引视图是整个 EBO(`WireDraw.inc:674`,同 monolith) |
|
||||
| uint8 索引无扩展 / 非固定 restart 索引 / 非对齐偏移:整段索引在 CPU 改写,命令仍 GPU 读 | 两臂都在各自的索引上传里 | `WireDraw.inc:398`,索引视图是整个 EBO(`WireDraw.inc:681`,同 monolith) |
|
||||
| baseInstance / gl_DrawID | 原生(monolith 不查 `drawIndirectFirstInstance`) | 原生;旧 CPU 展开的第 3 档把 gl_DrawID 全给 0(`VulkanRenderer.cpp:13154`),原生反而更对 |
|
||||
|
||||
## 4. 设计
|
||||
@@ -45,15 +45,15 @@
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 入口 | `DrawWireIndirect`(`DirectVulkan.cpp:381`)→ `DrawWireIndirectNative`;false 才走 CPU 展开 |
|
||||
| store | `AcquireWireSlice` 取 indirect / count / 元素 store(打 `lastUseSerial`,之后的 `glBufferSubData` 会排在这次读之后);范围、4 字节对齐、count 字越界都是具名拒绝(`MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def:109-112`) |
|
||||
| draw 准备 | 复用 `SetupDraw` → `SetupWireDraw`,带 `IndirectDrawBuffer`;该 aspect 原来是 `buffer-legacy-arm` Fatal,现只对 monolith 入口仍 Fatal(`WireDraw.inc:213`,`m_wireNativeIndirectDraw`);顶点流转换不再按 first+count 截断(`WireDraw.inc:319`) |
|
||||
| 发射 | 同 monolith 的三档:COUNT 原生、`multiDrawIndirect` 一次发、否则逐条;另按 `maxDrawIndirectCount` 分块(`WireDraw.inc:712-733`) |
|
||||
| store | `AcquireWireSlice` 取 indirect / count / 元素 store(打 `lastUseSerial`,之后的 `glBufferSubData` 会排在这次读之后);范围、4 字节对齐、count 字越界都是具名拒绝(`MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def:115-118`) |
|
||||
| draw 准备 | 复用 `SetupDraw` → `SetupWireDraw`,带 `IndirectDrawBuffer`;该 aspect 原来是 `buffer-legacy-arm` Fatal,现只对 monolith 入口仍 Fatal(`WireDraw.inc:213`,`m_wireNativeIndirectDraw`);顶点流转换不再按 first+count 截断(`WireDraw.inc:326`) |
|
||||
| 发射 | 同 monolith 的三档:COUNT 原生、`multiDrawIndirect` 一次发、否则逐条;另按 `maxDrawIndirectCount` 分块(`WireDraw.inc:719-740`) |
|
||||
| barrier | `MarkWireBufferGpuWritten` 置 `indirectReadBarrierPending`(`VkBufferManager.cpp:611`),`TakeWireIndirectReadBarrier`(`VkBufferManager.cpp:624`)取走;有则结束 render pass 并记 `ALL_COMMANDS/MEMORY_WRITE → DRAW_INDIRECT/INDIRECT_COMMAND_READ`。transfer 写已有 `MEMORY_READ` 后置 barrier(`VkBufferManager.cpp:514`、`:1437`),不置位。应用自己的 `glMemoryBarrier(GL_COMMAND_BARRIER_BIT)` 仍走 `BuildMemoryBarrierForGlBarriers`(`VulkanRenderer.cpp:7873`),与 monolith 一样 |
|
||||
| G1 | 全部在 `MOBILEGL_BUILD_DISAGGREGATED` / `MOBILEGL_PIPE_PUSH` 下;pull 构建 `added=0 removed=0` |
|
||||
|
||||
## 5. 门与 red-once
|
||||
|
||||
用例 `WireIndirectDrawScenario`(`MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp:225`、`:274`、`:302`、`:340`),读数经 `MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp:22`(进程内 PipeStats,只在 inproc 臂看得到 server)。登记:`MobileGL/MG_IntegrationTest/CMakeLists.txt:4651-4684`——Magma 门控三臂 `DirectVulkan.{Split,Spawn,Tcp}.Indirect.`、DirectGLES 三臂(经宏,含 Magma 信息层)、两后端 monolith(环境发现)。
|
||||
用例 `WireIndirectDrawScenario`(`MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp:225`、`:274`、`:304`、`:343`),读数经 `MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp:22`(进程内 PipeStats,只在 inproc 臂看得到 server)。登记:`MobileGL/MG_IntegrationTest/CMakeLists.txt:4831-4864`——Magma 门控三臂 `DirectVulkan.{Split,Spawn,Tcp}.Indirect.`、DirectGLES 三臂(经宏,含 Magma 信息层)、两后端 monolith(环境发现)。
|
||||
|
||||
| 用例 | 断言 | 两后端 / 各臂 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -18,21 +18,21 @@
|
||||
| store 路由 | `DirectGLES/WireTextureReadback.inc:352` | 驱动拒绝回读时,store 仅在「整层覆盖、尺寸一致、纹理从未被驱动写过」时回答;转换同 `GetTexImageViaShadowConversion`(打包格式按字原样,其余解成宽 RGBA 逐行转) |
|
||||
| 具名拒绝 / 武装标记 | `DirectGLES/WireTextureReadback.inc:411`,`:416` | 拒绝:`MGLOG_E_ONCE` `texture-readback-store-declined`,回复 error,client Fatal。回答:`MGLOG_W_ONCE` `texture-readback-from-store` |
|
||||
| 驱动写标记 | `MG_Remote/Server/StagedTextureStore.h:479-514` | 按纹理、粘性;只有 `Drop` / `DropAll` 清,`ResetLevels` 不清(附件在重定义后照样写) |
|
||||
| 标记来源 | `DirectGLES/Managers.cpp:10901` | `NoteDriverSideTextureWriteByHandle` 同时标 store:附件、可写 image、`CopyTexSubImage`、生成 mip 都经过它 |
|
||||
| store 跟随 copy | `StagedTextureStore.h:519`,`DirectGLES/Managers.cpp:10924`,调用点 `DirectGLES/DirectGLES.cpp:13347-13365` | 两端同一内部格式(`DirectGLES/Managers.cpp:10946`)、单 upload target(非 cube / 1D array)、源整层覆盖且未被驱动写、目标整层覆盖或 copy 覆盖整层 → store 做同一搬移;否则给目标标驱动写 |
|
||||
| copy 失败 | `DirectGLES/DirectGLES.cpp:13440` | 驱动拒绝 copy 时 store 已搬,补标驱动写 |
|
||||
| 跟随后的 client 上传 | `DirectGLES/Managers.cpp:3193`,`StagedTextureStore.h:573` | client run 是 client 影子(没见过 copy),只落 record 声明写过的盒(regions,无 region 时 union box);放不下则照常收养并标驱动写;`AdoptRun` 兜底同样标(`StagedTextureStore.h:353`) |
|
||||
| 标记来源 | `DirectGLES/Managers.cpp:10991` | `NoteDriverSideTextureWriteByHandle` 同时标 store:附件、可写 image、`CopyTexSubImage`、生成 mip 都经过它 |
|
||||
| store 跟随 copy | `StagedTextureStore.h:519`,`DirectGLES/Managers.cpp:11014`,调用点 `DirectGLES/DirectGLES.cpp:13670-13688` | 两端同一内部格式(`DirectGLES/Managers.cpp:11036`)、单 upload target(非 cube / 1D array)、源整层覆盖且未被驱动写、目标整层覆盖或 copy 覆盖整层 → store 做同一搬移;否则给目标标驱动写 |
|
||||
| copy 失败 | `DirectGLES/DirectGLES.cpp:13763` | 驱动拒绝 copy 时 store 已搬,补标驱动写 |
|
||||
| 跟随后的 client 上传 | `DirectGLES/Managers.cpp:3197`,`StagedTextureStore.h:573` | client run 是 client 影子(没见过 copy),只落 record 声明写过的盒(regions,无 region 时 union box);放不下则照常收养并标驱动写;`AdoptRun` 兜底同样标(`StagedTextureStore.h:353`) |
|
||||
| 测试旋钮 | `DirectGLES/WireTextureReadback.inc:328` | `MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=<w>x<h>`,server 读;该尺寸颜色层的 FBO 腿视为驱动拒绝,R32UI 字腿不动 |
|
||||
|
||||
不选「标 GPU 脏」:
|
||||
|
||||
- Espryt 的 `GpuDirty` 语义是「需要从 store 重传」(`DirectGLES/Managers.cpp:8460`),标了会把 store 旧字节传回去盖掉 GPU 上的 copy。
|
||||
- 跟随后其他整层读 store 的路径也拿到对的字节:重铸回读被拒时的 W2-b 重放(`DirectGLES/Managers.cpp:6888`)、整层重传。
|
||||
- Espryt 的 `GpuDirty` 语义是「需要从 store 重传」(`DirectGLES/Managers.cpp:8550`),标了会把 store 旧字节传回去盖掉 GPU 上的 copy。
|
||||
- 跟随后其他整层读 store 的路径也拿到对的字节:重铸回读被拒时的 W2-b 重放(`DirectGLES/Managers.cpp:6978`)、整层重传。
|
||||
- 跟随不了的形状(跨格式、renderbuffer 源、被驱动写过的源、cube / 1D array、视图)降为具名拒绝,不给旧字节。
|
||||
|
||||
旋钮按尺寸定界:
|
||||
|
||||
- tcp 的 server 是整条 lane 共用的 `TcpServer.Start` fixture,旋钮只能放进它的 ENVIRONMENT(`MG_IntegrationTest/CMakeLists.txt:3245`)。
|
||||
- tcp 的 server 是整条 lane 共用的 `TcpServer.Start` fixture,旋钮只能放进它的 ENVIRONMENT(`MG_IntegrationTest/CMakeLists.txt:3268`)。
|
||||
- 场景只有自身环境里有旋钮时才建 13x7 的纹理,否则 12x6(`CopyImageStoreReadbackScenario.cpp:66`),所以 lane 级旋钮只碰 StoreRead 条目。
|
||||
- 实测:tcp 上无旋钮条目的 server 日志没有 `texture-readback-from-store`,StoreRead 条目有。
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
| 条目 | 臂 | 登记 |
|
||||
|---|---|---|
|
||||
| `DirectGLES.*` / `DirectVulkan.*` | monolith | 整体发现 |
|
||||
| `DirectGLES.{Split,Spawn,Tcp}.*` | 三臂,无旋钮 | `mgl_itest_register_split_arms`(`MG_IntegrationTest/CMakeLists.txt:3229`) |
|
||||
| `DirectGLES.{Split,Spawn,Tcp}.StoreRead.*` | 三臂,旋钮 13x7,前 4 例 | `MG_IntegrationTest/CMakeLists.txt:3247-3273`,带 `integration-gpu` |
|
||||
| `DirectVulkan.{Split,Spawn,Tcp}.CopyStore.*` | Magma 门控三臂 | `MG_IntegrationTest/CMakeLists.txt:3281`;另有 tier 2 信息层 |
|
||||
| `DirectGLES.{Split,Spawn,Tcp}.*` | 三臂,无旋钮 | `mgl_itest_register_split_arms`(`MG_IntegrationTest/CMakeLists.txt:3252`) |
|
||||
| `DirectGLES.{Split,Spawn,Tcp}.StoreRead.*` | 三臂,旋钮 13x7,前 4 例 | `MG_IntegrationTest/CMakeLists.txt:3270-3296`,带 `integration-gpu` |
|
||||
| `DirectVulkan.{Split,Spawn,Tcp}.CopyStore.*` | Magma 门控三臂 | `MG_IntegrationTest/CMakeLists.txt:3304`;另有 tier 2 信息层 |
|
||||
|
||||
- 三臂对称,`spawn_lane_parity.py` 不需例外行(`StoreRead.` 尾不被 Espryt 的 `CASE` 解析,`scripts/ci/spawn_lane_parity.py:39`)。
|
||||
- store 单元用例 6 个:`MG_Test/Wire/StagedTextureStoreTest.cpp:330-475`。
|
||||
@@ -78,19 +78,19 @@
|
||||
|
||||
## 5. 死闩清理(无行为变化)
|
||||
|
||||
- `CopyImageSubData+RENDERBUFFER`:原在 `SyncRenderbufferObjectToBackend` 内,条件是 transport≠monolith;唯一调用点只在 monolith 臂(split 臂按句柄处理,`DirectGLES/DirectGLES.cpp:13134-13155`),删去并改写注释(`DirectGLES/DirectGLES.cpp:13110-13117`)。
|
||||
- 镜像函数里过时的「client 侧镜像留给 P8」「由两个 Fatal 兜底」注释改写(`DirectGLES/DirectGLES.cpp:13239-13252`)。
|
||||
- `CopyImageSubData+RENDERBUFFER`:原在 `SyncRenderbufferObjectToBackend` 内,条件是 transport≠monolith;唯一调用点只在 monolith 臂(split 臂按句柄处理,`DirectGLES/DirectGLES.cpp:13461-13482`),删去并改写注释(`DirectGLES/DirectGLES.cpp:13437-13444`)。
|
||||
- 镜像函数里过时的「client 侧镜像留给 P8」「由两个 Fatal 兜底」注释改写(`DirectGLES/DirectGLES.cpp:13566-13579`)。
|
||||
- Fatal 普查:abort 点 79 → 78(`DirectGLES.cpp` 15 → 14),族 45 不变;基线用 `fatal_census.py --write-baseline` 重写。
|
||||
|
||||
## 6. monolith 缺陷(ID-P8-3,记给 dev,不在此修)
|
||||
|
||||
- `MirrorCopyImageIntoDestinationShadow` 只对 `HasRedundantPackedEncoding`(仅 RGB9_E5)调用(`DirectGLES/DirectGLES.cpp:13450`);其他格式 copy 后 CPU 影子不变。
|
||||
- 驱动 attach 不了目标格式时 `GetTexImage` 退到影子(`DirectGLES/DirectGLES.cpp:15748`)→ 返回 copy 前字节。设备(Adreno:RGB16 / 16 位 SNORM / 无 norm16 时的 RGBA16)可达。
|
||||
- `MirrorCopyImageIntoDestinationShadow` 只对 `HasRedundantPackedEncoding`(仅 RGB9_E5)调用(`DirectGLES/DirectGLES.cpp:13773`);其他格式 copy 后 CPU 影子不变。
|
||||
- 驱动 attach 不了目标格式时 `GetTexImage` 退到影子(`DirectGLES/DirectGLES.cpp:16071`)→ 返回 copy 前字节。设备(Adreno:RGB16 / 16 位 SNORM / 无 norm16 时的 RGBA16)可达。
|
||||
- red-once 见 §4 MONO;修法建议:镜像对所有非压缩同格式 copy 都做,或像 split 一样按纹理记「驱动写过」后拒绝。
|
||||
|
||||
## 7. 未做 / 发现未修
|
||||
|
||||
- `copy-image-shadow-mirror` 的 `MGPipeUnmigratedEmulation` 调用(`DirectGLES/DirectGLES.cpp:13261`)在 transport 下不可达(`:13253` 先返回),未删:名单在 `PipeCatalogueTest.cpp:1439`,归 B / F 统一改。
|
||||
- `copy-image-shadow-mirror` 的 `MGPipeUnmigratedEmulation` 调用在 transport 下不可达(`DirectGLES/DirectGLES.cpp:13580` 先返回),本包未删;P8-SE 已删调用与名(`DirectGLES/DirectGLES.cpp:13582-13585`、`PipeCatalogueTest.cpp:1448-1450`,[`SE.md`](SE.md))。
|
||||
- `MGPipeTypes.h:1535` 的 P5b 历史注释仍提 `+RENDERBUFFER` 拒绝;是 wire 头注释,未动。
|
||||
- 跨格式 copy 不跟随(store 存规范影子,16 位打包、RGB10 / RGB12 的规范表示不是 GL 位),降为具名拒绝。
|
||||
- 具名拒绝仍是会话 Fatal(client 的 `RequireReadbackReplyComplete`);改成 `GL_INVALID_OPERATION` 需要协议变更,不在本包。
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
| 项 | 改动 | 位置 | 之前 | 之后 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | multi-draw 的两个 server 闩改族 | `PipeApplier.cpp:779-784`、`:976-981` | `Fatal{UnmigratedVerb, "MultiDraw{Arrays,Elements}+{CLIENT_INDICES,INSTANCED}"}` | `Fatal{ProtocolCorruption, <同名>}`,仍按名闩住外来 peer |
|
||||
| 2 | `Magma:client-vertex-array` 去掉 P8 阶段标签,改为协议不变量 | `WireDraw.inc:297-304` | `MagmaWireFatal`(UnmigratedVerb 族,名字带 `@P8`) | `MGPipeSessionFail(ProtocolCorruption)`,名字 `Magma:client-vertex-array` 加 location |
|
||||
| 2 | `Magma:client-vertex-array` 去掉 P8 阶段标签,改为协议不变量 | `WireDraw.inc:300-307` | `MagmaWireFatal`(UnmigratedVerb 族,名字带 `@P8`) | `MGPipeSessionFail(ProtocolCorruption)`,名字 `Magma:client-vertex-array` 加 location |
|
||||
| 3 | Magma 生成 mip 的"无着色器臂形状"改为具名拒绝 | `WireFramebuffer.inc:1405-1459`(判定)、`:1456`、`:1500`(两个拒绝点) | wire 臂三处 Fatal(旧 `:1427`、`:1452`、`:1489-1493`) | `WireDeclines.def:112` 的 `MipmapShaderFormatOrShape`;判定在 `GrowWireTextureMipChain`(`:1460`)之前,拒绝时不增长 mip 链 |
|
||||
| 4 | `kCapViewportArray`、`kCapNeedsHostIndexBytes`、`kCapNeedsHostUboBytes` 与 index-mirror 哨兵标为保留位 | `MGPipeTypes.h:110`、`:117-123`、`:315-320`、`:332-335`、`:994-999`、`:1007-1009`;`MGPipeHostSpan.h:16-26`;`VertexInputEmit.h:266-267`;`ShaderBufferEmit.h:250-253`;`MG_Backend/Init.cpp:189-191` | 注释说"P5 期间为 0"或"arms the index host mirror" | 注释说 RESERVED、从不发布、位不复用不重编号;只改注释 |
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
| 2 | client 把每个启用的 client 内存数组转成自有 buffer,指针为空就拒绝整个 draw | `OwnedDrawInputs.h:102-131` |
|
||||
| 2 | `fatal_census.py` 只数 `Fatal{Word` 族词与 abort 点,不按 `@P` 字面计数 | `fatal_census.py:165-167` |
|
||||
| 2 | 普查前后:79 个 abort 点、20 个文件、45 个族词,均不变;不重写基线 | 本包门禁读数 |
|
||||
| 2 | 树内 `@P8` 字面:1(`WireDraw.inc:297`,基线)→ 0 | `git grep '@P8' -- MobileGL scripts tools` |
|
||||
| 3 | monolith 臂对无 BLIT 的格式一直是拒绝(跳过、链不写) | `VulkanRenderer.cpp:12225-12229` |
|
||||
| 2 | 树内 `@P8` 字面:1(`WireDraw.inc:300`,基线)→ 0 | `git grep '@P8' -- MobileGL scripts tools` |
|
||||
| 3 | monolith 臂对无 BLIT 的格式一直是拒绝(跳过、链不写) | `VulkanRenderer.cpp:12228-12232` |
|
||||
| 3 | 增长前后格式、特性、knob 档位、aspect、view 类型、usage、层窗口都不变 | `VkTextureManager.cpp:2884-2995` |
|
||||
| 3 | 深度 pass 对本函数交给它的图像只会因"格式不能同时采样和作深度附件"拒绝 | `WireDepthMipmap.inc:58-81` |
|
||||
| 3 | 深度 pass 若仍拒绝(两份清单漂移),走同一行拒绝,不结束会话 | `WireFramebuffer.inc:1491-1502` |
|
||||
@@ -43,7 +43,7 @@
|
||||
| 3 | `spawn_lane_parity.py` 的 `.ShaderMipDecline.` 例外行 | 删掉该行 | `spawn_lane_parity.py build-split` | rc 1:`the tcp arm does not match the split arm: missing=['DirectVulkan.ShaderMipDecline.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession']` |
|
||||
| 2 | 普查读数 | — | `fatal_census.py` | 无变化:该脚本不计阶段标签(见上) |
|
||||
|
||||
- 新用例只登记 split + spawn:knob 由 server 读,tcp fixture 带不过去。登记在 `MobileGL/MG_IntegrationTest/CMakeLists.txt:3358-3376`,前缀 `.ShaderMipDecline.`。
|
||||
- 新用例只登记 split + spawn:knob 由 server 读,tcp fixture 带不过去。登记在 `MobileGL/MG_IntegrationTest/CMakeLists.txt:3484-3502`,前缀 `.ShaderMipDecline.`。
|
||||
- 该尾自成一行加进 `MAGMA_SERVER_ENV_KNOB_NO_TCP`(`spawn_lane_parity.py:141-143`):脚本要求每个尾恰好命中一个用例,借用 `.ShaderMip1.` 会命中两个,门禁时 parity 报 1 个错,已改。
|
||||
- Espryt 不涉及第 2、3 项(Magma 专属);新用例在 DirectGLES 上按后端名跳过。第 1 项在后端无关的 applier 里。
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# P8-SE:Espryt split 残项与 P8 包说明的行号漂移
|
||||
|
||||
> 2026-09-30,分支 `p8/se`(基线 `cbbaf77f`)。简报 `~/w7/notes/p8/BRIEF-SE.md`;证据 `~/w7/notes/p8/se-evidence/`(树外)。第二波,ID-P8-13。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
| 项 | 结果 | 位置 |
|
||||
|---|---|---|
|
||||
| 1 默认帧缓冲深度 / 模板在 Espryt spawn / tcp 读 0 | 修;A 的 pending-fix 豁免行删去,用例登上三臂(含 `ForcedDs.` 三臂) | `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:16239` |
|
||||
| 2 B2 的 server CPU mip 臂 × E 的驱动写标 | 修;驱动读回被拒且纹理被驱动写过时具名拒绝 `generate-mipmap-store-declined`,不读 store | `DirectGLES.cpp:13066`、`:13134` |
|
||||
| 3 D 的 case 3 在 Espryt split 臂放开 | 放开,三臂 + inproc A/B 绿;Espryt monolith 跳过保留(证红) | `MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp:306` |
|
||||
| 4 `PipeCatalogueTest` 名单 = 调用点 | 删 `copy-image-shadow-mirror` 调用与名(G1 不变);新 lint 进 CI 与 unit 车道 | `scripts/ci/unmigrated_emulation_sites.py` |
|
||||
| 5 A–F、S 包说明行号漂移 | 213 处引用逐条对内容;87 处按内容重定位 + E.md 一行改写;S.md 无引用 | §7 |
|
||||
|
||||
- monolith 缺陷:本包未发现新的(case 3 的 Espryt monolith 红是 C 已记的 OQ15 一条)。
|
||||
|
||||
## 2. 项 1:根因与修法
|
||||
|
||||
| 事实 | 出处 |
|
||||
|---|---|
|
||||
| 发布函数原 guard `!defaultFBOInfo \|\| ...` 在 `OnSurfaceChanged` post 之前 return | `DirectGLES.cpp:16229`(函数)、`:16360`(post) |
|
||||
| spawn / tcp 的 server 进程没有前端,`pDefaultFramebufferInfo` 为空(临时探针:spawn `(nil)`,inproc 非空) | 探针未提交 |
|
||||
| 结果:client 的默认帧缓冲 depth / stencil 留在占位格式 `Depth32FStencil8` | `MobileGL/MG_Impl/Init.cpp:33`、`:37` |
|
||||
| 探针:spawn client 报 depth 32 / `GL_FLOAT`;inproc 报 24 / `GL_UNSIGNED_NORMALIZED`(pbuffer 是 D24S8) | `DirectGLES.cpp:16498` |
|
||||
| 用例按报告分配 D32F_S8,server 驱动拒绝 D24S8→D32F_S8 的深度 / 模板 blit,只打 `the depth/stencil aspect was dropped`;颜色照过,client 无 GL 错误 | `DirectGLES.cpp:10926` |
|
||||
| inproc 绿是巧合:client 与 server 同进程共用该指针 | — |
|
||||
| Magma 的发布从不看该指针(callback 臂直接 post),三臂一直绿 | `MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp:346` |
|
||||
| 修:`MOBILEGL_PIPE_PUSH` 下 callback 在场即发布,只有 monolith 臂写占位;pull 构建保留原句(`#else`) | `DirectGLES.cpp:16239`、`:16325` |
|
||||
| 登记:`mgl_itest_register_split_arms` 三臂;`ForcedDs.` 过滤器不再减掉该例 | `MobileGL/MG_IntegrationTest/CMakeLists.txt:4866`;`MobileGL/MG_IntegrationTest/P8Coverage.cmake:177-178` |
|
||||
| 豁免表 pending-fix 类清空 | `scripts/data/split_coverage_exemptions.txt:64-65` |
|
||||
|
||||
- 顺带生效:P12 服务器自有窗口在 spawn 下的 extent 发布(`g_publishWindowExtent`,`DirectGLES.cpp:16656`)被同一 guard 挡住、从未发出过;现在会发出,client 走 `ApplySurfaceChangedToClient` 的带 extent 臂(`MobileGL/MG_Remote/Client/ClientSession.cpp:325`)。主机无覆盖,需设备确认。
|
||||
|
||||
## 3. 项 2:CPU mip 臂的 store 路由
|
||||
|
||||
| 事实 | 出处 |
|
||||
|---|---|
|
||||
| E 的驱动写标按纹理、粘性 | `MobileGL/MG_Remote/Server/StagedTextureStore.h:496-502` |
|
||||
| split 的 `GenerateMipmap` 在进 `GenerateMipmapByRecord` 前无条件记驱动写:CPU 臂读 store 时标恒为真 | `DirectGLES.cpp:13296` |
|
||||
| 所以在记写之前快照 `driverWrittenBefore` 并下传;否则 store 路由(B 为不可附着存储设计的)永远拒绝 | `DirectGLES.cpp:13291`、`:13305` |
|
||||
| store 路由拒绝条件:view、无 store、驱动写过、未整层覆盖、尺寸不符 → `MGLOG_E_ONCE` `generate-mipmap-store-declined`,层不动,不 Fatal | `DirectGLES.cpp:13066`、`:13134` |
|
||||
| E 的旋钮原只在 `ReadTextureImageWire` 查,CPU 臂直接调 `ReadTextureLevelTight`,主机到不了 store 路由;现在 CPU 臂也查(前向声明) | `MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc:445`;`DirectGLES.cpp:13026`、`:13045` |
|
||||
| 粒度:标按纹理不按层,纹理被生成过一次或任一层被画过,之后读回被拒的生成一律拒绝(保守,不给旧字节) | — |
|
||||
|
||||
| 用例(`GenerateMipmapServerScenario`) | 形状 | 断言 | 行 |
|
||||
|---|---|---|---|
|
||||
| `Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName` | level 0 上传哨兵色后当渲染目标画上下两半 | server 日志有 `generate-mipmap-store-declined`、无 store 滤波行;level 1..3 不是哨兵色 | `MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp:408`、`:546` |
|
||||
| `Rgb16fUploadedBaseWithARefusedReadbackIsFilteredFromTheStore`(对照) | 只上传 level 0 | 日志有 `filtering the staged store's copy`、无拒绝;level 1..3 正确 | `GenerateMipmapServerScenario.cpp:439`、`:550` |
|
||||
|
||||
| 条目 | 臂 | 登记 |
|
||||
|---|---|---|
|
||||
| `DirectGLES.{Split,Spawn}.CpuMipRefused.*` | 旋钮 `MGITEST_ESPRYT_FORCE_CPU_MIPMAP=1` + `MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=16x16`,标签 `integration-gpu`、`integration-cpu-mip-<arm>` | `MobileGL/MG_IntegrationTest/CMakeLists.txt:4883` |
|
||||
| 其他臂(monolith、无旋钮 split 三臂、Magma) | 两例按旋钮缺席跳过 | 环境发现 / `GenerateMipmapServerScenario.*` |
|
||||
|
||||
- 不上 tcp:server 是共享 `TcpServer.Start`,16x16 拒绝会碰到整条 lane 的 16x16 颜色读回;与 B 的 `CpuMip.` 同形,parity 无需例外行(实测 0 错)。
|
||||
|
||||
## 4. 项 3:D 的 case 3
|
||||
|
||||
- 跳过条件由「非 Magma」改为「非 Magma 且 monolith」(`WireIndirectDrawScenario.cpp:306`);Espryt split 的 count 字由 C 的 `SplitHostBytesForCpuRead` 刷新(`DirectGLES.cpp:9632`、`:9798`)。
|
||||
- 计数断言只在 Magma inproc(`CountersReadable`),Espryt 只断言像素。
|
||||
|
||||
## 5. 项 4:名单与调用点
|
||||
|
||||
| 事实 | 出处 |
|
||||
|---|---|
|
||||
| `copy-image-shadow-mirror` 调用在镜像函数自己的 transport return 之后,且在 `#if MOBILEGL_PIPE_PUSH` 内:任何 transport 到不了,pull 构建不编译它 | `DirectGLES.cpp:13580`、`:13582-13585` |
|
||||
| 删调用与名:G1 符号 +0 / −0,`.text` `a5ba13` 不变 | §8 |
|
||||
| 名单现 3 名,一名一处调用 | `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp:1448-1455` |
|
||||
| lint 规则(全是 error):名单每名恰一处产品调用;每处调用名在名单;名单无重复;调用须是字符串字面量。产品源 = `MobileGL/` 去掉 `MG_Test/`、`MG_IntegrationTest/`;先掩码注释 | `scripts/ci/unmigrated_emulation_sites.py` |
|
||||
| CI:`test.yml` 在 `split_coverage.py` 之后一步 | `.github/workflows/test.yml:1475` |
|
||||
| unit 车道:`PipeCatalogue.UnmigratedEmulationSites{SelfTest,MatchTheList}` | `MobileGL/MG_IntegrationTest/CMakeLists.txt:4895-4901` |
|
||||
| 门命令:`python3 scripts/ci/unmigrated_emulation_sites.py --self-test && python3 scripts/ci/unmigrated_emulation_sites.py` | — |
|
||||
| 回看:lint 在 `8d3e2317`(B 之前)红 2 条:`generate-mipmap-storage` 4 处调用、`generate-mipmap-cpu-filter` 不在名单;在 `cbbaf77f` 绿(4 名 4 处) | 证据 `r4b-lint-*.log` |
|
||||
|
||||
## 6. red-once
|
||||
|
||||
| # | 变异 | 载体 | 观察到的红 |
|
||||
|---|---|---|---|
|
||||
| R1 | 发布 guard 改回 `if (!defaultFBOInfo) return;`(= 基线) | `DirectGLES.{Split,Spawn,Tcp}.DepthStencilReadbackAttachmentShapeScenario.DefaultFramebuffer…` | Spawn、Tcp 红(depth 0、stencil 0),Split 绿 |
|
||||
| R1′ | 同上 | 三个 `ForcedDs.` 条目 | Spawn、Tcp 红,Split 绿 |
|
||||
| R2a | 去掉 `driverWrittenBefore` 判定 | `DirectGLES.{Split,Spawn}.CpuMipRefused.*` | 画过的例两臂红:level 1..3 = 哨兵 `rgba(255,0,255,255)`、无拒绝行、有 store 滤波行;对照绿 |
|
||||
| R2b | 快照挪到自身记写之后 | 同上 | 对照两臂红:store 路由拒绝、level 1..3 `rgba(0,0,0,255)`;画过的例绿 |
|
||||
| R2c | CPU 臂不查拒绝旋钮 | 同上 | 4/4 红:拒绝行与 store 滤波行都没有(原生读回路径) |
|
||||
| R3 | 两处 `*IndirectCount` 参数读改回 `SplitHostBytes`(去 C 的刷新) | `DirectGLES.{Split,Spawn,Tcp}.WireIndirectDrawScenario.ComputeWrittenCount…` | 三臂红:两处 count 画错条带 |
|
||||
| R3m | 去掉 Espryt monolith 跳过 | `DirectGLES.WireIndirectDrawScenario.ComputeWrittenCount…`(monolith) | 红:count 1 却画了条带 2、3(OQ15,跳过须保留) |
|
||||
| R4a | `kNames` 加回 `copy-image-shadow-mirror` | lint;`PipeCatalogue.UnmigratedEmulationSitesMatchTheList` | rc 1:「listed and has no call site」;ctest 红 |
|
||||
| R4b | `--self-test` 六例(多名、漏名、同名两处、名单重复、非字面量、匹配) | lint | 各规则按预期 rc;匹配树 rc 0 |
|
||||
|
||||
还原一律复制比对 + `touch`(`cmp` 一致)后重建再跑绿。
|
||||
|
||||
## 7. 项 5:行号漂移
|
||||
|
||||
- 方法:每份说明从最后写它的提交(A `06f28b0a`、B `b9feb9d7`、C `e815ce8d`、D `d639e546`、E `c00f5eab`、F `c7a14ed7`)经 `git diff -U0` 把每处 `file:line` 与同行 `:line` 简写映射到 HEAD;映射到的行与原行内容逐字相同。工具未提交。
|
||||
|
||||
| 说明 | 引用 | 未动 | 重定位 | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| A | 21 | 7 | 14 | — |
|
||||
| B | 31 | 2 | 29 | — |
|
||||
| C | 30 | 20 | 10 | — |
|
||||
| D | 44 | 32 | 12 | `WireDraw.inc` +7(F)、`WireDeclines.def` +6、`CMakeLists` D 块 `:4831-4864` |
|
||||
| E | 41 | 21 | 18 + 1 行改写 | 第 93 行:调用与名已由本包删,改指删除注释与名单 |
|
||||
| F | 46 | 42 | 4 | 第 11 行「旧 `:1427`…」是 F 之前的位置,按原意不动;`:1460` 属 `WireFramebuffer.inc`,未变 |
|
||||
| S | 0 | — | — | — |
|
||||
|
||||
- 集成方简报里的几处量值是中间合并点的:B 的 `CMakeLists` `:3355` 在 HEAD 是 `:3442`(非 `:3375`),`:4059` 是 `:4174`,`:2154`(`TcpServer.Start` 的 fixture 属性)是 `:2168`;D 的 `:4651` 是 `:4831`(非 `:4764`);按内容为准。
|
||||
- 只改行号,不改历史陈述:B.md 的「名单保持 4 名」是 B 当时的读数,现为 3 名(§5)。
|
||||
|
||||
## 8. 门
|
||||
|
||||
`gate.sh ~/w7/p8-se p8se`,头 `edcb9328`,dirty 0(`~/w7/notes/p8/se-evidence/gate-p8se.log`);与第一波集成头(ID-P8-12)对比。
|
||||
|
||||
| 项 | 读数 | 变化 |
|
||||
|---|---|---|
|
||||
| G1 | 符号 `added=0 removed=0`(clang 22.1.6);`.text` `a5ba13` | 不变 |
|
||||
| fatal census / link ratchet | 78 abort 点 / 20 文件 / 45 族;172 | 不变(`MGPipeUnmigratedEmulation` 调用不是 abort 点) |
|
||||
| parity / protocol pin / wire-declines | 0 错;revision 5;58 行 59 处 0 未记 | 不变 |
|
||||
| hygiene | 13/13 OK(含 `split_coverage`、`doc_citations`) | — |
|
||||
| split_coverage | DirectGLES monolith 703 = 门控 615 + 豁免 88,missing 0;DirectVulkan 703 = 门控 222 + 信息层 449 + 豁免 32 | monolith +2(两个新例);Espryt 门控 +3,豁免 −1(pending-fix) |
|
||||
| unit | 2723 | +2(lint 两条 ctest) |
|
||||
| integration-split / spawn / tcp | 739 / 653 / 665 | 各 +4:默认帧缓冲例、其 `ForcedDs.` 条目、两个新例(无旋钮臂跳过) |
|
||||
| magma split / spawn / tcp / full-split | 271 / 248 / 216 / 670 | full-split +2(两个新例经 tier 3 回放) |
|
||||
| integration-gpu[monolith] / [inproc] | 3704 / 3704 | +20:默认帧缓冲例的 Espryt 三臂与 `ForcedDs.` 三臂 6;两个新例 14(两后端 monolith 4、Espryt 三臂 6、`CpuMipRefused.` 4) |
|
||||
| retrace(`MOBILEGL_BUILD_TRACE_REPLAY=ON`,236 条) | 230 过;6 红 = 已知 `iris-iterationrp-in-world`(两后端 × monolith / SPLIT / SPAWN,主机 llvmpipe JIT `Cannot select`) | 与第一波相同(`retrace-p8se.log`) |
|
||||
|
||||
## 9. 发现未修
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| P12 extent 发布在 spawn 下首次生效 | §2 末条;需设备(红米 FCL→render server)确认 |
|
||||
| store 路由粒度 | 驱动写标按纹理;按层的标需要改 E 的 store 接口 |
|
||||
| B.md / E.md 的历史陈述 | 名单数、`copy-image-shadow-mirror` 的去向以本说明为准 |
|
||||
Executable
+217
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""P8-SE: PipeCatalogueTest's unmigrated-emulation list IS the set of call sites in the tree.
|
||||
|
||||
THE QUESTION. `MGPipeUnmigratedEmulation("<name>")` (MG_Pipe/PipeApply.h) names one emulation
|
||||
that reaches into client memory; a split server aborts on it, a monolith arm runs past it. The
|
||||
test `PipeCatalogue.EveryUnmigratedEmulationIsNamedOnce` (MG_Test/Pipe/PipeCatalogueTest.cpp)
|
||||
pins the LIST of names, and nothing compared that list with the tree: it drifted twice without a
|
||||
red (P8-B found a name the list never had; P8-SE a name whose only call site no transport could
|
||||
reach). This asserts, every one an error:
|
||||
|
||||
* every name in the test's `kNames[]` has EXACTLY ONE call site in the product sources;
|
||||
* every call site's name is in `kNames[]`;
|
||||
* `kNames[]` has no duplicate;
|
||||
* every call is spelled with a string literal (a computed name cannot be checked).
|
||||
|
||||
PRODUCT SOURCES are MobileGL/ minus MG_Test/ and MG_IntegrationTest/ (tests call the function
|
||||
with literals of their own), in C/C++ files (.c .cc .cpp .h .hpp .inc .mm). Comments are masked
|
||||
before matching, string literals are kept, so a name quoted in a comment is not a call site.
|
||||
|
||||
Usage: unmigrated_emulation_sites.py [repo-root] (default: this script's repository)
|
||||
unmigrated_emulation_sites.py --self-test
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
LIST_FILE = Path("MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp")
|
||||
TEST_NAME = "EveryUnmigratedEmulationIsNamedOnce"
|
||||
SOURCE_ROOT = Path("MobileGL")
|
||||
EXCLUDED = (Path("MobileGL/MG_Test"), Path("MobileGL/MG_IntegrationTest"))
|
||||
SUFFIXES = {".c", ".cc", ".cpp", ".h", ".hpp", ".inc", ".mm"}
|
||||
CALL = re.compile(r"\bMGPipeUnmigratedEmulation\s*\(")
|
||||
LITERAL_CALL = re.compile(r'\bMGPipeUnmigratedEmulation\s*\(\s*"((?:[^"\\]|\\.)*)"\s*\)')
|
||||
# The declaration and the definition: the return type (and any qualifier) right before the name
|
||||
# on its line.
|
||||
DECLARATION = re.compile(r"\bvoid\s+(?:[A-Za-z_]\w*::)*$")
|
||||
|
||||
|
||||
def mask_comments(text):
|
||||
"""`text` with every comment character replaced by a space (newlines kept, so offsets and line
|
||||
numbers survive); string and character literals are left as they are."""
|
||||
out = list(text)
|
||||
i, n = 0, len(text)
|
||||
while i < n:
|
||||
c = text[i]
|
||||
if c == "/" and i + 1 < n and text[i + 1] == "/":
|
||||
while i < n and text[i] != "\n":
|
||||
out[i] = " "
|
||||
i += 1
|
||||
elif c == "/" and i + 1 < n and text[i + 1] == "*":
|
||||
end = text.find("*/", i + 2)
|
||||
end = n if end < 0 else end + 2
|
||||
for k in range(i, end):
|
||||
if text[k] != "\n":
|
||||
out[k] = " "
|
||||
i = end
|
||||
elif c in "\"'":
|
||||
# R"(...)" raw strings: skip to the matching delimiter.
|
||||
if c == '"' and i > 0 and text[i - 1] == "R":
|
||||
close = text.find("(", i)
|
||||
delim = text[i + 1:close]
|
||||
end = text.find(")" + delim + '"', close)
|
||||
i = n if end < 0 else end + len(delim) + 2
|
||||
continue
|
||||
i += 1
|
||||
while i < n and text[i] != c:
|
||||
i += 2 if text[i] == "\\" else 1
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def list_names(root, errors):
|
||||
path = root / LIST_FILE
|
||||
try:
|
||||
text = mask_comments(path.read_text(encoding="utf-8"))
|
||||
except OSError as exc:
|
||||
errors.append(f"{LIST_FILE}: cannot read ({exc})")
|
||||
return []
|
||||
test = text.find(TEST_NAME)
|
||||
block = re.search(r"kNames\s*\[\s*\]\s*=\s*\{(.*?)\};", text[test:], re.S) if test >= 0 else None
|
||||
if block is None:
|
||||
errors.append(f"{LIST_FILE}: no `kNames[] = {{ ... }};` in {TEST_NAME}")
|
||||
return []
|
||||
return re.findall(r'"((?:[^"\\]|\\.)*)"', block.group(1))
|
||||
|
||||
|
||||
def call_sites(root, errors):
|
||||
"""{name: [file:line, ...]} over the product sources."""
|
||||
sites = {}
|
||||
base = root / SOURCE_ROOT
|
||||
for path in sorted(base.rglob("*")):
|
||||
if path.suffix not in SUFFIXES or not path.is_file():
|
||||
continue
|
||||
rel = path.relative_to(root)
|
||||
if any(rel.is_relative_to(ex) for ex in EXCLUDED):
|
||||
continue
|
||||
text = mask_comments(path.read_text(encoding="utf-8", errors="replace"))
|
||||
if "MGPipeUnmigratedEmulation" not in text:
|
||||
continue
|
||||
literal_starts = set()
|
||||
for match in LITERAL_CALL.finditer(text):
|
||||
line = text.count("\n", 0, match.start()) + 1
|
||||
sites.setdefault(match.group(1), []).append(f"{rel.as_posix()}:{line}")
|
||||
literal_starts.add(match.start())
|
||||
for match in CALL.finditer(text):
|
||||
if match.start() in literal_starts:
|
||||
continue
|
||||
prefix = text[text.rfind("\n", 0, match.start()) + 1:match.start()]
|
||||
if DECLARATION.search(prefix):
|
||||
continue
|
||||
line = text.count("\n", 0, match.start()) + 1
|
||||
errors.append(f"{rel.as_posix()}:{line}: MGPipeUnmigratedEmulation called without a string "
|
||||
f"literal - the name cannot be checked against {LIST_FILE.name}")
|
||||
return sites
|
||||
|
||||
|
||||
def check(root):
|
||||
errors = []
|
||||
names = list_names(root, errors)
|
||||
sites = call_sites(root, errors)
|
||||
seen = set()
|
||||
for name in names:
|
||||
if name in seen:
|
||||
errors.append(f"{LIST_FILE}: \"{name}\" is listed twice")
|
||||
seen.add(name)
|
||||
for name in sorted(seen):
|
||||
where = sites.get(name, [])
|
||||
if not where:
|
||||
errors.append(f"\"{name}\" is in {LIST_FILE.name}'s list and has no call site in the tree "
|
||||
f"(retired? take it out of the list)")
|
||||
elif len(where) > 1:
|
||||
errors.append(f"\"{name}\" has {len(where)} call sites ({', '.join(where)}); one site per name")
|
||||
for name in sorted(set(sites) - seen):
|
||||
errors.append(f"\"{name}\" is called at {', '.join(sites[name])} and is not in {LIST_FILE.name}'s "
|
||||
f"list")
|
||||
return names, sites, errors
|
||||
|
||||
|
||||
def run(root):
|
||||
names, sites, errors = check(root)
|
||||
for err in errors:
|
||||
print(f"error: {err}")
|
||||
total = sum(len(v) for v in sites.values())
|
||||
state = "OK" if not errors else f"{len(errors)} error(s)"
|
||||
print(f"unmigrated emulation sites: {len(names)} listed, {total} call sites - {state}")
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
def self_test():
|
||||
"""Each rule reds on a synthetic tree and a matching tree is green."""
|
||||
listing = """
|
||||
TEST(PipeCatalogue, EveryUnmigratedEmulationIsNamedOnce) {
|
||||
// "commented-out-name" is not in the list
|
||||
const char* const kNames[] = {
|
||||
%s
|
||||
};
|
||||
}
|
||||
"""
|
||||
product = """
|
||||
void MGPipeUnmigratedEmulation(const char* name);
|
||||
void Site() {
|
||||
// MGPipeUnmigratedEmulation("in-a-comment");
|
||||
/* MGPipeUnmigratedEmulation("in-a-block-comment"); */
|
||||
%s
|
||||
}
|
||||
"""
|
||||
cases = [
|
||||
("a matching tree", ['"a",', '"b",'], ['MGPipeUnmigratedEmulation("a");',
|
||||
'MG_Pipe::MGPipeUnmigratedEmulation( "b" );'], 0),
|
||||
("a stray listed name", ['"a",', '"b",', '"stray",'], ['MGPipeUnmigratedEmulation("a");',
|
||||
'MGPipeUnmigratedEmulation("b");'], 1),
|
||||
("an unlisted call site", ['"a",'], ['MGPipeUnmigratedEmulation("a");',
|
||||
'MGPipeUnmigratedEmulation("b");'], 1),
|
||||
("two sites for one name", ['"a",'], ['MGPipeUnmigratedEmulation("a");',
|
||||
'MGPipeUnmigratedEmulation("a");'], 1),
|
||||
("a duplicated list entry", ['"a",', '"a",'], ['MGPipeUnmigratedEmulation("a");'], 1),
|
||||
("a computed name", ['"a",'], ['MGPipeUnmigratedEmulation("a");',
|
||||
'MGPipeUnmigratedEmulation(name);'], 1),
|
||||
]
|
||||
failures = 0
|
||||
for label, listed, calls, want in cases:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
(root / LIST_FILE).parent.mkdir(parents=True)
|
||||
(root / LIST_FILE).write_text(listing % "\n".join(" " + s for s in listed))
|
||||
src = root / "MobileGL/MG_Backend/Site.cpp"
|
||||
src.parent.mkdir(parents=True)
|
||||
src.write_text(product % "\n".join(" " + c for c in calls))
|
||||
# A test's own literal call is not a product call site.
|
||||
other = root / "MobileGL/MG_Test/Other/OtherTest.cpp"
|
||||
other.parent.mkdir(parents=True)
|
||||
other.write_text('void T() { MGPipeUnmigratedEmulation("only-in-a-test"); }\n')
|
||||
_, _, errors = check(root)
|
||||
got = 1 if errors else 0
|
||||
if got != want:
|
||||
failures += 1
|
||||
print(f"self-test FAILED: {label}: rc {got} (want {want}); errors: {errors}")
|
||||
print("self-test " + ("OK" if failures == 0 else f"FAILED ({failures})") + f": {len(cases)} cases")
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("root", nargs="?", default=str(Path(__file__).resolve().parents[2]))
|
||||
parser.add_argument("--self-test", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.self_test:
|
||||
return self_test()
|
||||
return run(Path(args.root))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -62,4 +62,4 @@ SpirvShaderBinaryScenario.SpecializeShaderErrorSurfaceMatchesTheExtension no-rec
|
||||
ProgramPipelineScenario.AReservedNameTakesStateBeforeItIsAProgramPipeline no-records a reserved pipeline name's state is client-side and draws nothing
|
||||
|
||||
# ---- pending-fix -------------------------------------------------------------------------------
|
||||
DirectGLES:DepthStencilReadbackAttachmentShapeScenario.DefaultFramebufferDepthStencilFormatIsBlitCompatible pending-fix Espryt spawn+tcp blit the default framebuffer's depth out as 0 (inproc and all Magma arms green, knob or not); no P8 package owns it - DEBTS.md row requested in notes/p8/A.md
|
||||
# (none: P8-SE fixed the default-framebuffer depth/stencil case and registered it, notes/p8/SE.md)
|
||||
|
||||
Reference in New Issue
Block a user