0.2.6-dev: DESIGN §31.6 for the impostor box without the setup pose (slots 1.26× the old area on the roster, the DPR-2 crowd numbers, the batch-buffer setting; the open item resolved), docs/design/client.md, CHANGELOG 0.2.6-dev (#542); golden: unchanged
This commit is contained in:
1 parent
c973c1a1ad
commit
4c2fe9431d
3 files changed
+43
-36
No files matched your search
+41
-34
@@ -107,7 +107,8 @@ Regression covers normal two-, three- and four-human browser flows, the final tu
|
||||
|
||||
### 31.6 Impostor bounds and slot clearing (#542)
|
||||
|
||||
`render/spine.js clipBounds / stepClipBounds`, `render/units.js impostorBox / _impBox / _impBypass`, `render/impostor.js`.
|
||||
`render/spine.js clipBounds / stepClipBounds / SpineActor.update`, `render/units.js impostorBox / _impBox / _impBypass`,
|
||||
`render/impostor.js`, `render/app.js`.
|
||||
|
||||
- **Seen** (GitHub #542 by @ImPQlc; quality 高, usually from round 7 on): 野鬃's flag cut, offset and leaving stray pixels
|
||||
while she idles; 浊心斯卡蒂's head cut in her S3 flight (Skill_3_Begin); 隐现's pistol muzzle cut, offset and leaving
|
||||
@@ -129,37 +130,43 @@ Regression covers normal two-, three- and four-human browser flows, the final tu
|
||||
a region by its attachment rectangle, a mesh by its vertices; slots drawn at alpha 0 skipped. It is queued when the model
|
||||
loads and sampled within 2.5 ms per frame (`stepClipBounds`, called by `render/app.js`), ≈ 9 ms per skeleton on the
|
||||
roster (53 ms at most), shared by every unit of that skeleton; a unit is drawn directly until its box is ready. (2) The
|
||||
box is the setup pose ∪ the idle clip (never smaller than the setup pose), then each other clip in the order of the area
|
||||
it adds while the box stays within 4 × that core (a looping or held clip) or 2 × (a one-off clip: the deploy clip, a
|
||||
skill's, stun's or move's begin and end, a form's change), padded by 3 % + 8 units. A clip that does not fit (`over`: 319
|
||||
of the roster's 681 deploy clips — the drop from above, up to 1,700 units tall — and the looping clips of 6 skeletons with
|
||||
screen-wide effects) is drawn directly while it plays; the unit keeps its slot (`_impBypass`). The box is computed once
|
||||
per unit: no bounds work per frame. (3) The atlas draws each slot's skeleton in a container masked by the slot rectangle,
|
||||
which PIXI draws as a scissor rect (no stencil, no render-pass break): nothing paints outside a slot, and the slot's
|
||||
eraser clears all that an earlier pose can have left in it.
|
||||
- **Cost**: slot sizes grow with the boxes. The 120-unit stress crowd at 1600 × 900 (DPR 1) uses 8.7 M px of slots instead
|
||||
of 4.4 M (2.7 instead of 1.2 atlas pages, still no private render target). On the roster the boxes add up to 1.86 × the
|
||||
old area (the median skeleton 1.39 ×; without the setup-pose floor it would be 1.23 ×). The atlas keeps its ≤ 3 + 2 pages;
|
||||
a unit that finds no slot keeps a private render target, as before. At DPR 2 a page holds a quarter of the CSS pixels:
|
||||
the same crowd at 1280 × 720 keeps 39 of its 120 units in the atlas instead of 119 (81 private render targets; 55
|
||||
instead of 60 fps in headless Chrome), at 1920 × 1080 39 instead of 58; at 844 × 390, DPR 3, all 120 stay in two pages.
|
||||
CPU: one batch flush per redrawn slot.
|
||||
`tools/perfbench.mjs` (844 × 390, DPR 2, quality medium / low, where those battles draw through the atlas; 12 runs of
|
||||
6 s): total busy JS +0.8 %, the impostor-redraw stage +14 % (+0.1–0.25 ms per frame at a 4 × CPU throttle), 60 fps in
|
||||
every run before and after.
|
||||
- Open: the setup-pose floor doubles the cost. A model shows its setup pose only when it is frozen before its first update
|
||||
(built while stunned without a stun clip, or dead without a death or idle clip). If such a model were posed at its
|
||||
clip's first frame instead, the setup pose could be dropped from the box: the stress crowd's slots would add up to
|
||||
1.16 × the old area (DPR 1), and at 1280 × 720, DPR 2, 116 of the 120 units would stay in the atlas (60 fps). Kept as
|
||||
specified for #542; the maintainer's call.
|
||||
- [ASSUMED]: the growth limits (4 × / 2 ×), the 3 % + 8 padding, the sampling rate and the 2.5 ms budget. A pose between two
|
||||
samples can still pass the padded box for a frame (33 of the 681 skeletons by a denser 60 Hz check, all in long clips
|
||||
box is the idle clip, then each other clip in the order of the area it adds while the box stays within 5 × the idle
|
||||
clip's area (a looping or held clip) or 2 × (a one-off clip: the deploy clip, a skill's, stun's or move's begin and end, a
|
||||
form's change), padded by 3 % + 8 units. A clip that does not fit (`over`: 355 of the roster's 681 deploy clips — the drop
|
||||
from above, up to 1,700 units tall — and the looping or held clips of 13 skeletons with screen-wide effects, among them
|
||||
帕拉斯's skills and 诗怀雅's attack) is drawn directly while it plays; the unit keeps its slot (`_impBypass`). The box is
|
||||
computed once per unit: no bounds work per frame. The setup pose needs no room: a model frozen before its first update —
|
||||
built while its unit is stunned and it has no stun clip (202 of the 210 operators have none), or dead without a death or
|
||||
idle clip — showed its setup pose, which no clip shows (隐现's Back model stands 900 units right of its feet in it);
|
||||
`SpineActor.update` now poses it once at its clip's first frame. (3) The atlas draws each slot's skeleton in a container
|
||||
masked by the slot rectangle, which PIXI draws as a scissor rect (no stencil, no render-pass break): nothing paints
|
||||
outside a slot, and the slot's eraser clears all that an earlier pose can have left in it. Each redrawn slot is a batch
|
||||
flush of its own, and PIXI uploaded its one batch vertex buffer again for every flush, which stalls Apple GPUs (Chrome,
|
||||
ANGLE Metal, Apple M4: the 120-unit crowd at 1920 × 1080 ran at 42–58 fps instead of 60, by the machine's load), so
|
||||
`render/app.js` turns PIXI's `BatchRenderer.canUploadSameBuffer` off: a buffer per flush, PIXI's own setting on iOS.
|
||||
Switched back and forth within one page it gave 60 fps against 42 (DPR 2) and 50 (DPR 1) with the shared buffer —
|
||||
turning it off only around the atlas pass gave 55 — for ≈ 0.3 ms more render CPU per frame; under load it also steadied
|
||||
the 0.2.5 renderer (51 → 59 fps at DPR 2).
|
||||
- **Cost**: slot sizes grow with the boxes. On the roster they add up to 1.26 × the old area (the median skeleton
|
||||
1.13 ×). The 120-unit stress crowd at 1600 × 900 (DPR 1) uses 5.5 M px of slots instead of 4.4 M (two of the three atlas
|
||||
pages, no private render target). At DPR 2 a page holds a quarter of the CSS pixels: the crowd at 1280 × 720 keeps 98–108
|
||||
of its units in the atlas instead of 118–120, at 1920 × 1080 59–61 instead of 58–59 (its private render targets 4.4 M px
|
||||
instead of 2.8 M); at 844 × 390, DPR 3, all 120 stay in two pages. 60 fps in every case, before and after (runs on a
|
||||
quiet machine). A unit that finds no slot keeps a private render target, as before; the atlas keeps its ≤ 3 + 2 pages.
|
||||
CPU: `tools/perfbench.mjs` (844 × 390, DPR 2, quality medium / low, where those battles draw through the atlas; four
|
||||
battles at a 4 × CPU throttle, two interleaved runs before and after): the view's JS 4.69 → 5.04 ms per frame, the
|
||||
impostor-redraw stage +21 % (+0.12 ms per frame), all busy JS +0.4 %, 60 fps in every run.
|
||||
- [ASSUMED]: the growth limits (5 × / 2 ×), the 3 % + 8 padding, the sampling rate and the 2.5 ms budget. A pose between two
|
||||
samples can still pass the padded box for a frame (42 of the 681 skeletons by a denser 60 Hz check, all in long clips
|
||||
sampled sparser than that); the scissor cuts it and leaves nothing behind.
|
||||
- Tests: `test/render/impostor-bounds.test.js` (synthetic Spine 3.8 skeletons: setup and clip bounds, an attachment shown
|
||||
for 0.05 s, an alpha-0 slot, the budgeted queue; `impostorBox` / `playedClips`; every atlas slot's clip box and eraser;
|
||||
UnitView: drawn directly until measured, the over-clip bypass keeping its slot, the legacy fallback, a pen figure),
|
||||
`test/render/impostor-bounds-models.test.js` (the three operators through the real runtime: every pose of every clip
|
||||
they play, at 30 Hz, inside their box; the old box cut 野鬃 by 103 units and 浊心斯卡蒂 by 188; skips without the
|
||||
assets), `test/render/impostor-bounds.browser.test.js` (`RENDER_E2E=1`: the held stress crowd — inside their slots, no
|
||||
atlas pixel outside a live slot; the scissor on a bare page; 野鬃's deploy drawn directly), `test/render/boardart.test.js`
|
||||
(the allocator's test doubles). Dev: `render-demo.html?scene=stress&ops=<chessId>,…`. Golden: unchanged.
|
||||
- Tests: `test/render/impostor-bounds.test.js` (synthetic Spine 3.8 skeletons: clip bounds, an attachment shown for
|
||||
0.05 s, an alpha-0 slot, the budgeted queue; a model frozen before its first update — stunned without a stun clip, dead
|
||||
without a death or idle clip — posed once at its clip's first frame; `impostorBox` / `playedClips`; every atlas slot's
|
||||
clip box and eraser; UnitView: drawn directly until measured, built while stunned, a skeleton without an Idle clip, the
|
||||
over-clip bypass keeping its slot, the legacy fallback, a pen figure), `test/render/impostor-bounds-models.test.js` (the
|
||||
three operators through the real runtime: every pose of every clip they play, at 30 Hz, inside their box; the old box
|
||||
cut 野鬃 by 103 units and 浊心斯卡蒂 by 188; 隐现's Back model built while stunned shows its Idle's first frame inside
|
||||
the box, its setup pose lies outside it; skips without the assets), `test/render/impostor-bounds.browser.test.js`
|
||||
(`RENDER_E2E=1`: the held stress crowd — inside their slots, no atlas pixel outside a live slot; the scissor on a bare
|
||||
page; 野鬃's deploy drawn directly), `test/render/boardart.test.js` (the allocator's test doubles). Dev:
|
||||
`render-demo.html?scene=stress&ops=<chessId>,…`. Golden: unchanged.
|
||||
Reference in new issue
Block a user