mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Docs (P15): 1c rows (Magma inproc +5.0 % fps), PARITY-AUDIT M1 cross-FBO deferral, Espryt attrib-format split parked.
This commit is contained in:
@@ -109,6 +109,23 @@ Host call counts per frame (diagnostic counting builds; FCL vanilla; 600-frame w
|
||||
pass-begin cost with them.
|
||||
2. **Cut 1 stays local** (no measurable gain).
|
||||
|
||||
**Order from 2026-10-09:**
|
||||
1. 1c deferred clears (PARITY-AUDIT M1).
|
||||
2. The remaining Magma barriers.
|
||||
3. The pipe-record pieces.
|
||||
4. The TLS hoist.
|
||||
|
||||
**Measured and parked: Espryt `glVertexAttribPointer` -> `glBindVertexBuffer` + `glVertexAttribFormat`.**
|
||||
- Ceiling: `SyncToBackendFromApplier` costs 0.060 ms/frame inclusive, driver calls included
|
||||
(0.015 self). Source: the c4 p15w Espryt profile, cpuhunt.
|
||||
- The repeats are MC's per-draw re-point: the same layout with a new buffer at offset 0, about
|
||||
4 attributes per buffer. The split would turn 1 `glBindBuffer` + ~4 pointer calls into 1
|
||||
`glBindVertexBuffer`.
|
||||
- Expected saving: ~0.02-0.04 ms/frame. That is below H2 resolution (1.2-2.9% spread).
|
||||
- Cost: a second VAO shadow (format + binding). ES `glVertexAttribPointer` and
|
||||
`glVertexAttribDivisor` silently rebind attribute -> binding, so the change is
|
||||
correctness-sensitive.
|
||||
|
||||
The original list follows.
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ have an equivalent?
|
||||
|
||||
| # | mechanism (dev / 88a11b81) | wire arm at HEAD | profile evidence | verdict, cost |
|
||||
|---|---|---|---|---|
|
||||
| M1 | **Clears.** `VkClearManager`: a glClear inside the active pass on the same attachments became `ClearAttachmentsOnActiveRenderPass`; otherwise a pending clear keyed (texture identity, level, layer) turned the next pass's load op into CLEAR, materialised before reads, blits and copies | `ClearWireFramebuffer`: ends the draw pass; per surface, a new view + render pass + framebuffer, its own pass, then retire | FCL 0.42-0.46 ms (dev ~0.05); rd12 ~0.27 ms. Dev's per-draw `HasPendingClear` probe cost ~0.6 ms/frame on rd12 | **port, in two steps.** (a) The in-pass half: the clear takes the draw pass a draw to the same framebuffer would take (`PrepareWireDrawPass`) and records `vkCmdClearAttachments` in it. No new state, nothing deferred, so no read path has to materialise anything. (b) LOAD_OP_CLEAR only if a GPU-side reason appears; CPU-wise (a) removes the object churn and the pass break. Small cost, low risk |
|
||||
| M1 | **Clears.** `VkClearManager`: a glClear inside the active pass on the same attachments became `ClearAttachmentsOnActiveRenderPass`; otherwise a pending clear keyed (texture identity, level, layer) turned the next pass's load op into CLEAR, materialised before reads, blits and copies | `ClearWireFramebuffer`: ends the draw pass; per surface, a new view + render pass + framebuffer, its own pass, then retire | FCL 0.42-0.46 ms (dev ~0.05); rd12 ~0.27 ms. Dev's per-draw `HasPendingClear` probe cost ~0.6 ms/frame on rd12 | **port, in two steps.** (a) The in-pass half: the clear takes the draw pass a draw to the same framebuffer would take (`PrepareWireDrawPass`) and records `vkCmdClearAttachments` in it. No new state, nothing deferred, so no read path has to materialise anything. (b) LOAD_OP_CLEAR only if a GPU-side reason appears; CPU-wise (a) removes the object churn and the pass break. Small cost, low risk **Update 2026-10-09 (cut 1c):** (a) covered same-framebuffer clears only. Minecraft 1.21.5 clears through framebuffers of their own (a depth-only one for the shared depth texture, one per colour target) and draws through another framebuffer holding the same images. Every such clear then began a LOAD_OP_CLEAR pass that did nothing but clear: 11.5 passes/frame against dev's 4.1. Dev handled the cross-framebuffer case with its texture-keyed pending clears, deleted as dead code in P13 W6. Cut 1c restores it, keyed on the image itself (the storage resource, level and layers; never a GL name): a clear of images the open pass holds goes in-pass, a whole-image clear of other images waits on the image (folded into the next pass that draws into it as LOAD_OP_CLEAR, or run at the first other use through SyncTextureResourceByHandle), and the default framebuffer stays excluded (the known openra Adreno in-pass issue). See MONOLITH-DIFF.md. |
|
||||
| M2 | **Render pass + framebuffer cache.** `VkRenderPassManager::GetOrCreateRenderPass`, hash-keyed `RenderPassEntry` (render pass, framebuffer), garbage-collected at present | `m_wireDrawPassCaches`: exact-key reuse within one frame slot, emptied when the slot's frame begins (`ClearWireDrawPassCache`). Every frame rebuilds each framebuffer's pass, views and VkFramebuffer | FCL: vkCreateFramebuffer in SetupWireDraw 0.04, `~RenderPassEntry` 0.04-0.05, plus views | **port**: keep entries across frames while the key's image epochs hold, and retire through the submit-indexed list when an image goes away. Medium cost, medium risk (view lifetime against image release) |
|
||||
| M3 | **Blit resources.** `InitializeBlitResources`, `TryBlitToDefaultFramebufferWithShader` (persistent pipeline and pool) | `BlitWireColorImage`: persistent pipelines, but per blit a descriptor **pool** + set, 2 views and a framebuffer, all retired | FCL 0.13 ms (vkCreateDescriptorPool 0.084 + destroy) vs dev 0.02 | **port**: one pool per frame slot, reset when the slot's frame begins; a full pool falls back to the old path. Views and framebuffer are next if they show up. Small cost, low risk |
|
||||
| M4 | **Buffer sub-data.** `VkBufferManager::OnSubData`: a busy resident buffer took a staged copy (`StagedRangeCopy`, as now). A *streamed* buffer (`AcquireStreamedSlice`) updated only the CPU shadow, and the next draw streamed the whole buffer into a fresh arena slice, so no copy and no pass break | `WriteWireBuffer`: busy → `StagedWireRangeCopy`, which ends the pass and records two full barriers; idle → host memcpy. No streamed/renamed buffers on the wire | FCL 0.28 ms (11 sub-data calls/frame, ~25 µs each), and each one splits the pass on the GPU. WBUF probe: 4.5 host + 6.7 staged writes/frame; ~5.6 of the staged ones hit one 288-byte store | **ported**: write renaming for stores of 64 KB or less with a host shadow; the rename goes through the same orphan release as glBufferData, which every bind memo already handles (VkBuffer compare + destroy epoch) |
|
||||
|
||||
@@ -77,11 +77,51 @@ H2 sessions:
|
||||
| safter ~22:00 | p15s = 0b96a3ef | 321.5 (1.70) | 0.746 / 0.84 dev (3.18) | 0.768 / 0.81 dev (2.91) | - | - |
|
||||
| pt ~23:00 | p15t = 3def5cb2 (p15s + Magma blit cache); inproc Magma also p15s | 319.0 (1.69) | 0.755 / 0.85 dev (3.17) | 0.787 / 0.83 dev (2.83) | - | p15s 1.019 (1.36 / 1.42); p15t 1.032 (1.37 / 1.39) |
|
||||
| c1 ~00:30 (10-09) | t = p15t; u = p15t + cut 1 (in-frame uploads, local) | 324.8 (1.69) | - | t 0.782 / 0.82 dev (2.80); u 0.769 / 0.81 dev (2.86) | u 0.997 (1.38 / 1.63) | u 1.011 (1.37 / 1.39) |
|
||||
| c1b ~02:10 (10-09) | t = p15t; v = p15t + 1b (coalesced pass barriers, local) | 320.9 (1.69) | - | t 0.783 / 0.82 dev (2.85); v 0.789 / 0.83 dev (2.85) | - | v 1.048 (1.34 / 1.345) |
|
||||
| c4 ~02:55 (10-09) | v (as c1b); w = v + cut 4 (Espryt surface-size memo, 66e3b2c1) | 321.6 (1.70) | v 0.757 / 0.85 dev (3.18); w 0.762 / 0.86 dev (3.05) | - | - | - |
|
||||
| c1c2 ~03:30 (10-09) | t = p15t; y = p15t + 1b + 1c (deferred clears) + cut 4 | 321.3 (1.70) | - | t 0.795 / 0.83 dev (2.83); y 0.785 / 0.82 dev (2.74)⁴ | - | t 1.025 (1.35 / 1.41)⁵; y 1.076 (1.33 / 1.24) |
|
||||
|
||||
- **pt:** the blit view cache gives Magma inproc +1.2 % fps, with apply −0.03 ms/frame.
|
||||
- **c1:** cut 1 is within noise (−1.7 % fps, +0.06 ms). It engaged (the mid-frame submit is gone) but saves ≤0.04 ms; see MONOLITH-DIFF.md.
|
||||
- **c1b:** neutral in monolith on CPU and on GPU (busy 29 % in both). Barriers fell from 27.5 to 15.5 per frame, but passes only from 12.6 to 11.5.
|
||||
- **c1c2:** Magma per frame, from the counting build:
|
||||
- passes 11.5 -> 4.3 (dev 4.1);
|
||||
- barriers 15.5 -> 9.0 (dev 3.4);
|
||||
- Vulkan calls 343.5 (dev 331.6).
|
||||
|
||||
**Thread state** (c1, H2, ftrace window after the measurement; means of 3 reps; ms/frame):
|
||||
Effect:
|
||||
- Monolith GL-thread CPU −0.09 ms/frame (2.83 -> 2.74); fps within noise.
|
||||
- Inproc apply thread −0.17 ms/frame (1.41 -> 1.24) and fps +5.0 % (329.5 -> 345.7).
|
||||
- GPU busy: monolith 30 -> 27 % (≈1.18 -> 1.07 GPU ms/frame); inproc 35 -> 33 % (≈1.06 -> 0.96).
|
||||
- Profile: the `glClear` entry fell 0.286 -> 0.098 ms/frame. Part of that pass begin moved into draws (DrawArrays +0.07).
|
||||
|
||||
⁴ y has 2 valid reps: r2 never reached the world after 3 startup deaths (signal 34 during bootstrap, a harness flake also seen on MobileGlues and other builds).
|
||||
⁵ t inproc r2 is discarded: a host-side `binary_cache_builder` pulled libraries over adb inside its measurement window.
|
||||
- **c4:** Espryt GL-thread CPU fell 0.13 ms/frame (3.18 -> 3.05 in all three reps); fps moved by less than the noise.
|
||||
The profile confirms it: QueryCurrentSurfaceSize 0.070 -> 0.012 and SyncRenderState 0.092 -> 0.028 ms/frame.
|
||||
|
||||
**Thread state, Perfetto SQL** (c1, H2; `pftstate.py` over `trace_processor_shell` v58.2 on the
|
||||
`.pftrace` taken after the windows; means of 3 reps; ms/frame):
|
||||
|
||||
| arm | fps | GL run | GL woken-wait | GL preempted | GL sleep | apply run | apply sleep | both on-CPU / neither |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| MobileGlues monolith | 324.8 | 1.91 | 0.036 | 0.030 | 1.05 | - | - | - |
|
||||
| Magma monolith (t) | 254.1 | 3.09 | 0.042 | 0.027 | 0.71 | - | - | - |
|
||||
| Magma monolith (u) | 249.8 | 3.05 | 0.045 | 0.045 | 0.79 | - | - | - |
|
||||
| Magma inproc (u) | 328.3 | 1.43 | 0.047 | 0.073 | 1.45 | 1.61 | 1.31 | 41 % / 41 % |
|
||||
| Espryt inproc (u) | 323.8 | 1.39 | 0.057 | 0.088 | 1.51 | 1.95 | 1.02 | 42 % / 34 % |
|
||||
| c1c2: MobileGlues monolith | 321.3 | 1.95 | 0.039 | 0.019 | 1.05 | - | - | - |
|
||||
| c1c2: Magma monolith (t) | 255.3 | 3.11 | 0.039 | 0.038 | 0.66 | - | - | - |
|
||||
| c1c2: Magma monolith (y, 1c) | 252.3 | 3.07 | 0.047 | 0.029 | 0.75 | - | - | - |
|
||||
| c1c2: Magma inproc (t) | 329.5 | 1.38 | 0.050 | 0.073 | 1.48 | 1.58 | 1.32 | 40 % / 42 % |
|
||||
| c1c2: Magma inproc (y, 1c) | 345.7 | 1.39 | 0.043 | 0.079 | 1.33 | 1.43 | 1.31 | 39 % / 42 % |
|
||||
|
||||
The sleeps break down as:
|
||||
- Monolith (both stacks): the app's `waitForever` slice inside queueBuffer, woken by `kgsl-events`, i.e. the GPU fence of the previous frame.
|
||||
- Inproc GL thread: woken by `mgl-srv-apply`.
|
||||
- Inproc apply thread: `waitForever` (the GPU fence), as in monolith.
|
||||
|
||||
The table below is the same window from the ftrace text (`threadstate.py`). It carries GPU busy, because this Perfetto config does not record the kgsl events.
|
||||
|
||||
| arm | GL thread on-CPU | preempted | sleeping (main reason) | apply thread on-CPU | apply sleeping | both on-CPU / neither | GPU busy |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user