Files
MobileGL/docs/Disaggregated/notes
swung0x48 76b7469389 [MG_Backend, MG_IntegrationTest, docs] (P7): the wire readback waits for the frame it is reading, instead of inferring who wrote it
Exit gate 3, OpenRA on the Redmi. The mechanism, in two lines of this tree:

  WireFramebuffer.inc, ReadWirePixels: `if (HasPendingRecordedWork() &&
  !FlushPendingCommands())` - FlushPendingCommands (VulkanRenderer.cpp:13534-13598)
  puts the frame's draws on the queue under a POOLED FENCE OF ITS OWN and returns
  without waiting; there is no vkWaitForFences on its success path. The
  vkCmdCopyImageToBuffer that follows is recorded into a fresh command buffer and
  becomes a LATER submission.

  VulkanRenderer.cpp:10707-10748, SubmitReadbackCommandsAndWait: it waited ONE fence,
  its own, and then called OnSubmitsCompletedUpTo(frame.lastSubmitIndex) - which
  RECORDED the earlier submission as complete and retired its fence, so afterwards
  nothing could observe that it had not been waited for. The comment beneath it said
  "the wait proved every submission complete". That is true of the monolith arm, which
  never flushes before a readback and therefore has its draws and its copy in one
  submission under one fence. It was not true of the wire arm.

The only thing tying the two submissions together was the pipeline barrier that the
readback's TransitionWireImage records - and that barrier derives its SOURCE scope from
the image's tracked layout, through GetImageTransitionSourceState
(VulkanRenderer.cpp:1784-1825). Two of its arms, VK_IMAGE_LAYOUT_UNDEFINED and
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR (:1787-1791), return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
with no access bits, which is Vulkan's spelling of an EMPTY first scope: wait for
nothing. A just-presented default framebuffer is in the second of those layouts. The
slower pipeline creation is, the longer the draws' submission runs, and the more of the
frame's LAST-drawn objects the copy can miss - which is what the device's diff image
shows.

Measured on this host (OpenRA, DirectVulkan, inproc), at the moment the copy is
recorded: submit=72 completed=71. The submission carrying the whole frame's draws is in
flight and unwaited. That is structural, not a coin flip, and it reproduces every run.

The fix is two halves, both wire-only:

 - RecordWireReadbackWriteBarrier (WireFramebuffer.inc, new): the readback records its
   own ALL_COMMANDS -> TRANSFER, MEMORY_WRITE -> TRANSFER_READ|MEMORY_READ barrier
   before the copy rather than inferring from a layout who it should wait for. A
   barrier's first scope is every command earlier in SUBMISSION order on the queue, so
   it reaches across the submission boundary.
 - SubmitReadbackCommandsAndWait now waits every in-flight fence at or below its own
   submission index. Under #if MOBILEGL_BUILD_DISAGGREGATED: the pull build has no wire
   arm, cannot flush before a readback, and is the image G1 pins - its single-fence wait
   is unchanged. The cost is nothing a readback does not already pay.

THE COORDINATOR'S REPRODUCTION PATH DOES NOT WORK ON THIS HOST, and that is recorded
rather than glossed. MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS is added as asked
(split-only, inert unless set, on the pipeline-creation MISS path in
GetOrCreatePipelineWithInput) and it does bite - the OpenRA DirectVulkan SPLIT retrace
goes from 1.88 s to 4.53 s at 25 ms - but ssim stays 1.000000. The reason is in the
probe above: this host's default framebuffer is tracked as VK_IMAGE_LAYOUT_GENERAL at
the readback, which takes GetImageTransitionSourceState's `default:` arm (ALL_COMMANDS /
MEMORY_WRITE) and orders the copy by accident. The device's is not GENERAL.

Red once (executed, reverted) is therefore a COUNT, not a picture, and it is
deterministic: instrumenting how many fences the readback actually waits on gives
fences=2 upTo=73 completed=71 with the fix (submission 72, the draws, plus its own 73)
and fences=1 without it, with completed=71 either way - i.e. 72 provably outstanding and
provably unwaited before.

Lane: three readback-after-work cases (VertexIdSamplerAndScalarUniformPixels,
ComputeImageStoreFramebufferPixels, GenerateMipmapPixels) registered on the three Magma
arms under MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS=25, prefix .PipelineDelay. They are a
REGRESSION GATE, not the reproduction: they pin that a readback still returns the draw's
pixels when every pipeline creation on the path costs 25 ms.

Gates: integration-magma-split 84 (+3), -spawn 63 (+3), -tcp 65 (+3), 0 red;
ctest -L unit 2410; informational integration-magma-full-split 516, 0 red, 59 skip;
fatal_census green; link_ratchet --assert-monotone unchanged at 186, p7-magma 101;
G1 pull .text 0xa52203, defined-symbol name list identical (30570); OpenRA DirectVulkan
SPLIT retrace ssim 1.000000 with and without the delay, two runs each.

Owed to the device: the three cold-cache repeats. If they still diverge, the next two
suspects are named in notes/p7/magma-b.md §5.6 - the eleven silent `return false` drop
sites in SetupWireDraw (one of which is `pipeline == VK_NULL_HANDLE`, on the same path
as the device log's three "handing vkCreateShaderModule an INVALID module" errors), and
SetImageContentDefined being set BEFORE the draw is recorded on the wire arm while the
monolith sets it from EndRenderPass, which silences the "no write is recorded" warning
exactly when it should fire. Neither is in this commit's scope.
2026-09-22 16:37:54 -04:00
..

工作笔记(原 ~/w7/notes/)

这一层是 MGPipe 各阶段的工作笔记原件:包计划(BRIEF-*.md)、契约草稿、集成者逐条裁定 (INTEGRATOR-DECISIONS-*.md)、只读审计、普查、设备记录、包任务书(TASK-*.md)与阶段报告。

它们此前只存在于一台机器的 ~/w7/notes/ 下。上层那几份文档(ROADMAP.md、 CURRENT_STAGE_PROGRESS.md、MEASUREMENTS.md …)大量引用它们——裁定编号、审计行号、 逐门数字的出处几乎全在这里。一份引用了几百次、却只存在于本地未跟踪目录的记录,等于没有记录。 所以整体移进仓库。

路径映射

~/w7/notes/<phase>/<file>.md   →   docs/Disaggregated/notes/<phase>/<file>.md

上层文档里凡写作 ~/w7/notes/... 的 .md 引用,都按这条规则读。

移进来的是什么,没移的是什么

移进来了 全部 .md,415 份,约 11 MB,目录结构原样保留
没有移 脚本(.sh / .py)、运行日志(.log / .out / .err)、普查与门的结果数据(.json / .txt / .tsv)、补丁、APK 与 .idsig、nm 转储

没移的那些仍在 ~/w7/notes/ 与各构建树下。它们是证据数据而不是文档:体积大、随每次运行 重新产生、且多数只在产生它的那台机器上有意义(绝对路径、设备序列号、构建目录)。上层文档引用 它们时给的是路径,不是内容。

两个需要知道的例外

  1. P6 的包计划已提升为正式文档:原 ~/w7/notes/p6/BRIEF-P6.md 现在是 ../P6-SPAWN-PLAN.md,本目录不再保留副本,避免两份同源文档各自漂移。 契约草稿同理,见 ../P6-CONTRACT-DRAFT.md。
  2. recovered/ 是从失败的 workflow 运行里捞回来的材料(wf1…wf5),与同名阶段目录 可能重复,保留是因为其中几份是当时唯一的完整版本。查阶段记录请先看阶段目录。

不受 citation lint 管

CI 的文档引用 lint 只吃 docs/Disaggregated/*.md(非递归,.github/workflows/test.yml), 本目录不在其中,这是有意的:这些笔记的 file:line 是写在各自当时的代码头上的,拿今天的 HEAD 去解析必然大面积失败,而那不是错误——那是它们作为历史记录的正确状态。要核对某份笔记的 引用,用它自己声明的那个 revision:

python3 scripts/check_doc_citations.py --rev <当时的头> docs/Disaggregated/notes/<phase>/<file>.md