@moku-labs/game 0.14.13 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/page.d.mts +1 -1
- package/dist/app/page.mjs +2 -2
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +3 -3
- package/dist/app.mjs +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/cli.mjs +75 -36
- package/dist/{commands-DAETmK86.d.mts → commands-BynjWVVy.d.mts} +7 -6
- package/dist/control.d.mts +30 -18
- package/dist/control.mjs +2 -2
- package/dist/{define-BASd9ZUh.d.mts → define-Ckj_TMWu.d.mts} +2 -2
- package/dist/{dev-DbHgx-R-.mjs → dev-BNndV-hU.mjs} +4 -4
- package/dist/{headless-cFveciuY.d.mts → headless-B0mH9qYg.d.mts} +1 -1
- package/dist/{index-DedbED3T.d.mts → index-CCiTo1_3.d.mts} +16 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +2 -2
- package/dist/inspect.d.mts +6 -6
- package/dist/inspect.mjs +1 -1
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/project.d.mts +4 -4
- package/dist/project.mjs +1 -1
- package/dist/{run-BbipFRc6.mjs → run-C07NXds7.mjs} +35 -25
- package/dist/{runtime-DoSlRx8X.d.mts → runtime-BV3xv6gx.d.mts} +1 -1
- package/dist/{sources-Cdvb0rOS.mjs → sources-OvHvyeJS.mjs} +1 -1
- package/dist/{src-BEbJ_sSx.mjs → src-CwVzErzN.mjs} +1921 -652
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-GHE4J5Uf.d.mts → types-Bw-qLjFZ.d.mts} +2 -2
- package/dist/{types-C73hbmxi.d.mts → types-CpdkyhaC.d.mts} +4 -4
- package/dist/{types-DngFQJro.d.mts → types-D44EL4gT.d.mts} +2 -2
- package/dist/{types-BGcda5Le.d.mts → types-E2ykwIUO.d.mts} +556 -42
- package/dist/{types-7GXcXxxv.d.mts → types-G3ZAAsbd.d.mts} +20 -1
- package/dist/{visual-_XhoGeIC.d.mts → visual-DoHwXeQi.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +2 -2
- package/dist/{watch-BEdrp80i.mjs → watch-BSgptowj.mjs} +16 -6
- package/llms.txt +6 -4
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
|
3
3
|
import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze$1, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
|
|
4
4
|
import { d as graphHash, f as isRestCheckpoint, h as pushEntry, i as servePaused, l as findNode, m as compact, n as captureFrame, o as collectFlows, p as slotNames, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as framePath } from "./capture-D7XvoHdH.mjs";
|
|
5
5
|
import { A as Order, C as Sprite, D as Exiting, F as system, I as systemsFor, L as tag, M as component, O as Layer, P as resource, S as Shape, T as componentsFor, _ as HudPart, b as OverlayLevel, c as Countdown, d as builtInStyles, g as Display, h as textFor, i as samePose, j as Tree, k as Now, l as Text, m as readStyle, n as drawnRect, o as visualOf$1, r as fitScaleOf, s as visualRectOf, t as CONTENT$1, v as NineSlice, w as Transform, x as Parent, y as Overlay } from "./visual-2m4wJL_z.mjs";
|
|
6
|
-
import { a as isDev } from "./dev-
|
|
6
|
+
import { a as isDev } from "./dev-BNndV-hU.mjs";
|
|
7
7
|
import { i as isComponentDefinition, n as flatten, r as defineComponent } from "./flatten-BkXuscAV.mjs";
|
|
8
8
|
import { envPlugin, logPlugin } from "@moku-labs/common/browser";
|
|
9
9
|
import { createCoreConfig } from "@moku-labs/core";
|
|
@@ -124,7 +124,9 @@ function createClockApi(ctx) {
|
|
|
124
124
|
poke: () => {
|
|
125
125
|
deliverElapsed(ctx.state);
|
|
126
126
|
},
|
|
127
|
-
dueAt: () => ctx.state.dueAt
|
|
127
|
+
dueAt: () => ctx.state.dueAt,
|
|
128
|
+
pending: () => ctx.state.dueAt === void 0 ? 0 : 1,
|
|
129
|
+
listeners: () => ctx.state.listeners.length
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
//#endregion
|
|
@@ -684,6 +686,25 @@ function capSlackMs(displayHz, frameMs) {
|
|
|
684
686
|
return whole >= 1 && Math.abs(periods - whole) <= .05 ? 500 / displayHz : 1;
|
|
685
687
|
}
|
|
686
688
|
/**
|
|
689
|
+
* The rate the loop really runs at under a cap: the cap skips every tick that comes more than `capSlackMs` early, so
|
|
690
|
+
* a frame runs on the first vsync at or after one frame of the cap minus the slack, never faster than the display. The
|
|
691
|
+
* cap itself while the display is unknown. Called when the snapshot is rebuilt, never per frame.
|
|
692
|
+
*
|
|
693
|
+
* @param fps - The cap: the loop's rate, the idle cap left out.
|
|
694
|
+
* @param displayHz - The display's refresh rate, or `null` while unknown.
|
|
695
|
+
* @returns Frames per second the loop runs at.
|
|
696
|
+
* @example
|
|
697
|
+
* ```ts
|
|
698
|
+
* loopRateOf(60, 144); // 48: every third tick of a 144 Hz panel
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
701
|
+
function loopRateOf(fps, displayHz) {
|
|
702
|
+
if (displayHz === null) return fps;
|
|
703
|
+
const frameMs = 1e3 / fps;
|
|
704
|
+
const ticks = Math.ceil((frameMs - capSlackMs(displayHz, frameMs)) * displayHz / 1e3);
|
|
705
|
+
return displayHz / Math.max(1, ticks);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
687
708
|
* Normalizes a thermal status of the shell: a status name, or Android's whole number 0–6 mapped in order.
|
|
688
709
|
*
|
|
689
710
|
* @param value - The raw status.
|
|
@@ -856,20 +877,36 @@ function createGovernor(floor) {
|
|
|
856
877
|
};
|
|
857
878
|
}
|
|
858
879
|
/**
|
|
859
|
-
* The
|
|
880
|
+
* The rate the loop is told to hold: the cap in mode `"fixed"` (`maxFps`) and `"forced"` (the `setRate` value, never
|
|
881
|
+
* above `maxFps`); `null` in mode `"auto"`, where the adaptive rule decides. Outside `"auto"` the cap is always one of
|
|
882
|
+
* the fixed choices, so the guard only narrows its type.
|
|
883
|
+
*
|
|
884
|
+
* @param rate - The rate and its mode.
|
|
885
|
+
* @returns The asked rate, or `null`.
|
|
886
|
+
*/
|
|
887
|
+
function askedOf(rate) {
|
|
888
|
+
return rate.mode !== "auto" && isChoice(rate.fps) ? rate.fps : null;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* The frozen object `rate()` answers, built from the rate now: `fps` is what the loop really runs at under the cap on
|
|
892
|
+
* the known display, and `heldBy` is `"panel"` when the asked rate is above the display's.
|
|
860
893
|
*
|
|
861
894
|
* @param rate - The rate, its mode, its reason, the display and the governor.
|
|
862
895
|
* @returns A fresh frozen snapshot.
|
|
863
896
|
*/
|
|
864
897
|
function snapshotOf(rate) {
|
|
865
898
|
const { governor } = rate;
|
|
899
|
+
const { hz } = rate.display;
|
|
900
|
+
const asked = askedOf(rate);
|
|
866
901
|
return Object.freeze({
|
|
867
|
-
fps: rate.fps,
|
|
902
|
+
fps: loopRateOf(rate.fps, hz),
|
|
868
903
|
mode: rate.mode,
|
|
869
904
|
reason: rate.reason,
|
|
870
|
-
displayHz:
|
|
905
|
+
displayHz: hz,
|
|
871
906
|
ladder: governor?.ladder ?? NO_STEPS,
|
|
872
|
-
thermal: governor?.thermal ?? null
|
|
907
|
+
thermal: governor?.thermal ?? null,
|
|
908
|
+
asked,
|
|
909
|
+
heldBy: asked !== null && hz !== null && asked > hz ? "panel" : null
|
|
873
910
|
});
|
|
874
911
|
}
|
|
875
912
|
/**
|
|
@@ -1597,7 +1634,7 @@ function noteCap(ctx, frameMs) {
|
|
|
1597
1634
|
* skipped, is a sample of the display rate. The first frame, and the first frame after a
|
|
1598
1635
|
* `resume`, counts as one capped frame instead of the gap to a stale timestamp. A frame that
|
|
1599
1636
|
* arrives more than the cap's slack before the current cap allows it is skipped — the cap is
|
|
1600
|
-
* `idleFps` while the screen is idle and the loop's rate (`rate().
|
|
1637
|
+
* `idleFps` while the screen is idle and the loop's rate otherwise (`rate().asked`, or the adaptive rule's), the slack half
|
|
1601
1638
|
* a display period (`capSlackMs`) — and a long gap is clamped at `maxDeltaMs` so a backgrounded
|
|
1602
1639
|
* tab does not produce a giant step. The idle timer moves by the raw gap, at most `idleAfterMs`,
|
|
1603
1640
|
* so a throttled page goes idle in `idleAfterMs`. The interval that ends at a run frame is judged
|
|
@@ -1645,6 +1682,17 @@ function addFrameCallback(ctx, phase, callback) {
|
|
|
1645
1682
|
};
|
|
1646
1683
|
}
|
|
1647
1684
|
/**
|
|
1685
|
+
* Counts the registered frame callbacks of the six phases, named one by one as `capturePhases` does: no loop, no
|
|
1686
|
+
* iterator, no allocation.
|
|
1687
|
+
*
|
|
1688
|
+
* @param ctx - Domain context of the time plugin.
|
|
1689
|
+
* @returns The count.
|
|
1690
|
+
*/
|
|
1691
|
+
function countFrameListeners(ctx) {
|
|
1692
|
+
const { input, animate, layout, sync, signals, render } = ctx.state.callbacks;
|
|
1693
|
+
return input.length + animate.length + layout.length + sync.length + signals.length + render.length;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1648
1696
|
* Creates the time API: frame callbacks per phase, the `Time` resource, scale, pause and
|
|
1649
1697
|
* resume, `wake` for the idle cap, the measurement of an agent build, the frame rate, and `step`
|
|
1650
1698
|
* for tests and tools.
|
|
@@ -1679,6 +1727,7 @@ function createTimeApi(ctx) {
|
|
|
1679
1727
|
isRunning: () => ctx.state.running,
|
|
1680
1728
|
wake: () => wakeClock(ctx),
|
|
1681
1729
|
wakes: () => ctx.state.wakes,
|
|
1730
|
+
frameListeners: () => countFrameListeners(ctx),
|
|
1682
1731
|
measure: (listener) => {
|
|
1683
1732
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
1684
1733
|
const { measure } = ctx.state;
|
|
@@ -8045,8 +8094,9 @@ const WORLD_PHASES = [
|
|
|
8045
8094
|
];
|
|
8046
8095
|
/**
|
|
8047
8096
|
* Gives a timed system its row: the row of an earlier system of the same name, else the next one.
|
|
8048
|
-
* `
|
|
8049
|
-
*
|
|
8097
|
+
* A new row replaces `names` with a new array, so a reader that kept the old one sees the growth
|
|
8098
|
+
* by identity, and the old array stays as it was. `ms` grows (a new array, the old values copied)
|
|
8099
|
+
* only when the row is past its end. Both happen at registration, never per frame.
|
|
8050
8100
|
*
|
|
8051
8101
|
* @param state - The ecs branch of the plugin state.
|
|
8052
8102
|
* @param timing - The timing table.
|
|
@@ -8055,7 +8105,7 @@ const WORLD_PHASES = [
|
|
|
8055
8105
|
function giveRow(state, timing, name) {
|
|
8056
8106
|
if (state.timingSlots.has(name)) return;
|
|
8057
8107
|
const row = timing.names.length;
|
|
8058
|
-
timing.names.
|
|
8108
|
+
timing.names = [...timing.names, name];
|
|
8059
8109
|
state.timingSlots.set(name, row);
|
|
8060
8110
|
if (row < timing.ms.length) return;
|
|
8061
8111
|
const grown = new Float64Array(timing.ms.length * 2);
|
|
@@ -8341,6 +8391,7 @@ function createEcsApi(ctx) {
|
|
|
8341
8391
|
diff: (from, to) => diffHistory(state.history, from, to),
|
|
8342
8392
|
schema: () => schemaOf(state.types.values()),
|
|
8343
8393
|
ownerOf: (entity) => state.owners.get(entity),
|
|
8394
|
+
count: () => state.owners.size,
|
|
8344
8395
|
onOwnerLeft: (fn) => {
|
|
8345
8396
|
state.ownerLeft.push(fn);
|
|
8346
8397
|
return () => {
|
|
@@ -10612,6 +10663,7 @@ function exposeEcs(ecs, projection) {
|
|
|
10612
10663
|
changed: ecs.changed,
|
|
10613
10664
|
typeOf: ecs.typeOf,
|
|
10614
10665
|
ownerOf: ecs.ownerOf,
|
|
10666
|
+
count: ecs.count,
|
|
10615
10667
|
mode: ecs.mode,
|
|
10616
10668
|
setMode: (mode) => {
|
|
10617
10669
|
ecs.setMode(mode);
|
|
@@ -11298,18 +11350,27 @@ function someOf(entities, test) {
|
|
|
11298
11350
|
return false;
|
|
11299
11351
|
}
|
|
11300
11352
|
/**
|
|
11353
|
+
* Empties a set when it holds something. V8 gives an empty set a new table at every `clear()`, so clearing the empty
|
|
11354
|
+
* sets of a still screen every frame allocated about 18 MB in 20 000 frames.
|
|
11355
|
+
*
|
|
11356
|
+
* @param set - The set.
|
|
11357
|
+
*/
|
|
11358
|
+
function emptyOut(set) {
|
|
11359
|
+
if (set.size > 0) set.clear();
|
|
11360
|
+
}
|
|
11361
|
+
/**
|
|
11301
11362
|
* Empties the marks of a decision.
|
|
11302
11363
|
*
|
|
11303
11364
|
* @param state - The demand state.
|
|
11304
11365
|
*/
|
|
11305
11366
|
function clearMarks(state) {
|
|
11306
|
-
state.marked
|
|
11307
|
-
state.pending
|
|
11308
|
-
state.loose
|
|
11309
|
-
state.visual
|
|
11310
|
-
state.moved
|
|
11311
|
-
state.published
|
|
11312
|
-
state.wholes
|
|
11367
|
+
emptyOut(state.marked);
|
|
11368
|
+
emptyOut(state.pending);
|
|
11369
|
+
emptyOut(state.loose);
|
|
11370
|
+
emptyOut(state.visual);
|
|
11371
|
+
emptyOut(state.moved);
|
|
11372
|
+
emptyOut(state.published);
|
|
11373
|
+
emptyOut(state.wholes);
|
|
11313
11374
|
state.counted = false;
|
|
11314
11375
|
}
|
|
11315
11376
|
/**
|
|
@@ -11426,7 +11487,7 @@ function createDemandApi(ctx, deps) {
|
|
|
11426
11487
|
if (drawn) {
|
|
11427
11488
|
state.drawnOnce = true;
|
|
11428
11489
|
state.requests = 0;
|
|
11429
|
-
state.sinceDraw
|
|
11490
|
+
emptyOut(state.sinceDraw);
|
|
11430
11491
|
}
|
|
11431
11492
|
return drawn;
|
|
11432
11493
|
};
|
|
@@ -11443,7 +11504,7 @@ function createDemandApi(ctx, deps) {
|
|
|
11443
11504
|
}
|
|
11444
11505
|
if ((typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) && state.verify.on) {
|
|
11445
11506
|
verifyFrame(ctx, state, host, state.frame);
|
|
11446
|
-
if (state.frame.reason === "verify") state.sinceDraw
|
|
11507
|
+
if (state.frame.reason === "verify") emptyOut(state.sinceDraw);
|
|
11447
11508
|
}
|
|
11448
11509
|
};
|
|
11449
11510
|
/**
|
|
@@ -12363,7 +12424,12 @@ function stopHost(state) {
|
|
|
12363
12424
|
//#endregion
|
|
12364
12425
|
//#region src/plugins/renderer/monitor/units.ts
|
|
12365
12426
|
/**
|
|
12366
|
-
* @file renderer/monitor — the byte and rounding helpers the counters and the performance log share (LEAD-FIXES-3
|
|
12427
|
+
* @file renderer/monitor — the byte and rounding helpers the counters and the performance log share (LEAD-FIXES-3
|
|
12428
|
+
* O24), and the column readers: one per typed-array kind, never one shared. V8 keeps one load feedback per function,
|
|
12429
|
+
* inlined copies included: a reader that saw more than four array kinds goes generic, and every double it reads on the
|
|
12430
|
+
* per-frame path comes back boxed, a heap number a read. A reader that sees one kind stays monomorphic and reads the
|
|
12431
|
+
* double unboxed. The parameter type holds the rule: `cellF32` takes a `Float32Array` and nothing else. One read out of
|
|
12432
|
+
* bounds (slot -1) boxes a reader's doubles the same way, for every caller: a slot that may not exist is checked first.
|
|
12367
12433
|
*/
|
|
12368
12434
|
/** Bytes of one MiB. */
|
|
12369
12435
|
const BYTES_PER_MB$1 = 1048576;
|
|
@@ -12383,18 +12449,87 @@ function roundTo(value, decimals) {
|
|
|
12383
12449
|
return Math.round(value * factor) / factor;
|
|
12384
12450
|
}
|
|
12385
12451
|
/**
|
|
12386
|
-
* Reads one value of a column; a slot past the end reads NaN, "not measured".
|
|
12387
|
-
*
|
|
12452
|
+
* Reads one value of a `Float64Array` column; a slot past the end reads NaN, "not measured".
|
|
12453
|
+
*
|
|
12454
|
+
* @param column - The column.
|
|
12455
|
+
* @param index - The slot.
|
|
12456
|
+
* @returns The value.
|
|
12457
|
+
* @example
|
|
12458
|
+
* ```ts
|
|
12459
|
+
* cellF64(Float64Array.of(2.5), 0); // 2.5
|
|
12460
|
+
* ```
|
|
12461
|
+
*/
|
|
12462
|
+
function cellF64(column, index) {
|
|
12463
|
+
return column[index] ?? NaN;
|
|
12464
|
+
}
|
|
12465
|
+
/**
|
|
12466
|
+
* Reads one value of a `Float32Array` column; a slot past the end reads NaN, "not measured".
|
|
12467
|
+
*
|
|
12468
|
+
* @param column - The column.
|
|
12469
|
+
* @param index - The slot.
|
|
12470
|
+
* @returns The value.
|
|
12471
|
+
* @example
|
|
12472
|
+
* ```ts
|
|
12473
|
+
* cellF32(Float32Array.of(2.5), 1); // NaN
|
|
12474
|
+
* ```
|
|
12475
|
+
*/
|
|
12476
|
+
function cellF32(column, index) {
|
|
12477
|
+
return column[index] ?? NaN;
|
|
12478
|
+
}
|
|
12479
|
+
/**
|
|
12480
|
+
* Reads one value of a `Uint32Array` column; a slot past the end reads NaN.
|
|
12481
|
+
*
|
|
12482
|
+
* @param column - The column.
|
|
12483
|
+
* @param index - The slot.
|
|
12484
|
+
* @returns The value.
|
|
12485
|
+
* @example
|
|
12486
|
+
* ```ts
|
|
12487
|
+
* cellU32(Uint32Array.of(4812), 0); // 4812
|
|
12488
|
+
* ```
|
|
12489
|
+
*/
|
|
12490
|
+
function cellU32(column, index) {
|
|
12491
|
+
return column[index] ?? NaN;
|
|
12492
|
+
}
|
|
12493
|
+
/**
|
|
12494
|
+
* Reads one value of an `Int32Array` column; a slot past the end reads NaN.
|
|
12495
|
+
*
|
|
12496
|
+
* @param column - The column.
|
|
12497
|
+
* @param index - The slot.
|
|
12498
|
+
* @returns The value.
|
|
12499
|
+
* @example
|
|
12500
|
+
* ```ts
|
|
12501
|
+
* cellI32(Int32Array.of(-1), 0); // -1
|
|
12502
|
+
* ```
|
|
12503
|
+
*/
|
|
12504
|
+
function cellI32(column, index) {
|
|
12505
|
+
return column[index] ?? NaN;
|
|
12506
|
+
}
|
|
12507
|
+
/**
|
|
12508
|
+
* Reads one value of a `Uint16Array` column; a slot past the end reads NaN.
|
|
12388
12509
|
*
|
|
12389
|
-
* @param column -
|
|
12510
|
+
* @param column - The column.
|
|
12390
12511
|
* @param index - The slot.
|
|
12391
12512
|
* @returns The value.
|
|
12392
12513
|
* @example
|
|
12393
12514
|
* ```ts
|
|
12394
|
-
*
|
|
12515
|
+
* cellU16(Uint16Array.of(0xff_ff), 0); // 65535
|
|
12395
12516
|
* ```
|
|
12396
12517
|
*/
|
|
12397
|
-
function
|
|
12518
|
+
function cellU16(column, index) {
|
|
12519
|
+
return column[index] ?? NaN;
|
|
12520
|
+
}
|
|
12521
|
+
/**
|
|
12522
|
+
* Reads one value of a `Uint8Array` column; a slot past the end reads NaN.
|
|
12523
|
+
*
|
|
12524
|
+
* @param column - The column.
|
|
12525
|
+
* @param index - The slot.
|
|
12526
|
+
* @returns The value.
|
|
12527
|
+
* @example
|
|
12528
|
+
* ```ts
|
|
12529
|
+
* cellU8(Uint8Array.of(1), 0); // 1
|
|
12530
|
+
* ```
|
|
12531
|
+
*/
|
|
12532
|
+
function cellU8(column, index) {
|
|
12398
12533
|
return column[index] ?? NaN;
|
|
12399
12534
|
}
|
|
12400
12535
|
//#endregion
|
|
@@ -12438,7 +12573,7 @@ function sourceBytes(source) {
|
|
|
12438
12573
|
* isTarget({ uploadMethodId: "unknown", resource: null }); // true
|
|
12439
12574
|
* ```
|
|
12440
12575
|
*/
|
|
12441
|
-
function isTarget(source) {
|
|
12576
|
+
function isTarget$1(source) {
|
|
12442
12577
|
return source.uploadMethodId === "unknown" && (source.resource === null || source.resource === void 0);
|
|
12443
12578
|
}
|
|
12444
12579
|
/**
|
|
@@ -12473,7 +12608,7 @@ function textureUsage(app, kept) {
|
|
|
12473
12608
|
usage.count += 1;
|
|
12474
12609
|
usage.bytes += bytes;
|
|
12475
12610
|
if (kept.has(source)) usage.kept += bytes;
|
|
12476
|
-
else if (isTarget(source)) usage.targets += bytes;
|
|
12611
|
+
else if (isTarget$1(source)) usage.targets += bytes;
|
|
12477
12612
|
else usage.art += bytes;
|
|
12478
12613
|
}
|
|
12479
12614
|
return usage;
|
|
@@ -14141,6 +14276,7 @@ function createRecord$1(kctx, pixi, entity, view, slots, category) {
|
|
|
14141
14276
|
still: 0,
|
|
14142
14277
|
churn: 0,
|
|
14143
14278
|
calm: false,
|
|
14279
|
+
quiet: 0,
|
|
14144
14280
|
lastDrawn: kctx.state.frame,
|
|
14145
14281
|
depth: 0,
|
|
14146
14282
|
bytes: 0,
|
|
@@ -14708,6 +14844,7 @@ function stepRecord(kctx, pixi, matrices, record, view) {
|
|
|
14708
14844
|
if (eligible(kctx, record, view, NOW)) stepEligible(kctx, pixi, matrices, record, view);
|
|
14709
14845
|
else stepIneligible(kctx, record, view);
|
|
14710
14846
|
if (drawingChanged(kctx, record, BEFORE)) dirtyAbove(kctx, record);
|
|
14847
|
+
record.quiet = record.dirtyS1 ? 0 : record.quiet + 1;
|
|
14711
14848
|
record.dirtyS1 = false;
|
|
14712
14849
|
record.dirtyS2 = false;
|
|
14713
14850
|
record.dirtyS3 = false;
|
|
@@ -14872,21 +15009,21 @@ function shownRecords(kctx) {
|
|
|
14872
15009
|
return kctx.state.records.filter((record) => record.mode === "kept" && record.quad?.visible === true);
|
|
14873
15010
|
}
|
|
14874
15011
|
/**
|
|
14875
|
-
*
|
|
14876
|
-
*
|
|
15012
|
+
* Runs a draw with every picture drawn live: the kept records bypassed, the entries of every range with a sprite lifted
|
|
15013
|
+
* and its sprite hidden. The records draw their pictures again after it, the ranges show their sprites again.
|
|
14877
15014
|
*
|
|
14878
15015
|
* @param kctx - The kept context.
|
|
14879
|
-
* @param ranges - The ranges whose sprite stood in the frame.
|
|
14880
15016
|
* @param bypass - Draws every kept record live (true) or as its picture again (false).
|
|
14881
|
-
* @param
|
|
14882
|
-
* @returns
|
|
15017
|
+
* @param draw - The render or the read.
|
|
15018
|
+
* @returns What `draw` returned.
|
|
14883
15019
|
*/
|
|
14884
|
-
function
|
|
15020
|
+
function drawLive(kctx, bypass, draw) {
|
|
15021
|
+
const ranges = [...kctx.state.ranges.values()].filter((range) => range.sprite !== void 0);
|
|
14885
15022
|
const lift = ranges.flatMap((range) => range.entries);
|
|
14886
15023
|
const hide = ranges.flatMap((range) => range.sprite === void 0 ? [] : [range.sprite]);
|
|
14887
15024
|
bypass(true);
|
|
14888
15025
|
try {
|
|
14889
|
-
return kctx.sync.liftFor(kctx.hidden, lift, hide, "freeze",
|
|
15026
|
+
return kctx.sync.liftFor(kctx.hidden, lift, hide, "freeze", draw);
|
|
14890
15027
|
} finally {
|
|
14891
15028
|
bypass(false);
|
|
14892
15029
|
}
|
|
@@ -14926,7 +15063,7 @@ function verifyPictures(kctx, bypass, recover) {
|
|
|
14926
15063
|
const resolution = host.resolution();
|
|
14927
15064
|
const drawn = host.pixels(resolution);
|
|
14928
15065
|
if (drawn === void 0) return;
|
|
14929
|
-
const live =
|
|
15066
|
+
const live = drawLive(kctx, bypass, () => host.pixels(resolution));
|
|
14930
15067
|
if (live === void 0 || live.length !== drawn.length) return;
|
|
14931
15068
|
const screen = host.screen();
|
|
14932
15069
|
const width = screen === void 0 ? 0 : Math.round(screen.width * resolution);
|
|
@@ -15150,7 +15287,7 @@ function createKeptApi(ctx, deps) {
|
|
|
15150
15287
|
flushRanges(kctx);
|
|
15151
15288
|
},
|
|
15152
15289
|
after: () => {
|
|
15153
|
-
if ((typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) && deps.demand.verifying()) verifyPictures(kctx, bypass, () => {
|
|
15290
|
+
if ((typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) && deps.demand.verifying() && !ctx.state.sync.debug.overdraw) verifyPictures(kctx, bypass, () => {
|
|
15154
15291
|
for (const record of state.records) if (record.mode === "kept") live(record);
|
|
15155
15292
|
});
|
|
15156
15293
|
},
|
|
@@ -15182,6 +15319,10 @@ function createKeptApi(ctx, deps) {
|
|
|
15182
15319
|
return refreshes;
|
|
15183
15320
|
},
|
|
15184
15321
|
bypass,
|
|
15322
|
+
live: (draw) => {
|
|
15323
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) return drawLive(kctx, bypass, draw);
|
|
15324
|
+
return draw();
|
|
15325
|
+
},
|
|
15185
15326
|
enabled: (on) => {
|
|
15186
15327
|
const next = on && state.allowed;
|
|
15187
15328
|
if (next === state.on) return;
|
|
@@ -15541,9 +15682,9 @@ function overlapsLongFrame(ring, start, end) {
|
|
|
15541
15682
|
*/
|
|
15542
15683
|
function annotateLongFrame(perf, slot) {
|
|
15543
15684
|
const c = perf.ring.columns;
|
|
15544
|
-
const t =
|
|
15545
|
-
const gap =
|
|
15546
|
-
return overlapsLongFrame(perf.longFrames, Number.isNaN(gap) ? t : t - gap, t +
|
|
15685
|
+
const t = cellF64(c.t, slot);
|
|
15686
|
+
const gap = cellF32(c.gapMs, slot);
|
|
15687
|
+
return overlapsLongFrame(perf.longFrames, Number.isNaN(gap) ? t : t - gap, t + cellF32(c.workMs, slot));
|
|
15547
15688
|
}
|
|
15548
15689
|
/**
|
|
15549
15690
|
* The heap the page uses, in bytes: Chromium's `performance.memory.usedJSHeapSize`, read through a type guard.
|
|
@@ -15563,6 +15704,348 @@ function heapMb() {
|
|
|
15563
15704
|
return Number.isNaN(bytes) ? null : roundTo(bytes / BYTES_PER_MB$1, 2);
|
|
15564
15705
|
}
|
|
15565
15706
|
//#endregion
|
|
15707
|
+
//#region src/plugins/renderer/monitor/types.ts
|
|
15708
|
+
/**
|
|
15709
|
+
* Indices of the per-frame counters in `MonitorSinks.counts`. Not public.
|
|
15710
|
+
*/
|
|
15711
|
+
const COUNT = {
|
|
15712
|
+
passes: 0,
|
|
15713
|
+
stencilPasses: 1,
|
|
15714
|
+
filterApplies: 2,
|
|
15715
|
+
filterPixels: 3,
|
|
15716
|
+
uploads: 4,
|
|
15717
|
+
uploadBytes: 5,
|
|
15718
|
+
compiles: 6,
|
|
15719
|
+
marks: 7,
|
|
15720
|
+
rebuilds: 8,
|
|
15721
|
+
targetsCreated: 9,
|
|
15722
|
+
targetsDestroyed: 10
|
|
15723
|
+
};
|
|
15724
|
+
/**
|
|
15725
|
+
* What this application measures, as bits of `PerfState.measuring`. Not public.
|
|
15726
|
+
*/
|
|
15727
|
+
const MEASURE = {
|
|
15728
|
+
passes: 1,
|
|
15729
|
+
stencilPasses: 2,
|
|
15730
|
+
draws: 4,
|
|
15731
|
+
counted: 8,
|
|
15732
|
+
compiles: 16,
|
|
15733
|
+
gpu: 32,
|
|
15734
|
+
targets: 64
|
|
15735
|
+
};
|
|
15736
|
+
//#endregion
|
|
15737
|
+
//#region src/plugins/renderer/monitor/problems.ts
|
|
15738
|
+
/** Pixels per megapixel. */
|
|
15739
|
+
const PIXELS_PER_MPX$1 = 1e6;
|
|
15740
|
+
/** The window of the `retakes` rule, in seconds: two flushes. */
|
|
15741
|
+
const RETAKES_SECONDS = 2;
|
|
15742
|
+
/**
|
|
15743
|
+
* Adds one frame to the accumulators of the rules: for a drawn frame, each owner's run of drawn frames its filter
|
|
15744
|
+
* applied in (a drawn frame without an apply ends the run), its applies and pixels of the second; for every frame, the
|
|
15745
|
+
* render targets it made and dropped, a create in an idle frame and a create soon after a pool trim. Indexed stores
|
|
15746
|
+
* into the typed arrays of the perf state.
|
|
15747
|
+
*
|
|
15748
|
+
* @param pc - The perf context.
|
|
15749
|
+
* @param measure - What `time` measured of the frame.
|
|
15750
|
+
*/
|
|
15751
|
+
function accumulateProblems(pc, measure) {
|
|
15752
|
+
if (pc.perf.drawn) accumulateOwners(pc, measure);
|
|
15753
|
+
accumulateTargets(pc, measure);
|
|
15754
|
+
}
|
|
15755
|
+
/**
|
|
15756
|
+
* Adds a drawn frame to each owner's run: a frame its filter applied in grows the run and the second's applies and
|
|
15757
|
+
* pixels, a frame without an apply ends the run.
|
|
15758
|
+
*
|
|
15759
|
+
* @param pc - The perf context.
|
|
15760
|
+
* @param measure - What `time` measured of the frame.
|
|
15761
|
+
*/
|
|
15762
|
+
function accumulateOwners(pc, measure) {
|
|
15763
|
+
const { problems } = pc.perf;
|
|
15764
|
+
const { owners } = pc.sinks;
|
|
15765
|
+
for (let slot = 0; slot < 32; slot += 1) {
|
|
15766
|
+
if (cellF64(owners.ids, slot) === 0) continue;
|
|
15767
|
+
if (cellF64(owners.applies, slot) === 0) {
|
|
15768
|
+
problems.run[slot] = 0;
|
|
15769
|
+
continue;
|
|
15770
|
+
}
|
|
15771
|
+
if (cellF64(problems.run, slot) === 0) problems.runSince[slot] = measure.frame;
|
|
15772
|
+
problems.run[slot] = cellF64(problems.run, slot) + 1;
|
|
15773
|
+
problems.secFrames[slot] = cellF64(problems.secFrames, slot) + 1;
|
|
15774
|
+
problems.secPixels[slot] = cellF64(problems.secPixels, slot) + cellF64(owners.pixels, slot);
|
|
15775
|
+
}
|
|
15776
|
+
}
|
|
15777
|
+
/**
|
|
15778
|
+
* Adds a frame's render targets to the second: the made and dropped ones, a create in an idle frame, a create within
|
|
15779
|
+
* `churnAfterTrimMs` after a pool trim.
|
|
15780
|
+
*
|
|
15781
|
+
* @param pc - The perf context.
|
|
15782
|
+
* @param measure - What `time` measured of the frame.
|
|
15783
|
+
*/
|
|
15784
|
+
function accumulateTargets(pc, measure) {
|
|
15785
|
+
const { problems } = pc.perf;
|
|
15786
|
+
const { counts } = pc.sinks;
|
|
15787
|
+
const created = cellF64(counts, COUNT.targetsCreated);
|
|
15788
|
+
problems.secTargets += created + cellF64(counts, COUNT.targetsDestroyed);
|
|
15789
|
+
if (created === 0) return;
|
|
15790
|
+
if (measure.idle && !problems.idleCreate) {
|
|
15791
|
+
problems.idleCreate = true;
|
|
15792
|
+
problems.createFrame = measure.frame;
|
|
15793
|
+
}
|
|
15794
|
+
if (!problems.trimCreate && measure.at > problems.trimAt && measure.at - problems.trimAt <= problems.thresholds.churnAfterTrimMs) {
|
|
15795
|
+
problems.trimCreate = true;
|
|
15796
|
+
problems.createFrame = measure.frame;
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
/**
|
|
15800
|
+
* The label of an entity's view now.
|
|
15801
|
+
*
|
|
15802
|
+
* @param pc - The perf context.
|
|
15803
|
+
* @param entity - The entity.
|
|
15804
|
+
* @returns The label of its view's display object, or `null` once the view left.
|
|
15805
|
+
*/
|
|
15806
|
+
function viewLabel(pc, entity) {
|
|
15807
|
+
return pc.ctx.state.sync.views.get(entity)?.object.label ?? null;
|
|
15808
|
+
}
|
|
15809
|
+
/**
|
|
15810
|
+
* Tells whether a filtered view stands still unkept: its record is live while kept pictures are on, its subtree did
|
|
15811
|
+
* not change and its filter applied in the last `stillFrames` drawn frames, over `stillMpx` megapixels a frame.
|
|
15812
|
+
*
|
|
15813
|
+
* @param pc - The perf context.
|
|
15814
|
+
* @param slot - The owner's slot.
|
|
15815
|
+
* @param record - The owner's kept record, or `undefined` without one.
|
|
15816
|
+
* @returns True when the rule holds.
|
|
15817
|
+
*/
|
|
15818
|
+
function standsStill$1(pc, slot, record) {
|
|
15819
|
+
const { problems } = pc.perf;
|
|
15820
|
+
const { stillFrames, stillMpx } = problems.thresholds;
|
|
15821
|
+
const kept = pc.ctx.state.kept;
|
|
15822
|
+
const frames = cellF64(problems.secFrames, slot);
|
|
15823
|
+
if (!(record !== void 0 && record.mode === "live" && kept.on && !kept.bypassed)) return false;
|
|
15824
|
+
if (!(frames > 0 && record.quiet >= stillFrames && cellF64(problems.run, slot) >= stillFrames)) return false;
|
|
15825
|
+
return cellF64(problems.secPixels, slot) / frames / PIXELS_PER_MPX$1 >= stillMpx;
|
|
15826
|
+
}
|
|
15827
|
+
/**
|
|
15828
|
+
* Judges `filter-still` for one owner: a crossing opens the problem with its reason, a stop clears it.
|
|
15829
|
+
*
|
|
15830
|
+
* @param pc - The perf context.
|
|
15831
|
+
* @param slot - The owner's slot.
|
|
15832
|
+
* @param owner - The owner.
|
|
15833
|
+
* @param out - The details written now.
|
|
15834
|
+
*/
|
|
15835
|
+
function judgeStillOwner(pc, slot, owner, out) {
|
|
15836
|
+
const { problems } = pc.perf;
|
|
15837
|
+
const { owners, kindNames } = pc.sinks;
|
|
15838
|
+
const record = pc.ctx.state.kept.byEntity.get(owner);
|
|
15839
|
+
const still = standsStill$1(pc, slot, record);
|
|
15840
|
+
const open = cellU8(problems.stillOpen, slot) === 1;
|
|
15841
|
+
if (still && !open && record !== void 0) {
|
|
15842
|
+
const kind = cellU8(owners.kinds, slot);
|
|
15843
|
+
const frames = cellF64(problems.run, slot);
|
|
15844
|
+
problems.stillOpen[slot] = 1;
|
|
15845
|
+
out.push({
|
|
15846
|
+
rule: "filter-still",
|
|
15847
|
+
severity: "warn",
|
|
15848
|
+
owner,
|
|
15849
|
+
view: viewLabel(pc, owner),
|
|
15850
|
+
filter: kind === 0 ? null : kindNames[kind - 1] ?? "other",
|
|
15851
|
+
value: Math.min(frames, record.quiet),
|
|
15852
|
+
threshold: problems.thresholds.stillFrames,
|
|
15853
|
+
since: cellF64(problems.runSince, slot),
|
|
15854
|
+
frames,
|
|
15855
|
+
reason: record.refused > 0 ? "budget" : "not-eligible"
|
|
15856
|
+
});
|
|
15857
|
+
} else if (!still && open) {
|
|
15858
|
+
problems.stillOpen[slot] = 0;
|
|
15859
|
+
out.push({
|
|
15860
|
+
rule: "filter-still",
|
|
15861
|
+
cleared: true,
|
|
15862
|
+
owner,
|
|
15863
|
+
view: viewLabel(pc, owner)
|
|
15864
|
+
});
|
|
15865
|
+
}
|
|
15866
|
+
}
|
|
15867
|
+
/**
|
|
15868
|
+
* Judges `filter-still` for every owner of the table: a crossing opens the problem with its reason (`"budget"` when
|
|
15869
|
+
* the kept budget refused the picture, `"not-eligible"` otherwise), a stop clears it; an owner whose filter applied in
|
|
15870
|
+
* no frame of the second leaves its slot.
|
|
15871
|
+
*
|
|
15872
|
+
* @param pc - The perf context.
|
|
15873
|
+
* @param out - The details written now.
|
|
15874
|
+
*/
|
|
15875
|
+
function judgeStill(pc, out) {
|
|
15876
|
+
const { problems } = pc.perf;
|
|
15877
|
+
const { owners } = pc.sinks;
|
|
15878
|
+
for (let slot = 0; slot < 32; slot += 1) {
|
|
15879
|
+
const owner = cellF64(owners.ids, slot);
|
|
15880
|
+
if (owner === 0) continue;
|
|
15881
|
+
judgeStillOwner(pc, slot, owner, out);
|
|
15882
|
+
if (cellF64(problems.secFrames, slot) === 0) {
|
|
15883
|
+
owners.ids[slot] = 0;
|
|
15884
|
+
problems.run[slot] = 0;
|
|
15885
|
+
}
|
|
15886
|
+
problems.secFrames[slot] = 0;
|
|
15887
|
+
problems.secPixels[slot] = 0;
|
|
15888
|
+
}
|
|
15889
|
+
}
|
|
15890
|
+
/**
|
|
15891
|
+
* Why `target-churn` opens: a create soon after a pool trim wins over a create in an idle frame, and both over the
|
|
15892
|
+
* rate.
|
|
15893
|
+
*
|
|
15894
|
+
* @param problems - The problem state.
|
|
15895
|
+
* @returns The reason of the problem line.
|
|
15896
|
+
*/
|
|
15897
|
+
function churnReason(problems) {
|
|
15898
|
+
if (problems.trimCreate) return "after-trim";
|
|
15899
|
+
if (problems.idleCreate) return "idle";
|
|
15900
|
+
return "rate";
|
|
15901
|
+
}
|
|
15902
|
+
/**
|
|
15903
|
+
* Judges `target-churn`: `churnPerSecond` targets made and dropped in each of `churnSeconds` seconds in a row, a
|
|
15904
|
+
* create in an idle frame, or a create within `churnAfterTrimMs` after a pool trim. One problem for the whole canvas.
|
|
15905
|
+
*
|
|
15906
|
+
* @param pc - The perf context.
|
|
15907
|
+
* @param out - The details written now.
|
|
15908
|
+
*/
|
|
15909
|
+
function judgeChurn(pc, out) {
|
|
15910
|
+
const { problems } = pc.perf;
|
|
15911
|
+
const { churnPerSecond, churnSeconds } = problems.thresholds;
|
|
15912
|
+
const busy = problems.secTargets >= churnPerSecond;
|
|
15913
|
+
if (busy && problems.churnRun === 0) problems.churnSince = pc.perf.frame;
|
|
15914
|
+
problems.churnRun = busy ? problems.churnRun + 1 : 0;
|
|
15915
|
+
const steady = problems.churnRun >= churnSeconds;
|
|
15916
|
+
const churns = steady || problems.idleCreate || problems.trimCreate;
|
|
15917
|
+
if (churns && !problems.churnOpen) {
|
|
15918
|
+
const since = steady ? problems.churnSince : problems.createFrame;
|
|
15919
|
+
problems.churnOpen = true;
|
|
15920
|
+
out.push({
|
|
15921
|
+
rule: "target-churn",
|
|
15922
|
+
severity: "warn",
|
|
15923
|
+
owner: null,
|
|
15924
|
+
view: null,
|
|
15925
|
+
filter: null,
|
|
15926
|
+
value: problems.secTargets,
|
|
15927
|
+
threshold: churnPerSecond,
|
|
15928
|
+
since,
|
|
15929
|
+
frames: Math.max(1, pc.perf.frame - since),
|
|
15930
|
+
reason: churnReason(problems)
|
|
15931
|
+
});
|
|
15932
|
+
} else if (!churns && problems.churnOpen) {
|
|
15933
|
+
problems.churnOpen = false;
|
|
15934
|
+
out.push({
|
|
15935
|
+
rule: "target-churn",
|
|
15936
|
+
cleared: true,
|
|
15937
|
+
owner: null,
|
|
15938
|
+
view: null
|
|
15939
|
+
});
|
|
15940
|
+
}
|
|
15941
|
+
problems.secTargets = 0;
|
|
15942
|
+
problems.idleCreate = false;
|
|
15943
|
+
problems.trimCreate = false;
|
|
15944
|
+
}
|
|
15945
|
+
/**
|
|
15946
|
+
* The stage-1 takes of a kept record in the last two seconds, its first take left out: the takes now less the ones of
|
|
15947
|
+
* two flushes back (one back for a record that came since, none for a record of this second).
|
|
15948
|
+
*
|
|
15949
|
+
* @param state - The problem state.
|
|
15950
|
+
* @param record - The record.
|
|
15951
|
+
* @returns Its retakes in the window.
|
|
15952
|
+
*/
|
|
15953
|
+
function retakesOf(state, record) {
|
|
15954
|
+
const before = state.takes[1].get(record.entity) ?? state.takes[0].get(record.entity) ?? 1;
|
|
15955
|
+
return Math.max(0, record.s1Version - Math.max(1, before));
|
|
15956
|
+
}
|
|
15957
|
+
/**
|
|
15958
|
+
* Judges `retakes`: a kept view taken again `RETAKES_LIMIT` times or more in two seconds. Its `value` is the takes per
|
|
15959
|
+
* second. A record that left clears its problem. The takes of every record are kept for the next two judgments.
|
|
15960
|
+
*
|
|
15961
|
+
* @param pc - The perf context.
|
|
15962
|
+
* @param out - The details written now.
|
|
15963
|
+
*/
|
|
15964
|
+
function judgeRetakes(pc, out) {
|
|
15965
|
+
const { problems } = pc.perf;
|
|
15966
|
+
const { records } = pc.ctx.state.kept;
|
|
15967
|
+
const now = /* @__PURE__ */ new Map();
|
|
15968
|
+
const since = problems.takesFrame[1];
|
|
15969
|
+
for (const record of records) {
|
|
15970
|
+
const { entity } = record;
|
|
15971
|
+
const count = retakesOf(problems, record);
|
|
15972
|
+
const open = problems.retakesOpen.has(entity);
|
|
15973
|
+
now.set(entity, record.s1Version);
|
|
15974
|
+
if (count >= 3 && !open) {
|
|
15975
|
+
problems.retakesOpen.add(entity);
|
|
15976
|
+
out.push({
|
|
15977
|
+
rule: "retakes",
|
|
15978
|
+
severity: "info",
|
|
15979
|
+
owner: entity,
|
|
15980
|
+
view: viewLabel(pc, entity),
|
|
15981
|
+
filter: record.slots[0]?.kind ?? null,
|
|
15982
|
+
value: roundTo(count / RETAKES_SECONDS, 2),
|
|
15983
|
+
threshold: 3 / RETAKES_SECONDS,
|
|
15984
|
+
since,
|
|
15985
|
+
frames: Math.max(1, pc.perf.frame - since)
|
|
15986
|
+
});
|
|
15987
|
+
} else if (count < 3 && open) {
|
|
15988
|
+
problems.retakesOpen.delete(entity);
|
|
15989
|
+
out.push({
|
|
15990
|
+
rule: "retakes",
|
|
15991
|
+
cleared: true,
|
|
15992
|
+
owner: entity,
|
|
15993
|
+
view: viewLabel(pc, entity)
|
|
15994
|
+
});
|
|
15995
|
+
}
|
|
15996
|
+
}
|
|
15997
|
+
for (const entity of problems.retakesOpen) {
|
|
15998
|
+
if (now.has(entity)) continue;
|
|
15999
|
+
problems.retakesOpen.delete(entity);
|
|
16000
|
+
out.push({
|
|
16001
|
+
rule: "retakes",
|
|
16002
|
+
cleared: true,
|
|
16003
|
+
owner: entity,
|
|
16004
|
+
view: viewLabel(pc, entity)
|
|
16005
|
+
});
|
|
16006
|
+
}
|
|
16007
|
+
problems.takes = [now, problems.takes[0]];
|
|
16008
|
+
problems.takesFrame = [pc.perf.frame, problems.takesFrame[0]];
|
|
16009
|
+
}
|
|
16010
|
+
/**
|
|
16011
|
+
* The once-a-second judgment of the three rules, at the flush: the details of the problems that opened or cleared.
|
|
16012
|
+
* Allocates: the flush is allowed to.
|
|
16013
|
+
*
|
|
16014
|
+
* @param pc - The perf context.
|
|
16015
|
+
* @returns The details, in rule order.
|
|
16016
|
+
*/
|
|
16017
|
+
function judgeProblems(pc) {
|
|
16018
|
+
const out = [];
|
|
16019
|
+
judgeStill(pc, out);
|
|
16020
|
+
judgeChurn(pc, out);
|
|
16021
|
+
judgeRetakes(pc, out);
|
|
16022
|
+
return out;
|
|
16023
|
+
}
|
|
16024
|
+
/**
|
|
16025
|
+
* Forgets every accumulator and every open problem: the renderer stopped.
|
|
16026
|
+
*
|
|
16027
|
+
* @param pc - The perf context's sinks and perf state.
|
|
16028
|
+
* @param pc.perf - The perf state.
|
|
16029
|
+
* @param pc.sinks - The sinks.
|
|
16030
|
+
*/
|
|
16031
|
+
function resetProblems(pc) {
|
|
16032
|
+
const { problems } = pc.perf;
|
|
16033
|
+
pc.sinks.owners.ids.fill(0);
|
|
16034
|
+
problems.run.fill(0);
|
|
16035
|
+
problems.secFrames.fill(0);
|
|
16036
|
+
problems.secPixels.fill(0);
|
|
16037
|
+
problems.stillOpen.fill(0);
|
|
16038
|
+
problems.secTargets = 0;
|
|
16039
|
+
problems.idleCreate = false;
|
|
16040
|
+
problems.trimCreate = false;
|
|
16041
|
+
problems.trimAt = NaN;
|
|
16042
|
+
problems.churnRun = 0;
|
|
16043
|
+
problems.churnOpen = false;
|
|
16044
|
+
problems.takes = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
|
|
16045
|
+
problems.takesFrame = [0, 0];
|
|
16046
|
+
problems.retakesOpen.clear();
|
|
16047
|
+
}
|
|
16048
|
+
//#endregion
|
|
15566
16049
|
//#region src/plugins/renderer/monitor/log-types.ts
|
|
15567
16050
|
/**
|
|
15568
16051
|
* The version every line carries as `v`.
|
|
@@ -15593,6 +16076,35 @@ const PERF_CAUSES = [
|
|
|
15593
16076
|
"longFrame",
|
|
15594
16077
|
"gc?"
|
|
15595
16078
|
];
|
|
16079
|
+
/**
|
|
16080
|
+
* What a `shader` event line reports, in its `what`: a render pipeline (WebGPU) or a program (WebGL) created, or a
|
|
16081
|
+
* WebGPU shader module compiled. New values are only ever appended.
|
|
16082
|
+
*
|
|
16083
|
+
* @example
|
|
16084
|
+
* ```ts
|
|
16085
|
+
* // The editor counts the pipelines of a recording without a switch over strings it does not know.
|
|
16086
|
+
* const pipelines = lines.filter(line => line.kind === "event" && line.name === "shader" && line.detail.what === SHADER_WHATS[0]).length; // 14
|
|
16087
|
+
* ```
|
|
16088
|
+
*/
|
|
16089
|
+
const SHADER_WHATS = ["compile", "module"];
|
|
16090
|
+
/**
|
|
16091
|
+
* The rules of the `problem` event lines, judged once a second: `filter-still` (a filtered view stood still and was
|
|
16092
|
+
* filtered again in every drawn frame), `target-churn` (render targets made and dropped all the time, or right after a
|
|
16093
|
+
* pool trim, or while idle), `retakes` (a kept view taken again and again). New rules are only ever appended.
|
|
16094
|
+
*
|
|
16095
|
+
* @example
|
|
16096
|
+
* ```ts
|
|
16097
|
+
* // The editor reads the rule of the first problem a recording opened: a fog blur filtered again on a still board.
|
|
16098
|
+
* const problems = lines.filter((line): line is PerfEventLine => line.kind === "event" && line.name === "problem");
|
|
16099
|
+
* const opened = problems.find(line => line.detail.cleared !== true);
|
|
16100
|
+
* PROBLEM_RULES.find(rule => rule === opened?.detail.rule); // "filter-still"
|
|
16101
|
+
* ```
|
|
16102
|
+
*/
|
|
16103
|
+
const PROBLEM_RULES = [
|
|
16104
|
+
"filter-still",
|
|
16105
|
+
"target-churn",
|
|
16106
|
+
"retakes"
|
|
16107
|
+
];
|
|
15596
16108
|
//#endregion
|
|
15597
16109
|
//#region src/plugins/renderer/monitor/ring.ts
|
|
15598
16110
|
/**
|
|
@@ -15637,12 +16149,26 @@ const CHANGE_NAMES = [
|
|
|
15637
16149
|
"bundle",
|
|
15638
16150
|
"restore"
|
|
15639
16151
|
];
|
|
15640
|
-
/**
|
|
16152
|
+
/**
|
|
16153
|
+
* The pass kinds by their index in the pass ring's `kind` column: 0 the canvas, 1 a texture, 2 a filter. A root export,
|
|
16154
|
+
* so a reader of the ring names a pass's kind without building a record.
|
|
16155
|
+
*
|
|
16156
|
+
* @example
|
|
16157
|
+
* ```ts
|
|
16158
|
+
* // The editor's pass strip names the newest pass of a debug build from the ring.
|
|
16159
|
+
* const { passes } = app.renderer.monitor.frames();
|
|
16160
|
+
* PASS_KINDS[passes.kind[passes.newest] ?? 0]; // "filter"
|
|
16161
|
+
* ```
|
|
16162
|
+
*/
|
|
15641
16163
|
const PASS_KINDS = [
|
|
15642
16164
|
"canvas",
|
|
15643
16165
|
"texture",
|
|
15644
16166
|
"filter"
|
|
15645
16167
|
];
|
|
16168
|
+
/** The prefix of the label of a pass that took or refreshed a kept picture (`kept:<view>#n`). */
|
|
16169
|
+
const KEPT_PREFIX = "kept:";
|
|
16170
|
+
/** The largest draw count the 16-bit `drawsAt` column holds. */
|
|
16171
|
+
const DRAWS_MAX = 65535;
|
|
15646
16172
|
/**
|
|
15647
16173
|
* Creates the ring of the changes with a subject: 64 slots, empty.
|
|
15648
16174
|
*
|
|
@@ -15684,7 +16210,7 @@ function noteSubject(events, frame, change, what) {
|
|
|
15684
16210
|
function subjectOf(events, frame, bit) {
|
|
15685
16211
|
for (let age = 0; age < events.size; age += 1) {
|
|
15686
16212
|
const slot = (events.next - 1 - age + 64) % 64;
|
|
15687
|
-
if (
|
|
16213
|
+
if (cellU32(events.frames, slot) === frame && cellU8(events.kinds, slot) === bit) return events.whats[slot] ?? null;
|
|
15688
16214
|
}
|
|
15689
16215
|
return null;
|
|
15690
16216
|
}
|
|
@@ -15732,7 +16258,9 @@ function createColumns(capacity) {
|
|
|
15732
16258
|
fltApplies: new Uint16Array(capacity * 4),
|
|
15733
16259
|
fltMpx: new Float32Array(capacity * 4),
|
|
15734
16260
|
kept: new Float32Array(capacity).fill(NaN),
|
|
15735
|
-
retakes: new Float32Array(capacity).fill(NaN)
|
|
16261
|
+
retakes: new Float32Array(capacity).fill(NaN),
|
|
16262
|
+
targetsCreated: new Float32Array(capacity).fill(NaN),
|
|
16263
|
+
targetsDestroyed: new Float32Array(capacity).fill(NaN)
|
|
15736
16264
|
};
|
|
15737
16265
|
}
|
|
15738
16266
|
/**
|
|
@@ -15754,11 +16282,86 @@ function createPasses(capacity) {
|
|
|
15754
16282
|
width: new Uint16Array(capacity),
|
|
15755
16283
|
height: new Uint16Array(capacity),
|
|
15756
16284
|
gpuMs: new Float32Array(capacity),
|
|
15757
|
-
label: Array.from({ length: capacity }).fill(null)
|
|
16285
|
+
label: Array.from({ length: capacity }).fill(null),
|
|
16286
|
+
owner: new Uint32Array(capacity),
|
|
16287
|
+
kind2: new Uint8Array(capacity),
|
|
16288
|
+
drawsAt: new Uint16Array(capacity),
|
|
16289
|
+
format: Array.from({ length: capacity }).fill(null),
|
|
16290
|
+
resolution: new Float32Array(capacity).fill(NaN)
|
|
15758
16291
|
};
|
|
15759
16292
|
}
|
|
15760
16293
|
/**
|
|
15761
|
-
* Copies one column of `count` frames, oldest first, into the caller's array, an indexed loop over the
|
|
16294
|
+
* Copies one `Float32Array` column of `count` frames, oldest first, into the caller's array, an indexed loop over the
|
|
16295
|
+
* ring. One copy per array kind, as one column reader per kind: its loads and stores see one kind of array, and a
|
|
16296
|
+
* double is copied unboxed.
|
|
16297
|
+
*
|
|
16298
|
+
* @param target - The caller's array; nothing is copied without one.
|
|
16299
|
+
* @param source - The ring's column.
|
|
16300
|
+
* @param stride - Values per frame.
|
|
16301
|
+
* @param ring - The ring.
|
|
16302
|
+
* @param oldest - Slot of the oldest frame to copy.
|
|
16303
|
+
* @param count - Frames to copy.
|
|
16304
|
+
*/
|
|
16305
|
+
function copyF32(target, source, stride, ring, oldest, count) {
|
|
16306
|
+
if (target === void 0) return;
|
|
16307
|
+
for (let index = 0; index < count; index += 1) {
|
|
16308
|
+
const slot = (oldest + index) % ring.capacity;
|
|
16309
|
+
for (let value = 0; value < stride; value += 1) target[index * stride + value] = cellF32(source, slot * stride + value);
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16312
|
+
/**
|
|
16313
|
+
* Copies one `Float64Array` column of `count` frames, oldest first, into the caller's array: `copyF32` for its kind.
|
|
16314
|
+
*
|
|
16315
|
+
* @param target - The caller's array; nothing is copied without one.
|
|
16316
|
+
* @param source - The ring's column.
|
|
16317
|
+
* @param stride - Values per frame.
|
|
16318
|
+
* @param ring - The ring.
|
|
16319
|
+
* @param oldest - Slot of the oldest frame to copy.
|
|
16320
|
+
* @param count - Frames to copy.
|
|
16321
|
+
*/
|
|
16322
|
+
function copyF64(target, source, stride, ring, oldest, count) {
|
|
16323
|
+
if (target === void 0) return;
|
|
16324
|
+
for (let index = 0; index < count; index += 1) {
|
|
16325
|
+
const slot = (oldest + index) % ring.capacity;
|
|
16326
|
+
for (let value = 0; value < stride; value += 1) target[index * stride + value] = cellF64(source, slot * stride + value);
|
|
16327
|
+
}
|
|
16328
|
+
}
|
|
16329
|
+
/**
|
|
16330
|
+
* Copies one `Uint32Array` column of `count` frames, oldest first, into the caller's array: `copyF32` for its kind.
|
|
16331
|
+
*
|
|
16332
|
+
* @param target - The caller's array; nothing is copied without one.
|
|
16333
|
+
* @param source - The ring's column.
|
|
16334
|
+
* @param stride - Values per frame.
|
|
16335
|
+
* @param ring - The ring.
|
|
16336
|
+
* @param oldest - Slot of the oldest frame to copy.
|
|
16337
|
+
* @param count - Frames to copy.
|
|
16338
|
+
*/
|
|
16339
|
+
function copyU32(target, source, stride, ring, oldest, count) {
|
|
16340
|
+
if (target === void 0) return;
|
|
16341
|
+
for (let index = 0; index < count; index += 1) {
|
|
16342
|
+
const slot = (oldest + index) % ring.capacity;
|
|
16343
|
+
for (let value = 0; value < stride; value += 1) target[index * stride + value] = cellU32(source, slot * stride + value);
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
/**
|
|
16347
|
+
* Copies one `Uint16Array` column of `count` frames, oldest first, into the caller's array: `copyF32` for its kind.
|
|
16348
|
+
*
|
|
16349
|
+
* @param target - The caller's array; nothing is copied without one.
|
|
16350
|
+
* @param source - The ring's column.
|
|
16351
|
+
* @param stride - Values per frame.
|
|
16352
|
+
* @param ring - The ring.
|
|
16353
|
+
* @param oldest - Slot of the oldest frame to copy.
|
|
16354
|
+
* @param count - Frames to copy.
|
|
16355
|
+
*/
|
|
16356
|
+
function copyU16(target, source, stride, ring, oldest, count) {
|
|
16357
|
+
if (target === void 0) return;
|
|
16358
|
+
for (let index = 0; index < count; index += 1) {
|
|
16359
|
+
const slot = (oldest + index) % ring.capacity;
|
|
16360
|
+
for (let value = 0; value < stride; value += 1) target[index * stride + value] = cellU16(source, slot * stride + value);
|
|
16361
|
+
}
|
|
16362
|
+
}
|
|
16363
|
+
/**
|
|
16364
|
+
* Copies one `Uint8Array` column of `count` frames, oldest first, into the caller's array: `copyF32` for its kind.
|
|
15762
16365
|
*
|
|
15763
16366
|
* @param target - The caller's array; nothing is copied without one.
|
|
15764
16367
|
* @param source - The ring's column.
|
|
@@ -15767,11 +16370,11 @@ function createPasses(capacity) {
|
|
|
15767
16370
|
* @param oldest - Slot of the oldest frame to copy.
|
|
15768
16371
|
* @param count - Frames to copy.
|
|
15769
16372
|
*/
|
|
15770
|
-
function
|
|
16373
|
+
function copyU8(target, source, stride, ring, oldest, count) {
|
|
15771
16374
|
if (target === void 0) return;
|
|
15772
16375
|
for (let index = 0; index < count; index += 1) {
|
|
15773
16376
|
const slot = (oldest + index) % ring.capacity;
|
|
15774
|
-
for (let value = 0; value < stride; value += 1) target[index * stride + value] =
|
|
16377
|
+
for (let value = 0; value < stride; value += 1) target[index * stride + value] = cellU8(source, slot * stride + value);
|
|
15775
16378
|
}
|
|
15776
16379
|
}
|
|
15777
16380
|
/**
|
|
@@ -15791,43 +16394,45 @@ function readRing(ring, into, since) {
|
|
|
15791
16394
|
result.lost = available - count;
|
|
15792
16395
|
if (count === 0) return result;
|
|
15793
16396
|
const oldest = (ring.newest - count + 1 + ring.capacity) % ring.capacity;
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
16397
|
+
copyU32(into.frame, columns.frame, 1, ring, oldest, count);
|
|
16398
|
+
copyF64(into.t, columns.t, 1, ring, oldest, count);
|
|
16399
|
+
copyF32(into.gapMs, columns.gapMs, 1, ring, oldest, count);
|
|
16400
|
+
copyF32(into.capMs, columns.capMs, 1, ring, oldest, count);
|
|
16401
|
+
copyU8(into.skipped, columns.skipped, 1, ring, oldest, count);
|
|
16402
|
+
copyF32(into.startMs, columns.startMs, 1, ring, oldest, count);
|
|
16403
|
+
copyF32(into.rateMs, columns.rateMs, 1, ring, oldest, count);
|
|
16404
|
+
copyF32(into.budgetMs, columns.budgetMs, 1, ring, oldest, count);
|
|
16405
|
+
copyF32(into.phaseMs, columns.phaseMs, 6, ring, oldest, count);
|
|
16406
|
+
copyF32(into.agentMs, columns.agentMs, 1, ring, oldest, count);
|
|
16407
|
+
copyF32(into.workMs, columns.workMs, 1, ring, oldest, count);
|
|
16408
|
+
copyU8(into.drawn, columns.drawn, 1, ring, oldest, count);
|
|
16409
|
+
copyU8(into.idle, columns.idle, 1, ring, oldest, count);
|
|
16410
|
+
copyU8(into.stepped, columns.stepped, 1, ring, oldest, count);
|
|
16411
|
+
copyF32(into.passes, columns.passes, 1, ring, oldest, count);
|
|
16412
|
+
copyF32(into.stencilPasses, columns.stencilPasses, 1, ring, oldest, count);
|
|
16413
|
+
copyF32(into.draws, columns.draws, 1, ring, oldest, count);
|
|
16414
|
+
copyF32(into.filterApplies, columns.filterApplies, 1, ring, oldest, count);
|
|
16415
|
+
copyF32(into.filterMpx, columns.filterMpx, 1, ring, oldest, count);
|
|
16416
|
+
copyF32(into.uploads, columns.uploads, 1, ring, oldest, count);
|
|
16417
|
+
copyF32(into.uploadMb, columns.uploadMb, 1, ring, oldest, count);
|
|
16418
|
+
copyF32(into.compiles, columns.compiles, 1, ring, oldest, count);
|
|
16419
|
+
copyF32(into.marks, columns.marks, 1, ring, oldest, count);
|
|
16420
|
+
copyF32(into.rebuilds, columns.rebuilds, 1, ring, oldest, count);
|
|
16421
|
+
copyF32(into.gpuMs, columns.gpuMs, 1, ring, oldest, count);
|
|
16422
|
+
copyU8(into.gpuPartial, columns.gpuPartial, 1, ring, oldest, count);
|
|
16423
|
+
copyU16(into.path, columns.path, 1, ring, oldest, count);
|
|
16424
|
+
copyU8(into.changed, columns.changed, 1, ring, oldest, count);
|
|
16425
|
+
copyU8(into.causes, columns.causes, 1, ring, oldest, count);
|
|
16426
|
+
copyU8(into.reason, columns.reason, 1, ring, oldest, count);
|
|
16427
|
+
copyU16(into.sysIdx, columns.sysIdx, 5, ring, oldest, count);
|
|
16428
|
+
copyF32(into.sysMs, columns.sysMs, 5, ring, oldest, count);
|
|
16429
|
+
copyU8(into.fltKind, columns.fltKind, 4, ring, oldest, count);
|
|
16430
|
+
copyU16(into.fltApplies, columns.fltApplies, 4, ring, oldest, count);
|
|
16431
|
+
copyF32(into.fltMpx, columns.fltMpx, 4, ring, oldest, count);
|
|
16432
|
+
copyF32(into.kept, columns.kept, 1, ring, oldest, count);
|
|
16433
|
+
copyF32(into.retakes, columns.retakes, 1, ring, oldest, count);
|
|
16434
|
+
copyF32(into.targetsCreated, columns.targetsCreated, 1, ring, oldest, count);
|
|
16435
|
+
copyF32(into.targetsDestroyed, columns.targetsDestroyed, 1, ring, oldest, count);
|
|
15831
16436
|
return result;
|
|
15832
16437
|
}
|
|
15833
16438
|
/**
|
|
@@ -15864,7 +16469,8 @@ function createFrameRing(capacity, events = createEventRing(), kindNames = []) {
|
|
|
15864
16469
|
*/
|
|
15865
16470
|
const EMPTY_RING = /*#__PURE__*/ createFrameRing(0);
|
|
15866
16471
|
/**
|
|
15867
|
-
* Records one render pass in the pass ring, the label by reference.
|
|
16472
|
+
* Records one render pass in the pass ring, the label by reference. The detail of the pass (its owner, filter kind,
|
|
16473
|
+
* draw count, format and resolution) goes into the same slot with `writePassDetail`, right after.
|
|
15868
16474
|
*
|
|
15869
16475
|
* @param passes - The pass ring.
|
|
15870
16476
|
* @param frame - The frame number.
|
|
@@ -15887,12 +16493,36 @@ function writePass(passes, frame, index, kind, stencil, width, height, label) {
|
|
|
15887
16493
|
passes.height[slot] = height;
|
|
15888
16494
|
passes.gpuMs[slot] = NaN;
|
|
15889
16495
|
passes.label[slot] = label;
|
|
16496
|
+
passes.owner[slot] = 0;
|
|
16497
|
+
passes.kind2[slot] = 0;
|
|
16498
|
+
passes.drawsAt[slot] = 0;
|
|
16499
|
+
passes.format[slot] = null;
|
|
16500
|
+
passes.resolution[slot] = NaN;
|
|
15890
16501
|
passes.newest = slot;
|
|
15891
16502
|
passes.size = Math.min(passes.size + 1, passes.capacity);
|
|
15892
16503
|
passes.written = sequence + 1;
|
|
15893
16504
|
return sequence;
|
|
15894
16505
|
}
|
|
15895
16506
|
/**
|
|
16507
|
+
* Writes the detail of the newest pass: the entity whose filter drew it, the filter kind (1-based, 0 none), the draw
|
|
16508
|
+
* count of the frame when it began, and its target's format (by reference) and resolution, read from the target's
|
|
16509
|
+
* source here so no double is handed over. Indexed stores only.
|
|
16510
|
+
*
|
|
16511
|
+
* @param passes - The pass ring, its newest slot the pass `writePass` just wrote.
|
|
16512
|
+
* @param owner - The entity, 0 for none.
|
|
16513
|
+
* @param kind2 - The interned filter kind plus one, 0 for none.
|
|
16514
|
+
* @param drawsAt - The frame's draws so far.
|
|
16515
|
+
* @param source - The source of the target's color texture, or `undefined`.
|
|
16516
|
+
*/
|
|
16517
|
+
function writePassDetail(passes, owner, kind2, drawsAt, source) {
|
|
16518
|
+
const slot = passes.newest;
|
|
16519
|
+
passes.owner[slot] = owner;
|
|
16520
|
+
passes.kind2[slot] = kind2;
|
|
16521
|
+
passes.drawsAt[slot] = Math.min(drawsAt, DRAWS_MAX);
|
|
16522
|
+
passes.format[slot] = source?.format ?? null;
|
|
16523
|
+
passes.resolution[slot] = source?.resolution ?? NaN;
|
|
16524
|
+
}
|
|
16525
|
+
/**
|
|
15896
16526
|
* The commit number of the frame in a slot: `written − 1` for the newest.
|
|
15897
16527
|
*
|
|
15898
16528
|
* @param ring - The ring.
|
|
@@ -15959,13 +16589,13 @@ function spikeCauses(frame, agentMs, budget, previousCapMs) {
|
|
|
15959
16589
|
return causes;
|
|
15960
16590
|
}
|
|
15961
16591
|
/**
|
|
15962
|
-
* Tells whether a ring is the monitor's own: the one that
|
|
16592
|
+
* Tells whether a ring is the monitor's own: the one that keeps the subjects of its changes.
|
|
15963
16593
|
*
|
|
15964
16594
|
* @param ring - A ring.
|
|
15965
16595
|
* @returns True for a ring of `monitor.frames()`.
|
|
15966
16596
|
*/
|
|
15967
16597
|
function isPerfRing(ring) {
|
|
15968
|
-
return "events" in ring
|
|
16598
|
+
return "events" in ring;
|
|
15969
16599
|
}
|
|
15970
16600
|
/**
|
|
15971
16601
|
* Milliseconds or megabytes of a column as a record carries them: 2 decimals, so a `Float32Array`
|
|
@@ -16012,17 +16642,17 @@ function namesOf$3(bits, names) {
|
|
|
16012
16642
|
* @returns Kind, applies and megapixels, the most pixels first.
|
|
16013
16643
|
*/
|
|
16014
16644
|
function filterKindsAt(ring, slot) {
|
|
16015
|
-
const names =
|
|
16645
|
+
const names = ring.kindNames;
|
|
16016
16646
|
const { columns } = ring;
|
|
16017
16647
|
const kinds = [];
|
|
16018
16648
|
for (let place = 0; place < 4; place += 1) {
|
|
16019
16649
|
const at = slot * 4 + place;
|
|
16020
|
-
const kind =
|
|
16650
|
+
const kind = cellU8(columns.fltKind, at);
|
|
16021
16651
|
if (kind === 0) continue;
|
|
16022
16652
|
kinds.push({
|
|
16023
16653
|
kind: names[kind - 1] ?? "other",
|
|
16024
|
-
applies:
|
|
16025
|
-
mpx: two(
|
|
16654
|
+
applies: cellU16(columns.fltApplies, at),
|
|
16655
|
+
mpx: two(cellF32(columns.fltMpx, at))
|
|
16026
16656
|
});
|
|
16027
16657
|
}
|
|
16028
16658
|
return kinds;
|
|
@@ -16036,16 +16666,16 @@ function filterKindsAt(ring, slot) {
|
|
|
16036
16666
|
*/
|
|
16037
16667
|
function systemsAt(ring, slot) {
|
|
16038
16668
|
const { columns } = ring;
|
|
16039
|
-
if (Number.isNaN(
|
|
16040
|
-
const names =
|
|
16669
|
+
if (Number.isNaN(cellF32(columns.sysMs, slot * 5))) return null;
|
|
16670
|
+
const names = ring.systemNames;
|
|
16041
16671
|
const systems = [];
|
|
16042
16672
|
for (let place = 0; place < 5; place += 1) {
|
|
16043
16673
|
const at = slot * 5 + place;
|
|
16044
|
-
const index =
|
|
16674
|
+
const index = cellU16(columns.sysIdx, at);
|
|
16045
16675
|
if (index === 65535) continue;
|
|
16046
16676
|
systems.push({
|
|
16047
16677
|
name: names[index] ?? String(index),
|
|
16048
|
-
ms: two(
|
|
16678
|
+
ms: two(cellF32(columns.sysMs, at))
|
|
16049
16679
|
});
|
|
16050
16680
|
}
|
|
16051
16681
|
return systems;
|
|
@@ -16058,39 +16688,73 @@ function systemsAt(ring, slot) {
|
|
|
16058
16688
|
* @returns One entry per change, in bit order.
|
|
16059
16689
|
*/
|
|
16060
16690
|
function changesAt(ring, slot) {
|
|
16061
|
-
const bits =
|
|
16062
|
-
const frame =
|
|
16691
|
+
const bits = cellU8(ring.columns.changed, slot);
|
|
16692
|
+
const frame = cellU32(ring.columns.frame, slot);
|
|
16063
16693
|
return namesOf$3(bits, CHANGE_NAMES).map((kind) => ({
|
|
16064
16694
|
kind,
|
|
16065
16695
|
what: isPerfRing(ring) ? subjectOf(ring.events, frame, CHANGED[kind]) : null
|
|
16066
16696
|
}));
|
|
16067
16697
|
}
|
|
16068
16698
|
/**
|
|
16069
|
-
* The
|
|
16699
|
+
* The draws of each pass of a frame from the snapshots its passes took when they began: up to the next pass's
|
|
16700
|
+
* snapshot, the last up to the frame's total. Pure: rewrites `draws` in place.
|
|
16701
|
+
*
|
|
16702
|
+
* @param found - The passes of the frame, in order, with `draws` holding their snapshot.
|
|
16703
|
+
* @param total - The frame's draws; NaN where draws are not counted, which makes every `draws` `null`.
|
|
16704
|
+
* @example
|
|
16705
|
+
* ```ts
|
|
16706
|
+
* splitDraws([{ draws: 0 }, { draws: 5 }], 7); // the passes now say 5 and 2
|
|
16707
|
+
* ```
|
|
16708
|
+
*/
|
|
16709
|
+
function splitDraws(found, total) {
|
|
16710
|
+
for (let index = 0; index < found.length; index += 1) {
|
|
16711
|
+
const pass = found[index];
|
|
16712
|
+
if (pass === void 0) continue;
|
|
16713
|
+
const next = found[index + 1]?.draws ?? total;
|
|
16714
|
+
pass.draws = Number.isNaN(total) ? null : Math.max(0, next - (pass.draws ?? 0));
|
|
16715
|
+
}
|
|
16716
|
+
}
|
|
16717
|
+
/**
|
|
16718
|
+
* The render passes of a frame still in the pass ring, in their order, with their detail: the entity whose filter drew
|
|
16719
|
+
* each and its view's label, the filter kind, the draws, the target's format and resolution, whether it was a kept
|
|
16720
|
+
* picture's.
|
|
16070
16721
|
*
|
|
16071
16722
|
* @param ring - The ring.
|
|
16072
16723
|
* @param frame - The frame number.
|
|
16724
|
+
* @param total - The frame's draws (its `draws` column); NaN where draws are not counted.
|
|
16725
|
+
* @param viewOf - The label of an entity's view now, or `null` once it left.
|
|
16073
16726
|
* @returns The passes; empty when none was recorded or they were overwritten.
|
|
16074
16727
|
*/
|
|
16075
|
-
function passesOf$1(ring, frame) {
|
|
16728
|
+
function passesOf$1(ring, frame, total = NaN, viewOf = () => null) {
|
|
16076
16729
|
const { passes } = ring;
|
|
16077
16730
|
const found = [];
|
|
16078
16731
|
for (let age = 0; age < passes.size; age += 1) {
|
|
16079
16732
|
const slot = (passes.newest - age + passes.capacity) % passes.capacity;
|
|
16080
|
-
const
|
|
16081
|
-
if (
|
|
16082
|
-
if (
|
|
16083
|
-
const stencil =
|
|
16733
|
+
const at = cellU32(passes.frame, slot);
|
|
16734
|
+
if (at < frame) break;
|
|
16735
|
+
if (at !== frame) continue;
|
|
16736
|
+
const stencil = cellU8(passes.stencil, slot);
|
|
16737
|
+
const label = passes.label[slot] ?? null;
|
|
16738
|
+
const owner = cellU32(passes.owner, slot);
|
|
16739
|
+
const kind2 = cellU8(passes.kind2, slot);
|
|
16084
16740
|
found.unshift({
|
|
16085
|
-
index:
|
|
16086
|
-
kind: PASS_KINDS[
|
|
16087
|
-
label
|
|
16741
|
+
index: cellU8(passes.index, slot),
|
|
16742
|
+
kind: PASS_KINDS[cellU8(passes.kind, slot)] ?? "texture",
|
|
16743
|
+
label,
|
|
16088
16744
|
stencil: stencil === 255 ? null : stencil === 1,
|
|
16089
|
-
w:
|
|
16090
|
-
h:
|
|
16091
|
-
gpuMs: measured(
|
|
16745
|
+
w: cellU16(passes.width, slot),
|
|
16746
|
+
h: cellU16(passes.height, slot),
|
|
16747
|
+
gpuMs: measured(cellF32(passes.gpuMs, slot)),
|
|
16748
|
+
owner: owner === 0 ? null : owner,
|
|
16749
|
+
view: owner === 0 ? null : viewOf(owner),
|
|
16750
|
+
filter: kind2 === 0 ? null : ring.kindNames[kind2 - 1] ?? "other",
|
|
16751
|
+
draws: cellU16(passes.drawsAt, slot),
|
|
16752
|
+
format: passes.format[slot] ?? null,
|
|
16753
|
+
resolution: measured(cellF32(passes.resolution, slot)),
|
|
16754
|
+
kept: label?.startsWith(KEPT_PREFIX) === true
|
|
16092
16755
|
});
|
|
16093
16756
|
}
|
|
16757
|
+
splitDraws(found, total);
|
|
16094
16758
|
return found;
|
|
16095
16759
|
}
|
|
16096
16760
|
/**
|
|
@@ -16103,56 +16767,56 @@ function passesOf$1(ring, frame) {
|
|
|
16103
16767
|
function recordAt(ring, slot) {
|
|
16104
16768
|
const c = ring.columns;
|
|
16105
16769
|
const phase = slot * 6;
|
|
16106
|
-
const applies = measured(
|
|
16107
|
-
const uploads = measured(
|
|
16108
|
-
const kept = measured(
|
|
16109
|
-
return {
|
|
16110
|
-
frame:
|
|
16111
|
-
t: two(
|
|
16112
|
-
gapMs: measured(
|
|
16113
|
-
capMs: two(
|
|
16114
|
-
rateMs: two(
|
|
16115
|
-
budgetMs: two(
|
|
16116
|
-
skipped:
|
|
16117
|
-
startMs: two(
|
|
16770
|
+
const applies = measured(cellF32(c.filterApplies, slot));
|
|
16771
|
+
const uploads = measured(cellF32(c.uploads, slot));
|
|
16772
|
+
const kept = measured(cellF32(c.kept, slot));
|
|
16773
|
+
return {
|
|
16774
|
+
frame: cellU32(c.frame, slot),
|
|
16775
|
+
t: two(cellF64(c.t, slot)),
|
|
16776
|
+
gapMs: measured(cellF32(c.gapMs, slot)),
|
|
16777
|
+
capMs: two(cellF32(c.capMs, slot)),
|
|
16778
|
+
rateMs: two(cellF32(c.rateMs, slot)),
|
|
16779
|
+
budgetMs: two(cellF32(c.budgetMs, slot)),
|
|
16780
|
+
skipped: cellU8(c.skipped, slot),
|
|
16781
|
+
startMs: two(cellF32(c.startMs, slot)),
|
|
16118
16782
|
phases: {
|
|
16119
|
-
input: two(
|
|
16120
|
-
animate: two(
|
|
16121
|
-
layout: two(
|
|
16122
|
-
sync: two(
|
|
16123
|
-
signals: two(
|
|
16124
|
-
render: two(
|
|
16125
|
-
agent: two(
|
|
16126
|
-
},
|
|
16127
|
-
workMs: two(
|
|
16128
|
-
drawn:
|
|
16129
|
-
idle:
|
|
16130
|
-
stepped:
|
|
16131
|
-
reason: DEMAND_REASONS[
|
|
16132
|
-
passes: measured(
|
|
16133
|
-
stencilPasses: measured(
|
|
16134
|
-
draws: measured(
|
|
16783
|
+
input: two(cellF32(c.phaseMs, phase)),
|
|
16784
|
+
animate: two(cellF32(c.phaseMs, phase + 1)),
|
|
16785
|
+
layout: two(cellF32(c.phaseMs, phase + 2)),
|
|
16786
|
+
sync: two(cellF32(c.phaseMs, phase + 3)),
|
|
16787
|
+
signals: two(cellF32(c.phaseMs, phase + 4)),
|
|
16788
|
+
render: two(cellF32(c.phaseMs, phase + 5)),
|
|
16789
|
+
agent: two(cellF32(c.agentMs, slot))
|
|
16790
|
+
},
|
|
16791
|
+
workMs: two(cellF32(c.workMs, slot)),
|
|
16792
|
+
drawn: cellU8(c.drawn, slot) === 1,
|
|
16793
|
+
idle: cellU8(c.idle, slot) === 1,
|
|
16794
|
+
stepped: cellU8(c.stepped, slot) === 1,
|
|
16795
|
+
reason: DEMAND_REASONS[cellU8(c.reason, slot)] ?? null,
|
|
16796
|
+
passes: measured(cellF32(c.passes, slot)),
|
|
16797
|
+
stencilPasses: measured(cellF32(c.stencilPasses, slot)),
|
|
16798
|
+
draws: measured(cellF32(c.draws, slot)),
|
|
16135
16799
|
filters: applies === null ? null : {
|
|
16136
16800
|
applies,
|
|
16137
|
-
mpx: two(
|
|
16801
|
+
mpx: two(cellF32(c.filterMpx, slot)),
|
|
16138
16802
|
kinds: filterKindsAt(ring, slot)
|
|
16139
16803
|
},
|
|
16140
16804
|
uploads: uploads === null ? null : {
|
|
16141
16805
|
count: uploads,
|
|
16142
|
-
mb: two(
|
|
16806
|
+
mb: two(cellF32(c.uploadMb, slot))
|
|
16143
16807
|
},
|
|
16144
|
-
compiles: measured(
|
|
16145
|
-
marks: measured(
|
|
16146
|
-
rebuilds: measured(
|
|
16808
|
+
compiles: measured(cellF32(c.compiles, slot)),
|
|
16809
|
+
marks: measured(cellF32(c.marks, slot)),
|
|
16810
|
+
rebuilds: measured(cellF32(c.rebuilds, slot)),
|
|
16147
16811
|
systems: systemsAt(ring, slot),
|
|
16148
|
-
gpuMs: measured(
|
|
16149
|
-
gpuPartial:
|
|
16150
|
-
path: ring.paths[
|
|
16812
|
+
gpuMs: measured(cellF32(c.gpuMs, slot)),
|
|
16813
|
+
gpuPartial: cellU8(c.gpuPartial, slot) === 1,
|
|
16814
|
+
path: ring.paths[cellU16(c.path, slot)] ?? "…",
|
|
16151
16815
|
changed: changesAt(ring, slot),
|
|
16152
|
-
causes: namesOf$3(
|
|
16816
|
+
causes: namesOf$3(cellU8(c.causes, slot), PERF_CAUSES),
|
|
16153
16817
|
kept: kept === null ? null : {
|
|
16154
16818
|
pictures: kept,
|
|
16155
|
-
retakes:
|
|
16819
|
+
retakes: cellF32(c.retakes, slot)
|
|
16156
16820
|
}
|
|
16157
16821
|
};
|
|
16158
16822
|
}
|
|
@@ -16175,63 +16839,68 @@ function frameAt(ring, age) {
|
|
|
16175
16839
|
return recordAt(ring, (ring.newest - age + ring.capacity) % ring.capacity);
|
|
16176
16840
|
}
|
|
16177
16841
|
//#endregion
|
|
16178
|
-
//#region src/plugins/renderer/monitor/types.ts
|
|
16179
|
-
/**
|
|
16180
|
-
* Indices of the per-frame counters in `MonitorSinks.counts`. Not public.
|
|
16181
|
-
*/
|
|
16182
|
-
const COUNT = {
|
|
16183
|
-
passes: 0,
|
|
16184
|
-
stencilPasses: 1,
|
|
16185
|
-
filterApplies: 2,
|
|
16186
|
-
filterPixels: 3,
|
|
16187
|
-
uploads: 4,
|
|
16188
|
-
uploadBytes: 5,
|
|
16189
|
-
compiles: 6,
|
|
16190
|
-
marks: 7,
|
|
16191
|
-
rebuilds: 8
|
|
16192
|
-
};
|
|
16193
|
-
/**
|
|
16194
|
-
* What this application measures, as bits of `PerfState.measuring`. Not public.
|
|
16195
|
-
*/
|
|
16196
|
-
const MEASURE = {
|
|
16197
|
-
passes: 1,
|
|
16198
|
-
stencilPasses: 2,
|
|
16199
|
-
draws: 4,
|
|
16200
|
-
counted: 8,
|
|
16201
|
-
compiles: 16,
|
|
16202
|
-
gpu: 32
|
|
16203
|
-
};
|
|
16204
|
-
//#endregion
|
|
16205
16842
|
//#region src/plugins/renderer/monitor/compiles.ts
|
|
16206
16843
|
/**
|
|
16207
16844
|
* @file renderer/monitor — pipeline compiles of an agent build (R5). Pixi creates its WebGPU pipelines in a private
|
|
16208
16845
|
* method, so no subclass sees a creation; the count is taken one level lower, on GPU objects that belong to this
|
|
16209
16846
|
* application, never on a prototype and never on a Pixi object (OQ4): own properties on the engine's own device, and
|
|
16210
16847
|
* `linkProgram` on this application's WebGL context. The remover deletes the own property, so the prototype's method
|
|
16211
|
-
* shows through again. A restore attaches to the new device or context.
|
|
16848
|
+
* shows through again. A restore attaches to the new device or context. Each record also notes where it happened:
|
|
16849
|
+
* the pass of the frame, and the filter kind and its owner while a filter applies; a compile of a plain batch has
|
|
16850
|
+
* none. The flush turns the records into `shader` events.
|
|
16212
16851
|
*/
|
|
16213
16852
|
/**
|
|
16214
|
-
*
|
|
16215
|
-
*
|
|
16853
|
+
* Keeps one compile or shader module in the next of the 64 pre-made records: its frame, its label by reference, the
|
|
16854
|
+
* CPU time of the creation call, the part a frame waits for, and the pass, filter kind and owner of the moment.
|
|
16216
16855
|
*
|
|
16217
16856
|
* @param sinks - The sinks.
|
|
16218
16857
|
* @param frame - The frame running now.
|
|
16219
|
-
* @param label - The pipeline's label, or `null`.
|
|
16858
|
+
* @param label - The pipeline's or module's label, or `null`.
|
|
16220
16859
|
* @param ms - The time of the call.
|
|
16860
|
+
* @param module - True for a WebGPU shader module.
|
|
16221
16861
|
*/
|
|
16222
|
-
function
|
|
16862
|
+
function writeRecord(sinks, frame, label, ms, module) {
|
|
16223
16863
|
const ring = sinks.compiles;
|
|
16224
16864
|
const record = ring.records[ring.next];
|
|
16225
|
-
|
|
16865
|
+
const applying = sinks.filterDepth > 0;
|
|
16226
16866
|
ring.next = (ring.next + 1) % ring.records.length;
|
|
16227
16867
|
ring.size = Math.min(ring.size + 1, ring.records.length);
|
|
16868
|
+
ring.written += 1;
|
|
16228
16869
|
if (record === void 0) return;
|
|
16229
16870
|
record.frame = frame;
|
|
16230
16871
|
record.label = label;
|
|
16231
16872
|
record.ms = ms;
|
|
16873
|
+
record.module = module;
|
|
16874
|
+
record.pass = sinks.inFrame ? cellF64(sinks.counts, COUNT.passes) - 1 : -1;
|
|
16875
|
+
record.kind = applying ? sinks.filterKind + 1 : 0;
|
|
16876
|
+
record.owner = applying ? sinks.filterOwner : 0;
|
|
16232
16877
|
}
|
|
16233
16878
|
/**
|
|
16234
|
-
*
|
|
16879
|
+
* Counts one compile of the frame and keeps its record.
|
|
16880
|
+
*
|
|
16881
|
+
* @param sinks - The sinks.
|
|
16882
|
+
* @param frame - The frame running now.
|
|
16883
|
+
* @param label - The pipeline's label, or `null`.
|
|
16884
|
+
* @param ms - The time of the call.
|
|
16885
|
+
*/
|
|
16886
|
+
function recordCompile(sinks, frame, label, ms) {
|
|
16887
|
+
sinks.counts[COUNT.compiles] = cellF64(sinks.counts, COUNT.compiles) + 1;
|
|
16888
|
+
writeRecord(sinks, frame, label, ms, false);
|
|
16889
|
+
}
|
|
16890
|
+
/**
|
|
16891
|
+
* Keeps the record of one shader module: not a compile of the frame's count, a `shader` event of its own.
|
|
16892
|
+
*
|
|
16893
|
+
* @param sinks - The sinks.
|
|
16894
|
+
* @param frame - The frame running now.
|
|
16895
|
+
* @param label - The module's label, or `null`.
|
|
16896
|
+
* @param ms - The time of the call.
|
|
16897
|
+
*/
|
|
16898
|
+
function recordModule(sinks, frame, label, ms) {
|
|
16899
|
+
sinks.compiles.shaderModules += 1;
|
|
16900
|
+
writeRecord(sinks, frame, label, ms, true);
|
|
16901
|
+
}
|
|
16902
|
+
/**
|
|
16903
|
+
* The compiles of one frame still in the ring, oldest first; shader modules left out.
|
|
16235
16904
|
*
|
|
16236
16905
|
* @param sinks - The sinks.
|
|
16237
16906
|
* @param frame - The frame number.
|
|
@@ -16242,7 +16911,7 @@ function compilesOf(sinks, frame) {
|
|
|
16242
16911
|
const found = [];
|
|
16243
16912
|
for (let age = ring.size - 1; age >= 0; age -= 1) {
|
|
16244
16913
|
const record = ring.records[(ring.next - 1 - age + ring.records.length) % ring.records.length];
|
|
16245
|
-
if (record?.frame === frame) found.push({
|
|
16914
|
+
if (record?.frame === frame && !record.module) found.push({
|
|
16246
16915
|
label: record.label,
|
|
16247
16916
|
ms: record.ms
|
|
16248
16917
|
});
|
|
@@ -16250,6 +16919,40 @@ function compilesOf(sinks, frame) {
|
|
|
16250
16919
|
return found;
|
|
16251
16920
|
}
|
|
16252
16921
|
/**
|
|
16922
|
+
* The details of the `shader` events not sent yet, oldest first, from the records still in the ring (the 64 newest);
|
|
16923
|
+
* marks them sent. Built at the flush: it allocates.
|
|
16924
|
+
*
|
|
16925
|
+
* @param pc - The perf context.
|
|
16926
|
+
* @param viewOf - The label of an entity's view now, or `null`.
|
|
16927
|
+
* @returns One detail per compile or module.
|
|
16928
|
+
*/
|
|
16929
|
+
function takeShaderEvents(pc, viewOf) {
|
|
16930
|
+
const ring = pc.sinks.compiles;
|
|
16931
|
+
const { kindNames } = pc.sinks;
|
|
16932
|
+
const first = Math.max(ring.sent, ring.written - ring.records.length);
|
|
16933
|
+
const boot = pc.perf.firstDrawnFrame;
|
|
16934
|
+
const details = [];
|
|
16935
|
+
for (let sequence = first; sequence < ring.written; sequence += 1) {
|
|
16936
|
+
const record = ring.records[sequence % ring.records.length];
|
|
16937
|
+
if (record === void 0) continue;
|
|
16938
|
+
details.push({
|
|
16939
|
+
what: record.module ? SHADER_WHATS[1] : SHADER_WHATS[0],
|
|
16940
|
+
label: record.label,
|
|
16941
|
+
ms: roundTo(record.ms, 2),
|
|
16942
|
+
frame: record.frame,
|
|
16943
|
+
boot: Number.isNaN(boot) || record.frame <= boot,
|
|
16944
|
+
during: {
|
|
16945
|
+
pass: record.pass < 0 ? null : record.pass,
|
|
16946
|
+
filter: record.kind === 0 ? null : kindNames[record.kind - 1] ?? "other",
|
|
16947
|
+
owner: record.owner === 0 ? null : record.owner,
|
|
16948
|
+
view: record.owner === 0 ? null : viewOf(record.owner)
|
|
16949
|
+
}
|
|
16950
|
+
});
|
|
16951
|
+
}
|
|
16952
|
+
ring.sent = ring.written;
|
|
16953
|
+
return details;
|
|
16954
|
+
}
|
|
16955
|
+
/**
|
|
16253
16956
|
* Counts the render pipelines and the shader modules the engine's own device creates.
|
|
16254
16957
|
*
|
|
16255
16958
|
* @param pc - The perf context.
|
|
@@ -16280,14 +16983,17 @@ function wrapDevice(pc, device) {
|
|
|
16280
16983
|
configurable: true,
|
|
16281
16984
|
writable: true,
|
|
16282
16985
|
/**
|
|
16283
|
-
* Creates the shader module and
|
|
16986
|
+
* Creates the shader module and keeps its record: counted for the header and sent as a `shader` event, not a
|
|
16987
|
+
* compile of the frame.
|
|
16284
16988
|
*
|
|
16285
16989
|
* @param descriptor - The module descriptor.
|
|
16286
16990
|
* @returns The module.
|
|
16287
16991
|
*/
|
|
16288
16992
|
value: function countedModule(descriptor) {
|
|
16289
|
-
|
|
16290
|
-
|
|
16993
|
+
const started = pageNow();
|
|
16994
|
+
const made = createShaderModule.call(this, descriptor);
|
|
16995
|
+
recordModule(sinks, pc.perf.frame, descriptor.label ?? null, pageNow() - started);
|
|
16996
|
+
return made;
|
|
16291
16997
|
}
|
|
16292
16998
|
});
|
|
16293
16999
|
return () => {
|
|
@@ -16334,6 +17040,414 @@ function wrapGl(pc, gl) {
|
|
|
16334
17040
|
function attachCompileCounting(pc, target) {
|
|
16335
17041
|
return "createRenderPipeline" in target ? wrapDevice(pc, target) : wrapGl(pc, target);
|
|
16336
17042
|
}
|
|
17043
|
+
/**
|
|
17044
|
+
* Creates the estimate: no interval yet, no period.
|
|
17045
|
+
*
|
|
17046
|
+
* @returns The estimate.
|
|
17047
|
+
*/
|
|
17048
|
+
function createTickEstimate() {
|
|
17049
|
+
return {
|
|
17050
|
+
samples: /* @__PURE__ */ new Float32Array(9),
|
|
17051
|
+
sorted: /* @__PURE__ */ new Float32Array(9),
|
|
17052
|
+
size: 0,
|
|
17053
|
+
next: 0,
|
|
17054
|
+
periodMs: Float64Array.of(NaN)
|
|
17055
|
+
};
|
|
17056
|
+
}
|
|
17057
|
+
/**
|
|
17058
|
+
* Forgets every interval: the next frames start a new estimate.
|
|
17059
|
+
*
|
|
17060
|
+
* @param ticks - The estimate.
|
|
17061
|
+
*/
|
|
17062
|
+
function resetTicks(ticks) {
|
|
17063
|
+
ticks.size = 0;
|
|
17064
|
+
ticks.next = 0;
|
|
17065
|
+
ticks.periodMs[0] = NaN;
|
|
17066
|
+
}
|
|
17067
|
+
/**
|
|
17068
|
+
* Adds the tick interval of a frame and snaps the median of the last nine to a display rate: `periodMs[0]` becomes
|
|
17069
|
+
* that rate's period, or NaN while the median matches none. A stepped frame, a frame without a gap and a gap over
|
|
17070
|
+
* 50 ms (a hitch, a page away) add nothing. The median is taken by an insertion sort into `sorted`: nine numbers.
|
|
17071
|
+
*
|
|
17072
|
+
* @param ticks - The estimate.
|
|
17073
|
+
* @param measure - What `time` measured of the frame; read, never kept.
|
|
17074
|
+
*/
|
|
17075
|
+
function noteTicks(ticks, measure) {
|
|
17076
|
+
if (measure.stepped || Number.isNaN(measure.gapMs) || measure.gapMs > 50) return;
|
|
17077
|
+
const { samples, sorted } = ticks;
|
|
17078
|
+
samples[ticks.next] = measure.gapMs / (measure.skipped + 1);
|
|
17079
|
+
ticks.next = (ticks.next + 1) % 9;
|
|
17080
|
+
ticks.size = Math.min(ticks.size + 1, 9);
|
|
17081
|
+
for (let index = 0; index < ticks.size; index += 1) {
|
|
17082
|
+
const value = cellF32(samples, index);
|
|
17083
|
+
let place = index;
|
|
17084
|
+
while (place > 0 && cellF32(sorted, place - 1) > value) {
|
|
17085
|
+
sorted[place] = cellF32(sorted, place - 1);
|
|
17086
|
+
place -= 1;
|
|
17087
|
+
}
|
|
17088
|
+
sorted[place] = value;
|
|
17089
|
+
}
|
|
17090
|
+
const rate = displayIndexOf(cellF32(sorted, ticks.size >> 1));
|
|
17091
|
+
const hz = rate === -1 ? void 0 : DISPLAY_RATES[rate];
|
|
17092
|
+
ticks.periodMs[0] = hz === void 0 ? NaN : 1e3 / hz;
|
|
17093
|
+
}
|
|
17094
|
+
//#endregion
|
|
17095
|
+
//#region src/plugins/renderer/monitor/state.ts
|
|
17096
|
+
/** Compiles the compile ring keeps. */
|
|
17097
|
+
const COMPILE_SLOTS = 64;
|
|
17098
|
+
/** Long frames the long-frame ring keeps. */
|
|
17099
|
+
const LONG_FRAME_SLOTS = 64;
|
|
17100
|
+
/** Top scripts a long frame keeps. */
|
|
17101
|
+
const SCRIPT_SLOTS = 3;
|
|
17102
|
+
/** Rows the per-second system accumulators hold before they first grow. */
|
|
17103
|
+
const SYSTEM_ROWS = 16;
|
|
17104
|
+
/** Drawn frames a filtered view stands still, filtered in every one, before `filter-still` (U5). */
|
|
17105
|
+
const STILL_FRAMES = 60;
|
|
17106
|
+
/** Megapixels a still filtered view filters a frame before `filter-still`. */
|
|
17107
|
+
const STILL_MPX = .25;
|
|
17108
|
+
/** Render targets made and dropped a second that count as churn. */
|
|
17109
|
+
const CHURN_PER_SECOND = 4;
|
|
17110
|
+
/** Seconds in a row of churn before `target-churn`. */
|
|
17111
|
+
const CHURN_SECONDS = 3;
|
|
17112
|
+
/** A target made this soon after a pool trim is churn, ms. */
|
|
17113
|
+
const CHURN_AFTER_TRIM_MS = 1e3;
|
|
17114
|
+
/**
|
|
17115
|
+
* Creates an empty map. It lives in its own non-exported function because lint rule L5 refuses
|
|
17116
|
+
* a collection built inside an exported declaration.
|
|
17117
|
+
*
|
|
17118
|
+
* @returns An empty map.
|
|
17119
|
+
*/
|
|
17120
|
+
function emptyMap$7() {
|
|
17121
|
+
return /* @__PURE__ */ new Map();
|
|
17122
|
+
}
|
|
17123
|
+
/**
|
|
17124
|
+
* Creates an empty weak set. Same reason as `emptyMap`.
|
|
17125
|
+
*
|
|
17126
|
+
* @returns An empty weak set.
|
|
17127
|
+
*/
|
|
17128
|
+
function emptyWeakSet$1() {
|
|
17129
|
+
return /* @__PURE__ */ new WeakSet();
|
|
17130
|
+
}
|
|
17131
|
+
/**
|
|
17132
|
+
* Creates an empty set. Same reason as `emptyMap`.
|
|
17133
|
+
*
|
|
17134
|
+
* @returns An empty set.
|
|
17135
|
+
*/
|
|
17136
|
+
function emptySet$6() {
|
|
17137
|
+
return /* @__PURE__ */ new Set();
|
|
17138
|
+
}
|
|
17139
|
+
/**
|
|
17140
|
+
* Creates the compile ring: 64 pre-made records.
|
|
17141
|
+
*
|
|
17142
|
+
* @returns The ring, empty.
|
|
17143
|
+
*/
|
|
17144
|
+
function createCompileRing() {
|
|
17145
|
+
return {
|
|
17146
|
+
records: Array.from({ length: COMPILE_SLOTS }, () => ({
|
|
17147
|
+
frame: 0,
|
|
17148
|
+
label: null,
|
|
17149
|
+
ms: 0,
|
|
17150
|
+
module: false,
|
|
17151
|
+
pass: -1,
|
|
17152
|
+
kind: 0,
|
|
17153
|
+
owner: 0
|
|
17154
|
+
})),
|
|
17155
|
+
next: 0,
|
|
17156
|
+
size: 0,
|
|
17157
|
+
shaderModules: 0,
|
|
17158
|
+
written: 0,
|
|
17159
|
+
sent: 0
|
|
17160
|
+
};
|
|
17161
|
+
}
|
|
17162
|
+
/**
|
|
17163
|
+
* Creates the owner table of the filter applies: `OWNER_SLOTS` empty slots.
|
|
17164
|
+
*
|
|
17165
|
+
* @returns The table.
|
|
17166
|
+
*/
|
|
17167
|
+
function createOwnerTable() {
|
|
17168
|
+
return {
|
|
17169
|
+
ids: /* @__PURE__ */ new Float64Array(32),
|
|
17170
|
+
applies: /* @__PURE__ */ new Float64Array(32),
|
|
17171
|
+
pixels: /* @__PURE__ */ new Float64Array(32),
|
|
17172
|
+
kinds: /* @__PURE__ */ new Uint8Array(32)
|
|
17173
|
+
};
|
|
17174
|
+
}
|
|
17175
|
+
/**
|
|
17176
|
+
* Creates the count table: room for `COUNT_SLOTS` counts, none registered.
|
|
17177
|
+
*
|
|
17178
|
+
* @returns The table.
|
|
17179
|
+
*/
|
|
17180
|
+
function createCountTable() {
|
|
17181
|
+
return {
|
|
17182
|
+
names: [],
|
|
17183
|
+
reads: [],
|
|
17184
|
+
failed: emptySet$6()
|
|
17185
|
+
};
|
|
17186
|
+
}
|
|
17187
|
+
/**
|
|
17188
|
+
* Creates the problem accumulators with the thresholds of `renderer.monitor`, a key left out at its default: a game
|
|
17189
|
+
* that sets `monitor` replaces the whole object (shallow merge).
|
|
17190
|
+
*
|
|
17191
|
+
* @param config - `renderer.monitor`.
|
|
17192
|
+
* @returns The accumulators, no problem open.
|
|
17193
|
+
*/
|
|
17194
|
+
function createProblemState(config) {
|
|
17195
|
+
return {
|
|
17196
|
+
thresholds: {
|
|
17197
|
+
stillFrames: config.stillFrames ?? STILL_FRAMES,
|
|
17198
|
+
stillMpx: config.stillMpx ?? STILL_MPX,
|
|
17199
|
+
churnPerSecond: config.churnPerSecond ?? CHURN_PER_SECOND,
|
|
17200
|
+
churnSeconds: config.churnSeconds ?? CHURN_SECONDS,
|
|
17201
|
+
churnAfterTrimMs: config.churnAfterTrimMs ?? CHURN_AFTER_TRIM_MS
|
|
17202
|
+
},
|
|
17203
|
+
run: /* @__PURE__ */ new Float64Array(32),
|
|
17204
|
+
runSince: /* @__PURE__ */ new Float64Array(32),
|
|
17205
|
+
secFrames: /* @__PURE__ */ new Float64Array(32),
|
|
17206
|
+
secPixels: /* @__PURE__ */ new Float64Array(32),
|
|
17207
|
+
stillOpen: /* @__PURE__ */ new Uint8Array(32),
|
|
17208
|
+
secTargets: 0,
|
|
17209
|
+
idleCreate: false,
|
|
17210
|
+
trimCreate: false,
|
|
17211
|
+
createFrame: 0,
|
|
17212
|
+
trimAt: NaN,
|
|
17213
|
+
churnRun: 0,
|
|
17214
|
+
churnSince: 0,
|
|
17215
|
+
churnOpen: false,
|
|
17216
|
+
takes: [emptyMap$7(), emptyMap$7()],
|
|
17217
|
+
takesFrame: [0, 0],
|
|
17218
|
+
retakesOpen: emptySet$6()
|
|
17219
|
+
};
|
|
17220
|
+
}
|
|
17221
|
+
/**
|
|
17222
|
+
* Creates the long-frame ring: 64 pre-made records with three pre-made scripts each.
|
|
17223
|
+
*
|
|
17224
|
+
* @returns The ring, empty, observing nothing.
|
|
17225
|
+
*/
|
|
17226
|
+
function createLongFrameRing() {
|
|
17227
|
+
return {
|
|
17228
|
+
records: Array.from({ length: LONG_FRAME_SLOTS }, () => ({
|
|
17229
|
+
startMs: 0,
|
|
17230
|
+
durationMs: 0,
|
|
17231
|
+
blockingMs: NaN,
|
|
17232
|
+
renderStartMs: NaN,
|
|
17233
|
+
styleLayoutMs: NaN,
|
|
17234
|
+
scripts: Array.from({ length: SCRIPT_SLOTS }, () => ({
|
|
17235
|
+
invoker: "",
|
|
17236
|
+
type: "",
|
|
17237
|
+
source: "",
|
|
17238
|
+
fn: "",
|
|
17239
|
+
ms: 0,
|
|
17240
|
+
forcedLayoutMs: 0
|
|
17241
|
+
})),
|
|
17242
|
+
scriptCount: 0
|
|
17243
|
+
})),
|
|
17244
|
+
next: 0,
|
|
17245
|
+
size: 0,
|
|
17246
|
+
type: void 0,
|
|
17247
|
+
disconnect: void 0
|
|
17248
|
+
};
|
|
17249
|
+
}
|
|
17250
|
+
/**
|
|
17251
|
+
* Creates the sinks the counting classes write into. Its identity never changes; in a production
|
|
17252
|
+
* build it stays at zero.
|
|
17253
|
+
*
|
|
17254
|
+
* @param draws - The draw counter of the monitor state; a fresh one when left out.
|
|
17255
|
+
* @returns The sinks.
|
|
17256
|
+
*/
|
|
17257
|
+
function createSinks(draws) {
|
|
17258
|
+
return {
|
|
17259
|
+
draws: draws ?? {
|
|
17260
|
+
frame: 0,
|
|
17261
|
+
last: 0
|
|
17262
|
+
},
|
|
17263
|
+
counts: new Float64Array(Object.keys(COUNT).length),
|
|
17264
|
+
inFrame: false,
|
|
17265
|
+
filterDepth: 0,
|
|
17266
|
+
filterKind: 0,
|
|
17267
|
+
kindNames: ["other"],
|
|
17268
|
+
kindApplies: /* @__PURE__ */ new Float64Array(32),
|
|
17269
|
+
kindPixels: /* @__PURE__ */ new Float64Array(32),
|
|
17270
|
+
filterKinds: void 0,
|
|
17271
|
+
filterOwners: void 0,
|
|
17272
|
+
filterOwner: 0,
|
|
17273
|
+
owners: createOwnerTable(),
|
|
17274
|
+
compiles: createCompileRing(),
|
|
17275
|
+
passes: void 0,
|
|
17276
|
+
measuring: false,
|
|
17277
|
+
rebuildsWindow: 0
|
|
17278
|
+
};
|
|
17279
|
+
}
|
|
17280
|
+
/**
|
|
17281
|
+
* Creates the performance monitor of an agent build. Nothing large is allocated here: the ring
|
|
17282
|
+
* gets its capacity at the first attach, once per renderer state.
|
|
17283
|
+
*
|
|
17284
|
+
* @param config - `renderer.monitor`.
|
|
17285
|
+
* @param sinks - The sinks of the monitor state.
|
|
17286
|
+
* @returns The perf state.
|
|
17287
|
+
*/
|
|
17288
|
+
function createPerfState(config, sinks) {
|
|
17289
|
+
const events = createEventRing();
|
|
17290
|
+
return {
|
|
17291
|
+
budget: config.budgetMs,
|
|
17292
|
+
frames: config.frames,
|
|
17293
|
+
ring: createFrameRing(0, events, sinks.kindNames),
|
|
17294
|
+
scratch: /* @__PURE__ */ new Float32Array(0),
|
|
17295
|
+
staged: /* @__PURE__ */ new Float64Array(2),
|
|
17296
|
+
ticks: createTickEstimate(),
|
|
17297
|
+
rate: void 0,
|
|
17298
|
+
drawn: false,
|
|
17299
|
+
flags: 0,
|
|
17300
|
+
pathIds: emptyMap$7(),
|
|
17301
|
+
pathId: 0,
|
|
17302
|
+
restPath: void 0,
|
|
17303
|
+
produced: 0,
|
|
17304
|
+
measuring: 0,
|
|
17305
|
+
lastHeap: NaN,
|
|
17306
|
+
secSum: new Float64Array(SYSTEM_ROWS),
|
|
17307
|
+
secMax: new Float32Array(SYSTEM_ROWS),
|
|
17308
|
+
gapHist: /* @__PURE__ */ new Uint16Array(81),
|
|
17309
|
+
listenersSum: 0,
|
|
17310
|
+
events,
|
|
17311
|
+
display: void 0,
|
|
17312
|
+
pending: /* @__PURE__ */ new Float64Array(64),
|
|
17313
|
+
pendingCount: 0,
|
|
17314
|
+
lastFlushAt: NaN,
|
|
17315
|
+
agentMs: 0,
|
|
17316
|
+
agentStart: NaN,
|
|
17317
|
+
frame: 0,
|
|
17318
|
+
listeners: [],
|
|
17319
|
+
waiting: [],
|
|
17320
|
+
failed: emptyWeakSet$1(),
|
|
17321
|
+
shell: "unknown",
|
|
17322
|
+
startup: void 0,
|
|
17323
|
+
firstDrawnAt: NaN,
|
|
17324
|
+
firstDrawnFrame: NaN,
|
|
17325
|
+
counts: createCountTable(),
|
|
17326
|
+
problems: createProblemState(config),
|
|
17327
|
+
timer: void 0,
|
|
17328
|
+
longFrames: createLongFrameRing(),
|
|
17329
|
+
detach: [],
|
|
17330
|
+
attached: false
|
|
17331
|
+
};
|
|
17332
|
+
}
|
|
17333
|
+
/**
|
|
17334
|
+
* Creates the initial monitor state: no frame seen, an empty window, no capture waiting, no draw
|
|
17335
|
+
* counted, zeroed sinks, and in an agent build the performance monitor.
|
|
17336
|
+
*
|
|
17337
|
+
* @param config - `renderer.monitor`.
|
|
17338
|
+
* @returns The monitor branch of the plugin state.
|
|
17339
|
+
*/
|
|
17340
|
+
function createMonitorState(config) {
|
|
17341
|
+
const draws = {
|
|
17342
|
+
frame: 0,
|
|
17343
|
+
last: 0
|
|
17344
|
+
};
|
|
17345
|
+
const sinks = createSinks(draws);
|
|
17346
|
+
return {
|
|
17347
|
+
frameStart: void 0,
|
|
17348
|
+
lastStart: void 0,
|
|
17349
|
+
windowMs: 0,
|
|
17350
|
+
intervals: 0,
|
|
17351
|
+
workMs: 0,
|
|
17352
|
+
frames: 0,
|
|
17353
|
+
fps: 0,
|
|
17354
|
+
drawnIntervals: 0,
|
|
17355
|
+
drawnFps: 0,
|
|
17356
|
+
lastDrawn: false,
|
|
17357
|
+
frameMs: 0,
|
|
17358
|
+
redrawsOpen: 0,
|
|
17359
|
+
rebuildsOpen: 0,
|
|
17360
|
+
redraws: 0,
|
|
17361
|
+
rebuilds: 0,
|
|
17362
|
+
captures: [],
|
|
17363
|
+
draws,
|
|
17364
|
+
sinks,
|
|
17365
|
+
perf: typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? createPerfState(config, sinks) : void 0,
|
|
17366
|
+
passes: {
|
|
17367
|
+
last: 0,
|
|
17368
|
+
open: 0,
|
|
17369
|
+
max: 0
|
|
17370
|
+
},
|
|
17371
|
+
passDetach: []
|
|
17372
|
+
};
|
|
17373
|
+
}
|
|
17374
|
+
//#endregion
|
|
17375
|
+
//#region src/plugins/renderer/monitor/counts.ts
|
|
17376
|
+
/**
|
|
17377
|
+
* The error of a register when the table is full.
|
|
17378
|
+
*
|
|
17379
|
+
* @returns The error.
|
|
17380
|
+
*/
|
|
17381
|
+
function fullTable() {
|
|
17382
|
+
return /* @__PURE__ */ new Error(`[game] Monitor count table is full (16).\n Remove a registered count or raise the table size.`);
|
|
17383
|
+
}
|
|
17384
|
+
/**
|
|
17385
|
+
* Registers a count, or gives a registered name its new reader.
|
|
17386
|
+
*
|
|
17387
|
+
* @param table - The count table.
|
|
17388
|
+
* @param name - The count's name.
|
|
17389
|
+
* @param read - Answers the count now.
|
|
17390
|
+
* @returns The remover; a second call, or a call after the name got another reader, does nothing.
|
|
17391
|
+
* @throws {Error} When the table holds `COUNT_SLOTS` other counts.
|
|
17392
|
+
*/
|
|
17393
|
+
function registerCount(table, name, read) {
|
|
17394
|
+
const at = table.names.indexOf(name);
|
|
17395
|
+
if (at !== -1) table.reads[at] = read;
|
|
17396
|
+
else if (table.names.length >= 16) throw fullTable();
|
|
17397
|
+
else {
|
|
17398
|
+
table.names.push(name);
|
|
17399
|
+
table.reads.push(read);
|
|
17400
|
+
}
|
|
17401
|
+
return () => {
|
|
17402
|
+
const index = table.names.indexOf(name);
|
|
17403
|
+
if (index === -1 || table.reads[index] !== read) return;
|
|
17404
|
+
table.names.splice(index, 1);
|
|
17405
|
+
table.reads.splice(index, 1);
|
|
17406
|
+
};
|
|
17407
|
+
}
|
|
17408
|
+
/**
|
|
17409
|
+
* Forgets every registered count: the renderer stopped.
|
|
17410
|
+
*
|
|
17411
|
+
* @param table - The count table.
|
|
17412
|
+
*/
|
|
17413
|
+
function clearCounts(table) {
|
|
17414
|
+
table.names.length = 0;
|
|
17415
|
+
table.reads.length = 0;
|
|
17416
|
+
table.failed.clear();
|
|
17417
|
+
}
|
|
17418
|
+
/**
|
|
17419
|
+
* The live object counts now: the five the renderer reads itself, then each registered count by its name. A reader
|
|
17420
|
+
* that throws is left out and warned about once.
|
|
17421
|
+
*
|
|
17422
|
+
* @param pc - The perf context.
|
|
17423
|
+
* @returns A fresh object.
|
|
17424
|
+
*/
|
|
17425
|
+
function readCounts(pc) {
|
|
17426
|
+
const { world, clock, time } = pc.ctx.deps;
|
|
17427
|
+
const table = pc.perf.counts;
|
|
17428
|
+
const counts = {
|
|
17429
|
+
entities: world.ecs.count(),
|
|
17430
|
+
views: pc.ctx.state.sync.views.size,
|
|
17431
|
+
timers: clock.pending(),
|
|
17432
|
+
frameListeners: time.frameListeners(),
|
|
17433
|
+
clockListeners: clock.listeners()
|
|
17434
|
+
};
|
|
17435
|
+
for (let index = 0; index < table.names.length; index += 1) {
|
|
17436
|
+
const name = table.names[index] ?? "";
|
|
17437
|
+
try {
|
|
17438
|
+
counts[name] = table.reads[index]?.() ?? 0;
|
|
17439
|
+
} catch (error) {
|
|
17440
|
+
if (!table.failed.has(name)) {
|
|
17441
|
+
table.failed.add(name);
|
|
17442
|
+
pc.ctx.log.warn("renderer: a monitor count failed", {
|
|
17443
|
+
name,
|
|
17444
|
+
error
|
|
17445
|
+
});
|
|
17446
|
+
}
|
|
17447
|
+
}
|
|
17448
|
+
}
|
|
17449
|
+
return counts;
|
|
17450
|
+
}
|
|
16337
17451
|
//#endregion
|
|
16338
17452
|
//#region src/plugins/renderer/monitor/log.ts
|
|
16339
17453
|
/** What follows the Android model in a user agent: `…; SM-A266B Build/…`. */
|
|
@@ -16345,7 +17459,7 @@ const UA_WEBVIEW = /; wv\)/;
|
|
|
16345
17459
|
/** Bytes of one KiB. */
|
|
16346
17460
|
const BYTES_PER_KB = 1024;
|
|
16347
17461
|
/**
|
|
16348
|
-
* Spec 10's build id of the page: `"debug-20261014T153012Z"
|
|
17462
|
+
* Spec 10's build id of the page: `"debug-20261014T153012Z"` or `"dev"`; never a version string.
|
|
16349
17463
|
*
|
|
16350
17464
|
* @returns The id, or `null` on a page without one.
|
|
16351
17465
|
*/
|
|
@@ -16353,6 +17467,15 @@ function buildId() {
|
|
|
16353
17467
|
return typeof __MOKU_GAME_BUILD__ === "string" ? __MOKU_GAME_BUILD__ : null;
|
|
16354
17468
|
}
|
|
16355
17469
|
/**
|
|
17470
|
+
* The bundle identifier the build wrote into the page (`__MOKU_GAME_APP__`), the `.debug` suffix of a native debug
|
|
17471
|
+
* build included.
|
|
17472
|
+
*
|
|
17473
|
+
* @returns The id, or `null` on a page without one.
|
|
17474
|
+
*/
|
|
17475
|
+
function appId() {
|
|
17476
|
+
return typeof __MOKU_GAME_APP__ === "string" ? __MOKU_GAME_APP__ : null;
|
|
17477
|
+
}
|
|
17478
|
+
/**
|
|
16356
17479
|
* Freezes a line and every object and array in it, so a listener may keep it after it returns.
|
|
16357
17480
|
*
|
|
16358
17481
|
* @param value - The line, or a part of it.
|
|
@@ -16486,6 +17609,7 @@ function measuredOf(pc) {
|
|
|
16486
17609
|
if (heapMb() !== null) list.push("heap");
|
|
16487
17610
|
if (pc.ctx.deps.world.systemTimes() !== void 0) list.push("systems");
|
|
16488
17611
|
list.push("kept");
|
|
17612
|
+
if ((bits & MEASURE.targets) !== 0) list.push("targets");
|
|
16489
17613
|
return list;
|
|
16490
17614
|
}
|
|
16491
17615
|
/**
|
|
@@ -16500,14 +17624,14 @@ function capsOf(ring) {
|
|
|
16500
17624
|
let idleFps = 0;
|
|
16501
17625
|
for (let age = 0; age < ring.size && (maxFps === 0 || idleFps === 0); age += 1) {
|
|
16502
17626
|
const slot = (ring.newest - age + ring.capacity) % ring.capacity;
|
|
16503
|
-
const fps = Math.round(1e3 /
|
|
16504
|
-
if (
|
|
17627
|
+
const fps = Math.round(1e3 / cellF32(c.capMs, slot));
|
|
17628
|
+
if (cellU8(c.idle, slot) === 1) idleFps ||= fps;
|
|
16505
17629
|
else maxFps ||= fps;
|
|
16506
17630
|
}
|
|
16507
17631
|
return {
|
|
16508
17632
|
maxFps,
|
|
16509
17633
|
idleFps,
|
|
16510
|
-
budgetMs: ring.size === 0 ? 0 :
|
|
17634
|
+
budgetMs: ring.size === 0 ? 0 : cellF32(c.budgetMs, ring.newest)
|
|
16511
17635
|
};
|
|
16512
17636
|
}
|
|
16513
17637
|
/**
|
|
@@ -16584,7 +17708,9 @@ function headerLine(pc, thermal) {
|
|
|
16584
17708
|
budget,
|
|
16585
17709
|
frames: pc.perf.ring.capacity
|
|
16586
17710
|
},
|
|
16587
|
-
measured: measuredOf(pc)
|
|
17711
|
+
measured: measuredOf(pc),
|
|
17712
|
+
app: { id: appId() },
|
|
17713
|
+
debug: { overdraw: pc.ctx.state.sync.debug.overdraw }
|
|
16588
17714
|
};
|
|
16589
17715
|
}
|
|
16590
17716
|
/**
|
|
@@ -16611,7 +17737,7 @@ function meanMax(sum, count, max) {
|
|
|
16611
17737
|
*/
|
|
16612
17738
|
function rank(sorted, count, quantile) {
|
|
16613
17739
|
if (count === 0) return 0;
|
|
16614
|
-
return ms(
|
|
17740
|
+
return ms(cellF32(sorted, Math.min(count - 1, Math.max(0, Math.ceil(quantile * count) - 1))));
|
|
16615
17741
|
}
|
|
16616
17742
|
/**
|
|
16617
17743
|
* The slots of a window of the ring, newest first: the frames whose start lies in `[start, end)`.
|
|
@@ -16624,9 +17750,9 @@ function rank(sorted, count, quantile) {
|
|
|
16624
17750
|
function windowOf$1(ring, start, end) {
|
|
16625
17751
|
const c = ring.columns;
|
|
16626
17752
|
let first = 0;
|
|
16627
|
-
while (first < ring.size &&
|
|
17753
|
+
while (first < ring.size && cellF64(c.t, slotAt(ring, first)) >= end) first += 1;
|
|
16628
17754
|
let count = 0;
|
|
16629
|
-
while (first + count < ring.size &&
|
|
17755
|
+
while (first + count < ring.size && cellF64(c.t, slotAt(ring, first + count)) >= start) count += 1;
|
|
16630
17756
|
return {
|
|
16631
17757
|
first,
|
|
16632
17758
|
count
|
|
@@ -16643,20 +17769,21 @@ function slotAt(ring, age) {
|
|
|
16643
17769
|
return (ring.newest - age + ring.capacity) % ring.capacity;
|
|
16644
17770
|
}
|
|
16645
17771
|
/**
|
|
16646
|
-
* Sorts the values a window
|
|
17772
|
+
* Sorts the values of one column over a window into the scratch array and answers its spread. The column, not a
|
|
17773
|
+
* reader closure: a closure's double comes back boxed from its call, once for every frame of the window.
|
|
16647
17774
|
*
|
|
16648
17775
|
* @param pc - The perf context.
|
|
16649
17776
|
* @param window - The window.
|
|
16650
17777
|
* @param window.first - Its first age.
|
|
16651
17778
|
* @param window.count - Its frames.
|
|
16652
|
-
* @param
|
|
17779
|
+
* @param column - A `Float32Array` column of the ring; NaN leaves the frame out.
|
|
16653
17780
|
* @returns The values kept, and the scratch view sorted over them.
|
|
16654
17781
|
*/
|
|
16655
|
-
function sortedOf(pc, window,
|
|
17782
|
+
function sortedOf(pc, window, column) {
|
|
16656
17783
|
const { ring, scratch } = pc.perf;
|
|
16657
17784
|
let count = 0;
|
|
16658
17785
|
for (let age = window.first; age < window.first + window.count; age += 1) {
|
|
16659
|
-
const value =
|
|
17786
|
+
const value = cellF32(column, slotAt(ring, age));
|
|
16660
17787
|
if (!Number.isNaN(value)) {
|
|
16661
17788
|
scratch[count] = value;
|
|
16662
17789
|
count += 1;
|
|
@@ -16675,8 +17802,8 @@ function sortedOf(pc, window, read) {
|
|
|
16675
17802
|
* @param value - The phase's ms.
|
|
16676
17803
|
*/
|
|
16677
17804
|
function addPhase(totals, index, value) {
|
|
16678
|
-
totals.phaseSum[index] =
|
|
16679
|
-
totals.phaseMax[index] = Math.max(
|
|
17805
|
+
totals.phaseSum[index] = cellF64(totals.phaseSum, index) + value;
|
|
17806
|
+
totals.phaseMax[index] = Math.max(cellF64(totals.phaseMax, index), value);
|
|
16680
17807
|
}
|
|
16681
17808
|
/**
|
|
16682
17809
|
* Adds one frame of a window: its phases, its counts, its gap. A missed gap is a frame with the `gap` cause: the
|
|
@@ -16687,22 +17814,24 @@ function addPhase(totals, index, value) {
|
|
|
16687
17814
|
* @param slot - The frame's slot.
|
|
16688
17815
|
*/
|
|
16689
17816
|
function addFrame(totals, c, slot) {
|
|
16690
|
-
const agent =
|
|
16691
|
-
const missed = (
|
|
16692
|
-
addPhase(totals, 0,
|
|
16693
|
-
for (let index = 0; index < 6; index += 1) addPhase(totals, index + 1,
|
|
17817
|
+
const agent = cellF32(c.agentMs, slot);
|
|
17818
|
+
const missed = (cellU8(c.causes, slot) & CAUSE.gap) !== 0;
|
|
17819
|
+
addPhase(totals, 0, cellF32(c.startMs, slot));
|
|
17820
|
+
for (let index = 0; index < 6; index += 1) addPhase(totals, index + 1, cellF32(c.phaseMs, slot * 6 + index));
|
|
16694
17821
|
addPhase(totals, 7, agent);
|
|
16695
|
-
totals.idle +=
|
|
16696
|
-
totals.overBudget +=
|
|
16697
|
-
totals.spikes +=
|
|
17822
|
+
totals.idle += cellU8(c.idle, slot);
|
|
17823
|
+
totals.overBudget += cellF32(c.workMs, slot) - agent > cellF32(c.budgetMs, slot) ? 1 : 0;
|
|
17824
|
+
totals.spikes += cellU8(c.causes, slot) === 0 ? 0 : 1;
|
|
16698
17825
|
totals.missed += missed ? 1 : 0;
|
|
16699
|
-
totals.missedDrawn += missed &&
|
|
16700
|
-
totals.skipped +=
|
|
16701
|
-
totals.uploads +=
|
|
16702
|
-
totals.uploadMb +=
|
|
16703
|
-
totals.compiles +=
|
|
16704
|
-
totals.rebuilds +=
|
|
16705
|
-
totals.retakes +=
|
|
17826
|
+
totals.missedDrawn += missed && cellU8(c.drawn, slot) === 1 ? 1 : 0;
|
|
17827
|
+
totals.skipped += cellU8(c.skipped, slot);
|
|
17828
|
+
totals.uploads += cellF32(c.uploads, slot);
|
|
17829
|
+
totals.uploadMb += cellF32(c.uploadMb, slot);
|
|
17830
|
+
totals.compiles += cellF32(c.compiles, slot);
|
|
17831
|
+
totals.rebuilds += cellF32(c.rebuilds, slot);
|
|
17832
|
+
totals.retakes += cellF32(c.retakes, slot);
|
|
17833
|
+
totals.targetsCreated += cellF32(c.targetsCreated, slot);
|
|
17834
|
+
totals.targetsDestroyed += cellF32(c.targetsDestroyed, slot);
|
|
16706
17835
|
}
|
|
16707
17836
|
/**
|
|
16708
17837
|
* Adds what only a drawn frame has: its passes, draws and filters.
|
|
@@ -16713,15 +17842,15 @@ function addFrame(totals, c, slot) {
|
|
|
16713
17842
|
*/
|
|
16714
17843
|
function addDrawn(totals, c, slot) {
|
|
16715
17844
|
totals.drawn += 1;
|
|
16716
|
-
totals.passes +=
|
|
16717
|
-
totals.passesMax = Math.max(totals.passesMax,
|
|
16718
|
-
totals.stencil +=
|
|
16719
|
-
totals.draws +=
|
|
16720
|
-
totals.drawsMax = Math.max(totals.drawsMax,
|
|
16721
|
-
totals.applies +=
|
|
16722
|
-
totals.appliesMax = Math.max(totals.appliesMax,
|
|
16723
|
-
totals.mpx +=
|
|
16724
|
-
totals.keptMax = Math.max(totals.keptMax,
|
|
17845
|
+
totals.passes += cellF32(c.passes, slot);
|
|
17846
|
+
totals.passesMax = Math.max(totals.passesMax, cellF32(c.passes, slot));
|
|
17847
|
+
totals.stencil += cellF32(c.stencilPasses, slot);
|
|
17848
|
+
totals.draws += cellF32(c.draws, slot);
|
|
17849
|
+
totals.drawsMax = Math.max(totals.drawsMax, cellF32(c.draws, slot));
|
|
17850
|
+
totals.applies += cellF32(c.filterApplies, slot);
|
|
17851
|
+
totals.appliesMax = Math.max(totals.appliesMax, cellF32(c.filterApplies, slot));
|
|
17852
|
+
totals.mpx += cellF32(c.filterMpx, slot);
|
|
17853
|
+
totals.keptMax = Math.max(totals.keptMax, cellF32(c.kept, slot));
|
|
16725
17854
|
}
|
|
16726
17855
|
/**
|
|
16727
17856
|
* Adds up the frames of a window.
|
|
@@ -16758,12 +17887,14 @@ function totalsOf(pc, window) {
|
|
|
16758
17887
|
compiles: 0,
|
|
16759
17888
|
rebuilds: 0,
|
|
16760
17889
|
keptMax: 0,
|
|
16761
|
-
retakes: 0
|
|
17890
|
+
retakes: 0,
|
|
17891
|
+
targetsCreated: 0,
|
|
17892
|
+
targetsDestroyed: 0
|
|
16762
17893
|
};
|
|
16763
17894
|
for (let age = window.first; age < window.first + window.count; age += 1) {
|
|
16764
17895
|
const slot = slotAt(ring, age);
|
|
16765
17896
|
addFrame(totals, c, slot);
|
|
16766
|
-
if (
|
|
17897
|
+
if (cellU8(c.drawn, slot) === 1) addDrawn(totals, c, slot);
|
|
16767
17898
|
}
|
|
16768
17899
|
return totals;
|
|
16769
17900
|
}
|
|
@@ -16780,8 +17911,8 @@ function systemsOfSecond(pc, frames) {
|
|
|
16780
17911
|
const { secSum, secMax } = pc.perf;
|
|
16781
17912
|
return timing.names.slice(0, secSum.length).map((name, index) => ({
|
|
16782
17913
|
name,
|
|
16783
|
-
mean: frames === 0 ? 0 :
|
|
16784
|
-
max:
|
|
17914
|
+
mean: frames === 0 ? 0 : cellF64(secSum, index) / frames,
|
|
17915
|
+
max: cellF32(secMax, index)
|
|
16785
17916
|
})).filter((system) => system.mean > 0).toSorted((first, second) => second.mean - first.mean).slice(0, 5).map((system) => ({
|
|
16786
17917
|
name: system.name,
|
|
16787
17918
|
mean: ms(system.mean),
|
|
@@ -16837,7 +17968,7 @@ function perDrawn(sum, drawn) {
|
|
|
16837
17968
|
*/
|
|
16838
17969
|
function gpuOfSecond(pc, window) {
|
|
16839
17970
|
const { columns } = pc.perf.ring;
|
|
16840
|
-
const gpu = sortedOf(pc, window,
|
|
17971
|
+
const gpu = sortedOf(pc, window, columns.gpuMs);
|
|
16841
17972
|
return {
|
|
16842
17973
|
p50: rank(gpu.sorted, gpu.count, .5),
|
|
16843
17974
|
p90: rank(gpu.sorted, gpu.count, .9),
|
|
@@ -16877,24 +18008,24 @@ function sampleLine(pc, start, end) {
|
|
|
16877
18008
|
const totals = totalsOf(pc, window);
|
|
16878
18009
|
const bits = perf.measuring;
|
|
16879
18010
|
const counted = (bits & MEASURE.counted) !== 0;
|
|
16880
|
-
const work = sortedOf(pc, window,
|
|
18011
|
+
const work = sortedOf(pc, window, c.workMs);
|
|
16881
18012
|
const workP50 = rank(work.sorted, work.count, .5);
|
|
16882
18013
|
const workP90 = rank(work.sorted, work.count, .9);
|
|
16883
18014
|
const workMax = rank(work.sorted, work.count, 1);
|
|
16884
|
-
const gaps = sortedOf(pc, window,
|
|
18015
|
+
const gaps = sortedOf(pc, window, c.gapMs);
|
|
16885
18016
|
const gapP50 = rank(gaps.sorted, gaps.count, .5);
|
|
16886
18017
|
const gapP90 = rank(gaps.sorted, gaps.count, .9);
|
|
16887
18018
|
const gapP99 = rank(gaps.sorted, gaps.count, .99);
|
|
16888
18019
|
const usage = deps.host.textures();
|
|
16889
18020
|
const counts = deps.sync.counts();
|
|
16890
18021
|
const drawn = totals.drawn;
|
|
16891
|
-
const phase = (index) => meanMax(
|
|
18022
|
+
const phase = (index) => meanMax(cellF64(totals.phaseSum, index), frames, cellF64(totals.phaseMax, index));
|
|
16892
18023
|
return {
|
|
16893
18024
|
v: 1,
|
|
16894
18025
|
kind: "sample",
|
|
16895
18026
|
t: epochOf(end),
|
|
16896
18027
|
build: buildId(),
|
|
16897
|
-
frame: frames === 0 ? 0 :
|
|
18028
|
+
frame: frames === 0 ? 0 : cellU32(c.frame, slotAt(ring, window.first)),
|
|
16898
18029
|
windowMs: ms(end - start),
|
|
16899
18030
|
path: ring.paths[perf.pathId] ?? "…",
|
|
16900
18031
|
ticks: frames + totals.skipped,
|
|
@@ -16962,7 +18093,12 @@ function sampleLine(pc, start, end) {
|
|
|
16962
18093
|
pooled: counts.pooled,
|
|
16963
18094
|
resolution: deps.host.resolution(),
|
|
16964
18095
|
monitorMs: frames === 0 ? 0 : roundTo(perf.listenersSum / frames, 3),
|
|
16965
|
-
spikes: totals.spikes
|
|
18096
|
+
spikes: totals.spikes,
|
|
18097
|
+
counts: readCounts(pc),
|
|
18098
|
+
targets: (bits & MEASURE.targets) === 0 ? null : {
|
|
18099
|
+
created: totals.targetsCreated,
|
|
18100
|
+
destroyed: totals.targetsDestroyed
|
|
18101
|
+
}
|
|
16966
18102
|
};
|
|
16967
18103
|
}
|
|
16968
18104
|
/**
|
|
@@ -16989,7 +18125,7 @@ function spikeLine(pc, slot) {
|
|
|
16989
18125
|
ms: ms(compile.ms)
|
|
16990
18126
|
})),
|
|
16991
18127
|
filters: record.filters?.kinds ?? [],
|
|
16992
|
-
passes: passesOf$1(ring, record.frame),
|
|
18128
|
+
passes: passesOf$1(ring, record.frame, cellF32(ring.columns.draws, slot), (entity) => viewLabel(pc, entity)),
|
|
16993
18129
|
longFrames: longFramesIn(longFrames, start, record.t + record.workMs).map((entry) => entryOf(entry))
|
|
16994
18130
|
};
|
|
16995
18131
|
}
|
|
@@ -17066,10 +18202,10 @@ function framesOver(ring, record) {
|
|
|
17066
18202
|
let last = NaN;
|
|
17067
18203
|
for (let age = ring.size - 1; age >= 0; age -= 1) {
|
|
17068
18204
|
const slot = slotAt(ring, age);
|
|
17069
|
-
const t =
|
|
17070
|
-
if (t < end && t +
|
|
17071
|
-
if (Number.isNaN(first)) first =
|
|
17072
|
-
last =
|
|
18205
|
+
const t = cellF64(c.t, slot);
|
|
18206
|
+
if (t < end && t + cellF32(c.workMs, slot) > record.startMs) {
|
|
18207
|
+
if (Number.isNaN(first)) first = cellU32(c.frame, slot);
|
|
18208
|
+
last = cellU32(c.frame, slot);
|
|
17073
18209
|
}
|
|
17074
18210
|
}
|
|
17075
18211
|
return Number.isNaN(first) ? null : [first, last];
|
|
@@ -17200,248 +18336,6 @@ function buildStartup(pc) {
|
|
|
17200
18336
|
}
|
|
17201
18337
|
};
|
|
17202
18338
|
}
|
|
17203
|
-
/**
|
|
17204
|
-
* Creates the estimate: no interval yet, no period.
|
|
17205
|
-
*
|
|
17206
|
-
* @returns The estimate.
|
|
17207
|
-
*/
|
|
17208
|
-
function createTickEstimate() {
|
|
17209
|
-
return {
|
|
17210
|
-
samples: /* @__PURE__ */ new Float32Array(9),
|
|
17211
|
-
sorted: /* @__PURE__ */ new Float32Array(9),
|
|
17212
|
-
size: 0,
|
|
17213
|
-
next: 0,
|
|
17214
|
-
periodMs: Float64Array.of(NaN)
|
|
17215
|
-
};
|
|
17216
|
-
}
|
|
17217
|
-
/**
|
|
17218
|
-
* Forgets every interval: the next frames start a new estimate.
|
|
17219
|
-
*
|
|
17220
|
-
* @param ticks - The estimate.
|
|
17221
|
-
*/
|
|
17222
|
-
function resetTicks(ticks) {
|
|
17223
|
-
ticks.size = 0;
|
|
17224
|
-
ticks.next = 0;
|
|
17225
|
-
ticks.periodMs[0] = NaN;
|
|
17226
|
-
}
|
|
17227
|
-
/**
|
|
17228
|
-
* Adds the tick interval of a frame and snaps the median of the last nine to a display rate: `periodMs[0]` becomes
|
|
17229
|
-
* that rate's period, or NaN while the median matches none. A stepped frame, a frame without a gap and a gap over
|
|
17230
|
-
* 50 ms (a hitch, a page away) add nothing. The median is taken by an insertion sort into `sorted`: nine numbers.
|
|
17231
|
-
*
|
|
17232
|
-
* @param ticks - The estimate.
|
|
17233
|
-
* @param measure - What `time` measured of the frame; read, never kept.
|
|
17234
|
-
*/
|
|
17235
|
-
function noteTicks(ticks, measure) {
|
|
17236
|
-
if (measure.stepped || Number.isNaN(measure.gapMs) || measure.gapMs > 50) return;
|
|
17237
|
-
const { samples, sorted } = ticks;
|
|
17238
|
-
samples[ticks.next] = measure.gapMs / (measure.skipped + 1);
|
|
17239
|
-
ticks.next = (ticks.next + 1) % 9;
|
|
17240
|
-
ticks.size = Math.min(ticks.size + 1, 9);
|
|
17241
|
-
for (let index = 0; index < ticks.size; index += 1) {
|
|
17242
|
-
const value = cell(samples, index);
|
|
17243
|
-
let place = index;
|
|
17244
|
-
while (place > 0 && cell(sorted, place - 1) > value) {
|
|
17245
|
-
sorted[place] = cell(sorted, place - 1);
|
|
17246
|
-
place -= 1;
|
|
17247
|
-
}
|
|
17248
|
-
sorted[place] = value;
|
|
17249
|
-
}
|
|
17250
|
-
const rate = displayIndexOf(cell(sorted, ticks.size >> 1));
|
|
17251
|
-
const hz = rate === -1 ? void 0 : DISPLAY_RATES[rate];
|
|
17252
|
-
ticks.periodMs[0] = hz === void 0 ? NaN : 1e3 / hz;
|
|
17253
|
-
}
|
|
17254
|
-
//#endregion
|
|
17255
|
-
//#region src/plugins/renderer/monitor/state.ts
|
|
17256
|
-
/** Compiles the compile ring keeps. */
|
|
17257
|
-
const COMPILE_SLOTS = 64;
|
|
17258
|
-
/** Long frames the long-frame ring keeps. */
|
|
17259
|
-
const LONG_FRAME_SLOTS = 64;
|
|
17260
|
-
/** Top scripts a long frame keeps. */
|
|
17261
|
-
const SCRIPT_SLOTS = 3;
|
|
17262
|
-
/** Rows the per-second system accumulators hold before they first grow. */
|
|
17263
|
-
const SYSTEM_ROWS = 16;
|
|
17264
|
-
/**
|
|
17265
|
-
* Creates an empty map. It lives in its own non-exported function because lint rule L5 refuses
|
|
17266
|
-
* a collection built inside an exported declaration.
|
|
17267
|
-
*
|
|
17268
|
-
* @returns An empty map.
|
|
17269
|
-
*/
|
|
17270
|
-
function emptyMap$7() {
|
|
17271
|
-
return /* @__PURE__ */ new Map();
|
|
17272
|
-
}
|
|
17273
|
-
/**
|
|
17274
|
-
* Creates an empty weak set. Same reason as `emptyMap`.
|
|
17275
|
-
*
|
|
17276
|
-
* @returns An empty weak set.
|
|
17277
|
-
*/
|
|
17278
|
-
function emptyWeakSet$1() {
|
|
17279
|
-
return /* @__PURE__ */ new WeakSet();
|
|
17280
|
-
}
|
|
17281
|
-
/**
|
|
17282
|
-
* Creates the compile ring: 64 pre-made records.
|
|
17283
|
-
*
|
|
17284
|
-
* @returns The ring, empty.
|
|
17285
|
-
*/
|
|
17286
|
-
function createCompileRing() {
|
|
17287
|
-
return {
|
|
17288
|
-
records: Array.from({ length: COMPILE_SLOTS }, () => ({
|
|
17289
|
-
frame: 0,
|
|
17290
|
-
label: null,
|
|
17291
|
-
ms: 0
|
|
17292
|
-
})),
|
|
17293
|
-
next: 0,
|
|
17294
|
-
size: 0,
|
|
17295
|
-
shaderModules: 0
|
|
17296
|
-
};
|
|
17297
|
-
}
|
|
17298
|
-
/**
|
|
17299
|
-
* Creates the long-frame ring: 64 pre-made records with three pre-made scripts each.
|
|
17300
|
-
*
|
|
17301
|
-
* @returns The ring, empty, observing nothing.
|
|
17302
|
-
*/
|
|
17303
|
-
function createLongFrameRing() {
|
|
17304
|
-
return {
|
|
17305
|
-
records: Array.from({ length: LONG_FRAME_SLOTS }, () => ({
|
|
17306
|
-
startMs: 0,
|
|
17307
|
-
durationMs: 0,
|
|
17308
|
-
blockingMs: NaN,
|
|
17309
|
-
renderStartMs: NaN,
|
|
17310
|
-
styleLayoutMs: NaN,
|
|
17311
|
-
scripts: Array.from({ length: SCRIPT_SLOTS }, () => ({
|
|
17312
|
-
invoker: "",
|
|
17313
|
-
type: "",
|
|
17314
|
-
source: "",
|
|
17315
|
-
fn: "",
|
|
17316
|
-
ms: 0,
|
|
17317
|
-
forcedLayoutMs: 0
|
|
17318
|
-
})),
|
|
17319
|
-
scriptCount: 0
|
|
17320
|
-
})),
|
|
17321
|
-
next: 0,
|
|
17322
|
-
size: 0,
|
|
17323
|
-
type: void 0,
|
|
17324
|
-
disconnect: void 0
|
|
17325
|
-
};
|
|
17326
|
-
}
|
|
17327
|
-
/**
|
|
17328
|
-
* Creates the sinks the counting classes write into. Its identity never changes; in a production
|
|
17329
|
-
* build it stays at zero.
|
|
17330
|
-
*
|
|
17331
|
-
* @param draws - The draw counter of the monitor state; a fresh one when left out.
|
|
17332
|
-
* @returns The sinks.
|
|
17333
|
-
*/
|
|
17334
|
-
function createSinks(draws) {
|
|
17335
|
-
return {
|
|
17336
|
-
draws: draws ?? {
|
|
17337
|
-
frame: 0,
|
|
17338
|
-
last: 0
|
|
17339
|
-
},
|
|
17340
|
-
counts: new Float64Array(Object.keys(COUNT).length),
|
|
17341
|
-
inFrame: false,
|
|
17342
|
-
filterDepth: 0,
|
|
17343
|
-
filterKind: 0,
|
|
17344
|
-
kindNames: ["other"],
|
|
17345
|
-
kindApplies: /* @__PURE__ */ new Float64Array(32),
|
|
17346
|
-
kindPixels: /* @__PURE__ */ new Float64Array(32),
|
|
17347
|
-
filterKinds: void 0,
|
|
17348
|
-
compiles: createCompileRing(),
|
|
17349
|
-
passes: void 0,
|
|
17350
|
-
measuring: false,
|
|
17351
|
-
rebuildsWindow: 0
|
|
17352
|
-
};
|
|
17353
|
-
}
|
|
17354
|
-
/**
|
|
17355
|
-
* Creates the performance monitor of an agent build. Nothing large is allocated here: the ring
|
|
17356
|
-
* gets its capacity at the first attach, once per renderer state.
|
|
17357
|
-
*
|
|
17358
|
-
* @param config - `renderer.monitor`.
|
|
17359
|
-
* @param sinks - The sinks of the monitor state.
|
|
17360
|
-
* @returns The perf state.
|
|
17361
|
-
*/
|
|
17362
|
-
function createPerfState(config, sinks) {
|
|
17363
|
-
const events = createEventRing();
|
|
17364
|
-
return {
|
|
17365
|
-
budget: config.budgetMs,
|
|
17366
|
-
frames: config.frames,
|
|
17367
|
-
ring: createFrameRing(0, events, sinks.kindNames),
|
|
17368
|
-
scratch: /* @__PURE__ */ new Float32Array(0),
|
|
17369
|
-
staged: /* @__PURE__ */ new Float64Array(2),
|
|
17370
|
-
ticks: createTickEstimate(),
|
|
17371
|
-
rate: void 0,
|
|
17372
|
-
drawn: false,
|
|
17373
|
-
flags: 0,
|
|
17374
|
-
pathIds: emptyMap$7(),
|
|
17375
|
-
pathId: 0,
|
|
17376
|
-
restPath: void 0,
|
|
17377
|
-
produced: 0,
|
|
17378
|
-
measuring: 0,
|
|
17379
|
-
lastHeap: NaN,
|
|
17380
|
-
secSum: new Float64Array(SYSTEM_ROWS),
|
|
17381
|
-
secMax: new Float32Array(SYSTEM_ROWS),
|
|
17382
|
-
gapHist: /* @__PURE__ */ new Uint16Array(81),
|
|
17383
|
-
listenersSum: 0,
|
|
17384
|
-
events,
|
|
17385
|
-
display: void 0,
|
|
17386
|
-
pending: /* @__PURE__ */ new Float64Array(64),
|
|
17387
|
-
pendingCount: 0,
|
|
17388
|
-
lastFlushAt: NaN,
|
|
17389
|
-
agentMs: 0,
|
|
17390
|
-
agentStart: NaN,
|
|
17391
|
-
frame: 0,
|
|
17392
|
-
listeners: [],
|
|
17393
|
-
waiting: [],
|
|
17394
|
-
failed: emptyWeakSet$1(),
|
|
17395
|
-
shell: "unknown",
|
|
17396
|
-
startup: void 0,
|
|
17397
|
-
firstDrawnAt: NaN,
|
|
17398
|
-
timer: void 0,
|
|
17399
|
-
longFrames: createLongFrameRing(),
|
|
17400
|
-
detach: [],
|
|
17401
|
-
attached: false
|
|
17402
|
-
};
|
|
17403
|
-
}
|
|
17404
|
-
/**
|
|
17405
|
-
* Creates the initial monitor state: no frame seen, an empty window, no capture waiting, no draw
|
|
17406
|
-
* counted, zeroed sinks, and in an agent build the performance monitor.
|
|
17407
|
-
*
|
|
17408
|
-
* @param config - `renderer.monitor`.
|
|
17409
|
-
* @returns The monitor branch of the plugin state.
|
|
17410
|
-
*/
|
|
17411
|
-
function createMonitorState(config) {
|
|
17412
|
-
const draws = {
|
|
17413
|
-
frame: 0,
|
|
17414
|
-
last: 0
|
|
17415
|
-
};
|
|
17416
|
-
const sinks = createSinks(draws);
|
|
17417
|
-
return {
|
|
17418
|
-
frameStart: void 0,
|
|
17419
|
-
lastStart: void 0,
|
|
17420
|
-
windowMs: 0,
|
|
17421
|
-
intervals: 0,
|
|
17422
|
-
workMs: 0,
|
|
17423
|
-
frames: 0,
|
|
17424
|
-
fps: 0,
|
|
17425
|
-
drawnIntervals: 0,
|
|
17426
|
-
drawnFps: 0,
|
|
17427
|
-
lastDrawn: false,
|
|
17428
|
-
frameMs: 0,
|
|
17429
|
-
redrawsOpen: 0,
|
|
17430
|
-
rebuildsOpen: 0,
|
|
17431
|
-
redraws: 0,
|
|
17432
|
-
rebuilds: 0,
|
|
17433
|
-
captures: [],
|
|
17434
|
-
draws,
|
|
17435
|
-
sinks,
|
|
17436
|
-
perf: typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? createPerfState(config, sinks) : void 0,
|
|
17437
|
-
passes: {
|
|
17438
|
-
last: 0,
|
|
17439
|
-
open: 0,
|
|
17440
|
-
max: 0
|
|
17441
|
-
},
|
|
17442
|
-
passDetach: []
|
|
17443
|
-
};
|
|
17444
|
-
}
|
|
17445
18339
|
/** A gap spike is annotated `compile` for a compile in one of this many frames before it: the stall comes later. */
|
|
17446
18340
|
const COMPILE_LOOKBACK = 4;
|
|
17447
18341
|
/** What a refused promise of the shell leads to: nothing, the log goes on. */
|
|
@@ -17555,7 +18449,7 @@ function addPending(perf, sequence) {
|
|
|
17555
18449
|
*/
|
|
17556
18450
|
function compiledBefore(ring, slot) {
|
|
17557
18451
|
const age = (ring.newest - slot + ring.capacity) % ring.capacity;
|
|
17558
|
-
for (let back = 1; back <= COMPILE_LOOKBACK && age + back < ring.size; back += 1) if (
|
|
18452
|
+
for (let back = 1; back <= COMPILE_LOOKBACK && age + back < ring.size; back += 1) if (cellF32(ring.columns.compiles, (slot - back + ring.capacity) % ring.capacity) > 0) return true;
|
|
17559
18453
|
return false;
|
|
17560
18454
|
}
|
|
17561
18455
|
/**
|
|
@@ -17570,10 +18464,10 @@ function compiledBefore(ring, slot) {
|
|
|
17570
18464
|
function annotate(pc, slot) {
|
|
17571
18465
|
const { ring } = pc.perf;
|
|
17572
18466
|
const c = ring.columns;
|
|
17573
|
-
const late = (
|
|
18467
|
+
const late = (cellU8(c.causes, slot) & CAUSE.gap) !== 0;
|
|
17574
18468
|
let bits = 0;
|
|
17575
|
-
if (
|
|
17576
|
-
if (
|
|
18469
|
+
if (cellF32(c.compiles, slot) > 0 || late && compiledBefore(ring, slot)) bits |= CAUSE.compile;
|
|
18470
|
+
if (cellF32(c.uploadMb, slot) >= 1) bits |= CAUSE.upload;
|
|
17577
18471
|
if (annotateLongFrame(pc.perf, slot)) bits |= CAUSE.longFrame;
|
|
17578
18472
|
return bits;
|
|
17579
18473
|
}
|
|
@@ -17590,11 +18484,11 @@ function flushSpikes(pc, windowStart) {
|
|
|
17590
18484
|
const c = ring.columns;
|
|
17591
18485
|
let kept = 0;
|
|
17592
18486
|
for (let index = 0; index < perf.pendingCount; index += 1) {
|
|
17593
|
-
const sequence =
|
|
18487
|
+
const sequence = cellF64(perf.pending, index);
|
|
17594
18488
|
const slot = slotOfSequence(ring, sequence);
|
|
17595
18489
|
if (slot === -1) continue;
|
|
17596
|
-
if (
|
|
17597
|
-
c.causes[slot] =
|
|
18490
|
+
if (cellU8(c.gpuPartial, slot) === 0 || cellF64(c.t, slot) < windowStart) {
|
|
18491
|
+
c.causes[slot] = cellU8(c.causes, slot) | annotate(pc, slot);
|
|
17598
18492
|
deliver(pc, spikeLine(pc, slot));
|
|
17599
18493
|
} else {
|
|
17600
18494
|
perf.pending[kept] = sequence;
|
|
@@ -17616,10 +18510,10 @@ function flushLoads(pc, start, end) {
|
|
|
17616
18510
|
const window = windowOf$1(ring, start, end);
|
|
17617
18511
|
for (let age = window.first + window.count - 1; age >= window.first; age -= 1) {
|
|
17618
18512
|
const slot = slotAt(ring, age);
|
|
17619
|
-
const frame =
|
|
17620
|
-
if (Number.isNaN(firstDrawnAt) ||
|
|
17621
|
-
const compiles =
|
|
17622
|
-
const mb =
|
|
18513
|
+
const frame = cellU32(c.frame, slot);
|
|
18514
|
+
if (Number.isNaN(firstDrawnAt) || cellF64(c.t, slot) <= firstDrawnAt) continue;
|
|
18515
|
+
const compiles = cellF32(c.compiles, slot);
|
|
18516
|
+
const mb = cellF32(c.uploadMb, slot);
|
|
17623
18517
|
if (compiles > 0) deliver(pc, eventLine("compile", {
|
|
17624
18518
|
frame,
|
|
17625
18519
|
count: compiles
|
|
@@ -17631,6 +18525,26 @@ function flushLoads(pc, start, end) {
|
|
|
17631
18525
|
}
|
|
17632
18526
|
}
|
|
17633
18527
|
/**
|
|
18528
|
+
* Writes a `shader` event for every compile and shader module not sent yet that the ring still holds, boot compiles
|
|
18529
|
+
* included: a listener that came after the boot still gets them at its first flush.
|
|
18530
|
+
*
|
|
18531
|
+
* @param pc - The perf context.
|
|
18532
|
+
*/
|
|
18533
|
+
function flushShaders(pc) {
|
|
18534
|
+
for (const detail of takeShaderEvents(pc, (entity) => viewLabel(pc, entity))) deliver(pc, eventLine("shader", detail, detail.frame));
|
|
18535
|
+
}
|
|
18536
|
+
/**
|
|
18537
|
+
* Judges the problem rules of the second and writes a `problem` event for each that opened or cleared; without a
|
|
18538
|
+
* listener the rules are judged all the same, so their accumulators stay per second.
|
|
18539
|
+
*
|
|
18540
|
+
* @param pc - The perf context.
|
|
18541
|
+
*/
|
|
18542
|
+
function flushProblems(pc) {
|
|
18543
|
+
const details = judgeProblems(pc);
|
|
18544
|
+
if (pc.perf.listeners.length === 0) return;
|
|
18545
|
+
for (const detail of details) deliver(pc, eventLine("problem", detail, pc.perf.frame));
|
|
18546
|
+
}
|
|
18547
|
+
/**
|
|
17634
18548
|
* Starts a new second: the histogram, the system sums and maxima and the monitor's own cost go back to 0, the system
|
|
17635
18549
|
* accumulators grown when the world registered more systems than they hold.
|
|
17636
18550
|
*
|
|
@@ -17647,7 +18561,7 @@ function nextSecond(pc) {
|
|
|
17647
18561
|
perf.secSum.fill(0);
|
|
17648
18562
|
perf.secMax.fill(0);
|
|
17649
18563
|
perf.listenersSum = 0;
|
|
17650
|
-
perf.lastFlushAt =
|
|
18564
|
+
perf.lastFlushAt = cellF64(perf.staged, 1);
|
|
17651
18565
|
}
|
|
17652
18566
|
/**
|
|
17653
18567
|
* The flush, once a second of page clock and on a pause: the GPU batch closes, the startup line is built once after
|
|
@@ -17660,7 +18574,7 @@ function nextSecond(pc) {
|
|
|
17660
18574
|
function flushPerf(pc) {
|
|
17661
18575
|
const { perf } = pc;
|
|
17662
18576
|
const start = perf.lastFlushAt;
|
|
17663
|
-
const windowEnd =
|
|
18577
|
+
const windowEnd = cellF64(perf.staged, 1);
|
|
17664
18578
|
perf.timer?.close();
|
|
17665
18579
|
if (perf.startup === void 0 && !Number.isNaN(perf.firstDrawnAt)) {
|
|
17666
18580
|
const startup = deepFreeze(buildStartup(pc));
|
|
@@ -17671,7 +18585,9 @@ function flushPerf(pc) {
|
|
|
17671
18585
|
deliver(pc, sampleLine(pc, start, windowEnd));
|
|
17672
18586
|
flushSpikes(pc, start);
|
|
17673
18587
|
flushLoads(pc, start, windowEnd);
|
|
18588
|
+
flushShaders(pc);
|
|
17674
18589
|
} else perf.pendingCount = 0;
|
|
18590
|
+
flushProblems(pc);
|
|
17675
18591
|
nextSecond(pc);
|
|
17676
18592
|
}
|
|
17677
18593
|
/**
|
|
@@ -17727,6 +18643,8 @@ function openFrame(sinks) {
|
|
|
17727
18643
|
sinks.counts.fill(0);
|
|
17728
18644
|
sinks.kindApplies.fill(0);
|
|
17729
18645
|
sinks.kindPixels.fill(0);
|
|
18646
|
+
sinks.owners.applies.fill(0);
|
|
18647
|
+
sinks.owners.pixels.fill(0);
|
|
17730
18648
|
sinks.inFrame = true;
|
|
17731
18649
|
}
|
|
17732
18650
|
/**
|
|
@@ -17742,21 +18660,24 @@ function writeCounters(pc, slot) {
|
|
|
17742
18660
|
const { counts } = sinks;
|
|
17743
18661
|
const bits = perf.measuring;
|
|
17744
18662
|
const counted = (bits & MEASURE.counted) !== 0;
|
|
17745
|
-
c.passes[slot] = (bits & MEASURE.passes) === 0 ? NaN :
|
|
17746
|
-
c.stencilPasses[slot] = (bits & MEASURE.stencilPasses) === 0 ? NaN :
|
|
18663
|
+
c.passes[slot] = (bits & MEASURE.passes) === 0 ? NaN : cellF64(counts, COUNT.passes);
|
|
18664
|
+
c.stencilPasses[slot] = (bits & MEASURE.stencilPasses) === 0 ? NaN : cellF64(counts, COUNT.stencilPasses);
|
|
17747
18665
|
c.draws[slot] = (bits & MEASURE.draws) === 0 ? NaN : sinks.draws.last;
|
|
17748
|
-
c.filterApplies[slot] = counted ?
|
|
17749
|
-
c.filterMpx[slot] = counted ?
|
|
17750
|
-
c.uploads[slot] = counted ?
|
|
17751
|
-
c.uploadMb[slot] = counted ?
|
|
17752
|
-
c.compiles[slot] = (bits & MEASURE.compiles) === 0 ? NaN :
|
|
18666
|
+
c.filterApplies[slot] = counted ? cellF64(counts, COUNT.filterApplies) : NaN;
|
|
18667
|
+
c.filterMpx[slot] = counted ? cellF64(counts, COUNT.filterPixels) / PIXELS_PER_MPX : NaN;
|
|
18668
|
+
c.uploads[slot] = counted ? cellF64(counts, COUNT.uploads) : NaN;
|
|
18669
|
+
c.uploadMb[slot] = counted ? cellF64(counts, COUNT.uploadBytes) / BYTES_PER_MB$1 : NaN;
|
|
18670
|
+
c.compiles[slot] = (bits & MEASURE.compiles) === 0 ? NaN : cellF64(counts, COUNT.compiles);
|
|
17753
18671
|
const decided = pc.deps.demand.frame();
|
|
17754
18672
|
c.marks[slot] = decided.marks;
|
|
17755
18673
|
c.reason[slot] = reasonIndex(decided.reason);
|
|
17756
|
-
c.rebuilds[slot] = perf.produced === 0 ? NaN :
|
|
18674
|
+
c.rebuilds[slot] = perf.produced === 0 ? NaN : cellF64(counts, COUNT.rebuilds);
|
|
17757
18675
|
const kept = pc.ctx.state.kept;
|
|
17758
18676
|
c.kept[slot] = perf.drawn ? kept.drawnPictures : 0;
|
|
17759
18677
|
c.retakes[slot] = kept.frameTakes;
|
|
18678
|
+
const targets = (bits & MEASURE.targets) !== 0;
|
|
18679
|
+
c.targetsCreated[slot] = targets ? cellF64(counts, COUNT.targetsCreated) : NaN;
|
|
18680
|
+
c.targetsDestroyed[slot] = targets ? cellF64(counts, COUNT.targetsDestroyed) : NaN;
|
|
17760
18681
|
}
|
|
17761
18682
|
/**
|
|
17762
18683
|
* The index of a draw reason in ring order, `NO_REASON` for a name the order does not know.
|
|
@@ -17785,11 +18706,11 @@ function reasonIndex(reason) {
|
|
|
17785
18706
|
*/
|
|
17786
18707
|
function insertTop(indices, values, base, stride, index, value) {
|
|
17787
18708
|
let place = stride;
|
|
17788
|
-
while (place > 0 && value >
|
|
18709
|
+
while (place > 0 && value > cellF32(values, base + place - 1)) place -= 1;
|
|
17789
18710
|
if (place === stride) return;
|
|
17790
18711
|
for (let move = stride - 1; move > place; move -= 1) {
|
|
17791
|
-
indices[base + move] =
|
|
17792
|
-
values[base + move] =
|
|
18712
|
+
indices[base + move] = indices[base + move - 1] ?? 0;
|
|
18713
|
+
values[base + move] = cellF32(values, base + move - 1);
|
|
17793
18714
|
}
|
|
17794
18715
|
indices[base + place] = index;
|
|
17795
18716
|
values[base + place] = value;
|
|
@@ -17815,7 +18736,7 @@ function writeSystems(pc, slot, timing) {
|
|
|
17815
18736
|
ring.systemNames = timing.names;
|
|
17816
18737
|
const count = Math.min(timing.names.length, timing.ms.length);
|
|
17817
18738
|
for (let index = 0; index < count; index += 1) {
|
|
17818
|
-
const ms =
|
|
18739
|
+
const ms = cellF64(timing.ms, index);
|
|
17819
18740
|
if (ms > 0) insertTop(c.sysIdx, c.sysMs, base, 5, index, ms);
|
|
17820
18741
|
}
|
|
17821
18742
|
}
|
|
@@ -17835,12 +18756,12 @@ function writeKinds(pc, slot) {
|
|
|
17835
18756
|
c.fltApplies.fill(0, base, base + 4);
|
|
17836
18757
|
const kinds = sinks.kindPixels.length;
|
|
17837
18758
|
for (let kind = 0; kind < kinds; kind += 1) {
|
|
17838
|
-
const pixels =
|
|
18759
|
+
const pixels = cellF64(sinks.kindPixels, kind);
|
|
17839
18760
|
if (pixels > 0) insertTop(c.fltKind, c.fltMpx, base, 4, kind + 1, pixels / PIXELS_PER_MPX);
|
|
17840
18761
|
}
|
|
17841
18762
|
for (let place = 0; place < 4; place += 1) {
|
|
17842
|
-
const kind =
|
|
17843
|
-
if (kind !== 0) c.fltApplies[base + place] =
|
|
18763
|
+
const kind = cellU8(c.fltKind, base + place);
|
|
18764
|
+
if (kind !== 0) c.fltApplies[base + place] = cellF64(sinks.kindApplies, kind - 1);
|
|
17844
18765
|
}
|
|
17845
18766
|
}
|
|
17846
18767
|
/**
|
|
@@ -17854,15 +18775,15 @@ function writeKinds(pc, slot) {
|
|
|
17854
18775
|
function accumulate(perf, measure, timing) {
|
|
17855
18776
|
if (!Number.isNaN(measure.gapMs)) {
|
|
17856
18777
|
const bucket = bucketOf(measure.gapMs);
|
|
17857
|
-
perf.gapHist[bucket] =
|
|
18778
|
+
perf.gapHist[bucket] = cellU16(perf.gapHist, bucket) + 1;
|
|
17858
18779
|
}
|
|
17859
18780
|
perf.listenersSum += measure.listenersMs;
|
|
17860
18781
|
if (timing === void 0) return;
|
|
17861
18782
|
const count = Math.min(timing.names.length, timing.ms.length, perf.secSum.length);
|
|
17862
18783
|
for (let index = 0; index < count; index += 1) {
|
|
17863
|
-
const ms =
|
|
17864
|
-
perf.secSum[index] =
|
|
17865
|
-
if (ms >
|
|
18784
|
+
const ms = cellF64(timing.ms, index);
|
|
18785
|
+
perf.secSum[index] = cellF64(perf.secSum, index) + ms;
|
|
18786
|
+
if (ms > cellF32(perf.secMax, index)) perf.secMax[index] = ms;
|
|
17866
18787
|
}
|
|
17867
18788
|
}
|
|
17868
18789
|
/**
|
|
@@ -17888,10 +18809,10 @@ function turned(pc) {
|
|
|
17888
18809
|
* @param measure - What `time` measured of the frame.
|
|
17889
18810
|
*/
|
|
17890
18811
|
function stageBudget(perf, measure) {
|
|
17891
|
-
const ticksSlower = Number.isNaN(measure.displayMs) &&
|
|
18812
|
+
const ticksSlower = Number.isNaN(measure.displayMs) && cellF64(perf.ticks.periodMs, 0) > measure.rateMs;
|
|
17892
18813
|
perf.staged[0] = measure.rateMs;
|
|
17893
18814
|
if (measure.displayMs > measure.rateMs) perf.staged[0] = measure.displayMs;
|
|
17894
|
-
else if (ticksSlower) perf.staged[0] =
|
|
18815
|
+
else if (ticksSlower) perf.staged[0] = cellF64(perf.ticks.periodMs, 0);
|
|
17895
18816
|
if (perf.budget !== "rate") perf.staged[0] = perf.budget;
|
|
17896
18817
|
}
|
|
17897
18818
|
/**
|
|
@@ -17931,7 +18852,7 @@ function gapJudged(pc, measure, previous, littleWork) {
|
|
|
17931
18852
|
if (pageHidden()) return false;
|
|
17932
18853
|
if (previous === -1) return true;
|
|
17933
18854
|
const c = pc.perf.ring.columns;
|
|
17934
|
-
return !(littleWork && measure.gapMs > AWAY_GAP_MS &&
|
|
18855
|
+
return !(littleWork && measure.gapMs > AWAY_GAP_MS && cellF32(c.gapMs, previous) > AWAY_GAP_MS) && (pc.perf.drawn || cellU8(c.drawn, previous) === 1);
|
|
17935
18856
|
}
|
|
17936
18857
|
/**
|
|
17937
18858
|
* Writes what `time` measured of the frame into its slot: the clock numbers, the phases, the work, the flags of the
|
|
@@ -17952,8 +18873,8 @@ function writeMeasure(pc, slot, measure) {
|
|
|
17952
18873
|
c.skipped[slot] = Math.min(SKIPPED_MAX, measure.skipped);
|
|
17953
18874
|
c.startMs[slot] = measure.startMs;
|
|
17954
18875
|
c.rateMs[slot] = measure.rateMs;
|
|
17955
|
-
c.budgetMs[slot] =
|
|
17956
|
-
for (let index = 0; index < 6; index += 1) c.phaseMs[phase + index] =
|
|
18876
|
+
c.budgetMs[slot] = cellF64(perf.staged, 0);
|
|
18877
|
+
for (let index = 0; index < 6; index += 1) c.phaseMs[phase + index] = cellF32(measure.phaseMs, index);
|
|
17957
18878
|
c.agentMs[slot] = perf.agentMs;
|
|
17958
18879
|
c.workMs[slot] = measure.workMs;
|
|
17959
18880
|
c.drawn[slot] = perf.drawn ? 1 : 0;
|
|
@@ -17978,7 +18899,7 @@ function commitFrame(pc, measure) {
|
|
|
17978
18899
|
const slot = ring.written % ring.capacity;
|
|
17979
18900
|
const previous = ring.size === 0 ? -1 : ring.newest;
|
|
17980
18901
|
stageBudget(perf, measure);
|
|
17981
|
-
const budget =
|
|
18902
|
+
const budget = cellF64(perf.staged, 0);
|
|
17982
18903
|
const { agentMs } = perf;
|
|
17983
18904
|
const timing = pc.ctx.deps.world.systemTimes();
|
|
17984
18905
|
writeMeasure(pc, slot, measure);
|
|
@@ -17987,7 +18908,7 @@ function commitFrame(pc, measure) {
|
|
|
17987
18908
|
writeKinds(pc, slot);
|
|
17988
18909
|
const heap = usedHeapBytes();
|
|
17989
18910
|
const heapDrop = perf.lastHeap - heap;
|
|
17990
|
-
let causes = spikeCauses(measure, agentMs, budget,
|
|
18911
|
+
let causes = spikeCauses(measure, agentMs, budget, previous === -1 ? NaN : cellF32(c.capMs, previous));
|
|
17991
18912
|
const littleWork = measure.workMs - agentMs < budget * GC_WORK_SHARE;
|
|
17992
18913
|
if ((causes & CAUSE.gap) !== 0 && !gapJudged(pc, measure, previous, littleWork)) causes &= ~CAUSE.gap;
|
|
17993
18914
|
const cameLate = (causes & CAUSE.gap) !== 0;
|
|
@@ -18009,8 +18930,12 @@ function commitFrame(pc, measure) {
|
|
|
18009
18930
|
flushPerf(pc);
|
|
18010
18931
|
}
|
|
18011
18932
|
accumulate(perf, measure, timing);
|
|
18933
|
+
accumulateProblems(pc, measure);
|
|
18012
18934
|
if (causes !== 0) addPending(perf, sequenceOf(ring, slot));
|
|
18013
|
-
if (perf.drawn && Number.isNaN(perf.firstDrawnAt))
|
|
18935
|
+
if (perf.drawn && Number.isNaN(perf.firstDrawnAt)) {
|
|
18936
|
+
perf.firstDrawnAt = measure.at + measure.workMs;
|
|
18937
|
+
perf.firstDrawnFrame = measure.frame;
|
|
18938
|
+
}
|
|
18014
18939
|
if ((perf.flags & (CHANGED.resize | CHANGED.restore)) !== 0 || turned(pc) || retuned(pc)) writeDisplay(pc);
|
|
18015
18940
|
perf.flags = 0;
|
|
18016
18941
|
perf.drawn = false;
|
|
@@ -18052,6 +18977,19 @@ function labelOf$2(pc, target, kind) {
|
|
|
18052
18977
|
return kind === KIND.filter ? pc.sinks.kindNames[pc.sinks.filterKind] ?? null : null;
|
|
18053
18978
|
}
|
|
18054
18979
|
/**
|
|
18980
|
+
* Writes the detail of the pass just recorded: for a filter pass the owner and the 1-based kind of the apply running
|
|
18981
|
+
* now, the frame's draws so far, the target's format and resolution.
|
|
18982
|
+
*
|
|
18983
|
+
* @param pc - The perf context.
|
|
18984
|
+
* @param target - The target.
|
|
18985
|
+
* @param kind - The pass kind.
|
|
18986
|
+
*/
|
|
18987
|
+
function writeDetail(pc, target, kind) {
|
|
18988
|
+
const { sinks } = pc;
|
|
18989
|
+
const filter = kind === KIND.filter;
|
|
18990
|
+
writePassDetail(pc.perf.ring.passes, filter ? sinks.filterOwner : 0, filter ? sinks.filterKind + 1 : 0, sinks.draws.frame, target.colorTexture?.source);
|
|
18991
|
+
}
|
|
18992
|
+
/**
|
|
18055
18993
|
* Creates the WebGPU pass sink the counting encoder calls: each pass of a frame goes into the pass ring, with the
|
|
18056
18994
|
* frame number and its index in the frame, and gets the timer's timestamp writes when there is a timer.
|
|
18057
18995
|
*
|
|
@@ -18064,7 +19002,8 @@ function createPassSink(pc, timer, canvas) {
|
|
|
18064
19002
|
return {
|
|
18065
19003
|
begin: (descriptor, target) => {
|
|
18066
19004
|
const kind = kindOf$3(pc, target === canvas);
|
|
18067
|
-
const sequence = writePass(pc.perf.ring.passes, pc.perf.frame,
|
|
19005
|
+
const sequence = writePass(pc.perf.ring.passes, pc.perf.frame, cellF64(pc.sinks.counts, COUNT.passes) - 1, kind, descriptor.depthStencilAttachment === void 0 ? 0 : 1, target.pixelWidth, target.pixelHeight, labelOf$2(pc, target, kind));
|
|
19006
|
+
writeDetail(pc, target, kind);
|
|
18068
19007
|
timer?.timePass(descriptor, sequence);
|
|
18069
19008
|
},
|
|
18070
19009
|
resolve: (commandEncoder) => timer?.resolve(commandEncoder)
|
|
@@ -18090,7 +19029,7 @@ function countGlPasses(renderer, sinks, record) {
|
|
|
18090
19029
|
*/
|
|
18091
19030
|
const count = (target) => {
|
|
18092
19031
|
const { counts } = sinks;
|
|
18093
|
-
const index =
|
|
19032
|
+
const index = cellF64(counts, COUNT.passes);
|
|
18094
19033
|
counts[COUNT.passes] = index + 1;
|
|
18095
19034
|
record?.(target, index);
|
|
18096
19035
|
};
|
|
@@ -18136,6 +19075,7 @@ function attachGlPasses(renderer, pc) {
|
|
|
18136
19075
|
return countGlPasses(renderer, pc.sinks, (target, index) => {
|
|
18137
19076
|
const kind = kindOf$3(pc, target.isRoot);
|
|
18138
19077
|
writePass(pc.perf.ring.passes, pc.perf.frame, index, kind, 255, target.pixelWidth, target.pixelHeight, labelOf$2(pc, target, kind));
|
|
19078
|
+
writeDetail(pc, target, kind);
|
|
18139
19079
|
});
|
|
18140
19080
|
}
|
|
18141
19081
|
/** Queries of one frame slot: two per pass. */
|
|
@@ -18179,10 +19119,10 @@ function createBatch() {
|
|
|
18179
19119
|
function noteGpuTime(pc, slot) {
|
|
18180
19120
|
const { ring } = pc.perf;
|
|
18181
19121
|
const c = ring.columns;
|
|
18182
|
-
const before =
|
|
18183
|
-
const gpuMs =
|
|
18184
|
-
if (
|
|
18185
|
-
if (Number.isNaN(gpuMs) || gpuMs <=
|
|
19122
|
+
const before = cellU8(c.causes, slot);
|
|
19123
|
+
const gpuMs = cellF32(c.gpuMs, slot);
|
|
19124
|
+
if (cellU8(c.stepped, slot) === 1 || (before & CAUSE.gpu) !== 0) return;
|
|
19125
|
+
if (Number.isNaN(gpuMs) || gpuMs <= cellF32(c.budgetMs, slot)) return;
|
|
18186
19126
|
c.causes[slot] = before | CAUSE.gpu | (before === 0 ? annotate(pc, slot) : 0);
|
|
18187
19127
|
if (before === 0) addPending(pc.perf, sequenceOf(ring, slot));
|
|
18188
19128
|
}
|
|
@@ -18194,7 +19134,7 @@ function noteGpuTime(pc, slot) {
|
|
|
18194
19134
|
* @returns Nanoseconds.
|
|
18195
19135
|
*/
|
|
18196
19136
|
function stampAt(view, query) {
|
|
18197
|
-
return
|
|
19137
|
+
return cellU32(view, 2 * query + 1) * HIGH_WORD + cellU32(view, 2 * query);
|
|
18198
19138
|
}
|
|
18199
19139
|
/**
|
|
18200
19140
|
* Writes what the readback says of one frame slot: the time of each pass into its pass-ring entry
|
|
@@ -18208,21 +19148,21 @@ function stampAt(view, query) {
|
|
|
18208
19148
|
*/
|
|
18209
19149
|
function readFrame(timer, batch, view, frame) {
|
|
18210
19150
|
const { columns, passes } = timer.pc.perf.ring;
|
|
18211
|
-
const count =
|
|
19151
|
+
const count = cellU8(batch.timed, frame);
|
|
18212
19152
|
let sum = 0;
|
|
18213
|
-
let whole =
|
|
19153
|
+
let whole = cellU8(batch.partial, frame) === 0;
|
|
18214
19154
|
for (let pass = 0; pass < count; pass += 1) {
|
|
18215
19155
|
const start = stampAt(view, frame * QUERIES_PER_FRAME + 2 * pass);
|
|
18216
19156
|
const end = stampAt(view, frame * QUERIES_PER_FRAME + 2 * pass + 1);
|
|
18217
|
-
const sequence =
|
|
19157
|
+
const sequence = cellF64(batch.passes, frame * 32 + pass);
|
|
18218
19158
|
if (start > 0 && end > start) {
|
|
18219
19159
|
const ms = (end - start) / NS_PER_MS$1;
|
|
18220
19160
|
sum += ms;
|
|
18221
19161
|
if (passes.written - sequence <= passes.capacity) passes.gpuMs[sequence % passes.capacity] = ms;
|
|
18222
19162
|
} else whole = false;
|
|
18223
19163
|
}
|
|
18224
|
-
const slot =
|
|
18225
|
-
if (count === 0 || slot < 0 ||
|
|
19164
|
+
const slot = cellI32(batch.slots, frame);
|
|
19165
|
+
if (count === 0 || slot < 0 || cellU32(columns.frame, slot) !== cellF64(batch.frames, frame)) return;
|
|
18226
19166
|
columns.gpuMs[slot] = sum;
|
|
18227
19167
|
if (whole) columns.gpuPartial[slot] = 0;
|
|
18228
19168
|
noteGpuTime(timer.pc, slot);
|
|
@@ -18299,7 +19239,7 @@ function timePass(timer, descriptor, sequence) {
|
|
|
18299
19239
|
if (timer.frameSlot === -1) reserveFrame(timer);
|
|
18300
19240
|
if (timer.buffer === -1) return;
|
|
18301
19241
|
const { open } = timer;
|
|
18302
|
-
const pass =
|
|
19242
|
+
const pass = cellU8(open.timed, timer.frameSlot);
|
|
18303
19243
|
const write = timer.writes[timer.frameSlot * 32 + pass];
|
|
18304
19244
|
if (pass >= 32 || write === void 0) {
|
|
18305
19245
|
open.partial[timer.frameSlot] = 1;
|
|
@@ -18320,7 +19260,7 @@ function timePass(timer, descriptor, sequence) {
|
|
|
18320
19260
|
function resolveFrame(timer, commandEncoder) {
|
|
18321
19261
|
if (timer.frameSlot === -1) return;
|
|
18322
19262
|
const target = timer.buffers[timer.buffer];
|
|
18323
|
-
const count =
|
|
19263
|
+
const count = cellU8(timer.open.timed, timer.frameSlot);
|
|
18324
19264
|
const offset = timer.frameSlot * REGION_BYTES;
|
|
18325
19265
|
if (target === void 0 || count === 0) return;
|
|
18326
19266
|
commandEncoder.resolveQuerySet(timer.querySet, timer.frameSlot * QUERIES_PER_FRAME, 2 * count, timer.resolveBuffer, offset);
|
|
@@ -18467,7 +19407,7 @@ prerender: () => {
|
|
|
18467
19407
|
state.tried = true;
|
|
18468
19408
|
if (state.free === 0) return;
|
|
18469
19409
|
state.free -= 1;
|
|
18470
|
-
state.active =
|
|
19410
|
+
state.active = cellI32(free, state.free);
|
|
18471
19411
|
const query = pool[state.active];
|
|
18472
19412
|
if (query !== void 0) gl.beginQuery(extension.TIME_ELAPSED_EXT, query);
|
|
18473
19413
|
} };
|
|
@@ -18487,7 +19427,7 @@ postrender: () => {
|
|
|
18487
19427
|
* Takes the query at the head of the queue back into the pool.
|
|
18488
19428
|
*/
|
|
18489
19429
|
const dequeue = () => {
|
|
18490
|
-
free[state.free] =
|
|
19430
|
+
free[state.free] = cellI32(queue, state.head);
|
|
18491
19431
|
state.free += 1;
|
|
18492
19432
|
state.head = (state.head + 1) % POOL_SIZE;
|
|
18493
19433
|
state.pending -= 1;
|
|
@@ -18503,16 +19443,16 @@ postrender: () => {
|
|
|
18503
19443
|
return;
|
|
18504
19444
|
}
|
|
18505
19445
|
while (state.pending > 0) {
|
|
18506
|
-
const query = pool[
|
|
19446
|
+
const query = pool[cellI32(queue, state.head)];
|
|
18507
19447
|
if (query === void 0 || !gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) return;
|
|
18508
19448
|
const ms = Number(gl.getQueryParameter(query, gl.QUERY_RESULT)) / NS_PER_MS;
|
|
18509
|
-
const slot =
|
|
18510
|
-
const frame =
|
|
19449
|
+
const slot = cellI32(queueSlots, state.head);
|
|
19450
|
+
const frame = cellF64(queueFrames, state.head);
|
|
18511
19451
|
dequeue();
|
|
18512
|
-
if (slot < 0 ||
|
|
18513
|
-
const before =
|
|
19452
|
+
if (slot < 0 || cellU32(columns.frame, slot) !== frame) continue;
|
|
19453
|
+
const before = cellF32(columns.gpuMs, slot);
|
|
18514
19454
|
columns.gpuMs[slot] = Number.isNaN(before) ? ms : before + ms;
|
|
18515
|
-
if (state.pending === 0 ||
|
|
19455
|
+
if (state.pending === 0 || cellF64(queueFrames, state.head) !== frame) columns.gpuPartial[slot] = 0;
|
|
18516
19456
|
noteGpuTime(pc, slot);
|
|
18517
19457
|
}
|
|
18518
19458
|
};
|
|
@@ -18523,7 +19463,7 @@ postrender: () => {
|
|
|
18523
19463
|
commit: (slot, frame) => {
|
|
18524
19464
|
for (let index = 0; index < state.pending; index += 1) {
|
|
18525
19465
|
const at = (state.head + index) % POOL_SIZE;
|
|
18526
|
-
if (
|
|
19466
|
+
if (cellF64(queueFrames, at) === frame) queueSlots[at] = slot;
|
|
18527
19467
|
}
|
|
18528
19468
|
if (state.tried) pc.perf.ring.columns.gpuPartial[slot] = 1;
|
|
18529
19469
|
state.tried = false;
|
|
@@ -18670,11 +19610,15 @@ function attachFirst(pc) {
|
|
|
18670
19610
|
perf.flags |= CHANGED.restore;
|
|
18671
19611
|
writeEvent(pc, "device-restored", { kind: host.kind() });
|
|
18672
19612
|
});
|
|
18673
|
-
host.onTrim((reason, freedMb) =>
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
19613
|
+
host.onTrim((reason, freedMb) => {
|
|
19614
|
+
perf.problems.trimAt = pageNow();
|
|
19615
|
+
writeEvent(pc, "pool-trim", {
|
|
19616
|
+
reason,
|
|
19617
|
+
freedMb
|
|
19618
|
+
});
|
|
19619
|
+
});
|
|
18677
19620
|
deps.sync.onWarm((draws) => writeEvent(pc, "warm", { draws }));
|
|
19621
|
+
deps.sync.onOverdraw((on) => writeEvent(pc, "debug", { overdraw: on }));
|
|
18678
19622
|
notePath(pc, ctx.deps.flow.state().path, false);
|
|
18679
19623
|
perf.display = displayOf(pc);
|
|
18680
19624
|
}
|
|
@@ -18740,17 +19684,19 @@ function attachPerf(pc) {
|
|
|
18740
19684
|
perf.timer = void 0;
|
|
18741
19685
|
sinks.passes = void 0;
|
|
18742
19686
|
sinks.filterKinds = ctx.state.sync.filterKinds;
|
|
19687
|
+
sinks.filterOwners = ctx.state.sync.filterOwners;
|
|
18743
19688
|
const counted = ctx.state.host.uninstallCounting !== void 0;
|
|
18744
19689
|
const kind = pc.deps.host.kind();
|
|
18745
19690
|
let bits = counted ? MEASURE.counted : 0;
|
|
19691
|
+
if (counted && kind === "webgpu") bits |= MEASURE.targets;
|
|
18746
19692
|
if (kind === "webgpu") bits |= attachGpu(pc, counted);
|
|
18747
19693
|
else if (kind === "webgl") bits |= attachGl(pc);
|
|
18748
19694
|
perf.measuring = bits;
|
|
18749
19695
|
}
|
|
18750
19696
|
/**
|
|
18751
19697
|
* Stops the monitor of an agent build: the per-application removers run, the timer stops, the observer disconnects,
|
|
18752
|
-
* the listeners, the pending spikes, the paths
|
|
18753
|
-
* them), empty.
|
|
19698
|
+
* the listeners, the pending spikes, the paths, the display's ticks, the registered counts and the problem
|
|
19699
|
+
* accumulators go. The ring keeps its arrays (a restart of the same app reuses them), empty.
|
|
18754
19700
|
*
|
|
18755
19701
|
* @param perf - The perf state.
|
|
18756
19702
|
* @param sinks - The sinks.
|
|
@@ -18783,7 +19729,14 @@ function stopPerf(perf, sinks) {
|
|
|
18783
19729
|
resetTicks(perf.ticks);
|
|
18784
19730
|
sinks.passes = void 0;
|
|
18785
19731
|
sinks.filterKinds = void 0;
|
|
19732
|
+
sinks.filterOwners = void 0;
|
|
19733
|
+
sinks.filterOwner = 0;
|
|
18786
19734
|
sinks.inFrame = false;
|
|
19735
|
+
clearCounts(perf.counts);
|
|
19736
|
+
resetProblems({
|
|
19737
|
+
perf,
|
|
19738
|
+
sinks
|
|
19739
|
+
});
|
|
18787
19740
|
}
|
|
18788
19741
|
//#endregion
|
|
18789
19742
|
//#region src/plugins/renderer/monitor/window.ts
|
|
@@ -18887,6 +19840,10 @@ function resetWindow(state) {
|
|
|
18887
19840
|
*/
|
|
18888
19841
|
const noStream = () => {};
|
|
18889
19842
|
/**
|
|
19843
|
+
* The remover `counts.register` answers in a build without the agent flag: it does nothing.
|
|
19844
|
+
*/
|
|
19845
|
+
const noCount = () => {};
|
|
19846
|
+
/**
|
|
18890
19847
|
* The detail of an event written without one.
|
|
18891
19848
|
*/
|
|
18892
19849
|
const NO_DETAIL = Object.freeze({});
|
|
@@ -18954,7 +19911,7 @@ function createMonitorApi(ctx, deps) {
|
|
|
18954
19911
|
* @param drawn - True when the render phase drew the frame.
|
|
18955
19912
|
*/
|
|
18956
19913
|
const notePasses = (drawn) => {
|
|
18957
|
-
const count =
|
|
19914
|
+
const count = cellF64(sinks.counts, COUNT.passes);
|
|
18958
19915
|
if (drawn && count > 0) {
|
|
18959
19916
|
passes.last = count;
|
|
18960
19917
|
passes.open = Math.max(passes.open, count);
|
|
@@ -18984,12 +19941,17 @@ function createMonitorApi(ctx, deps) {
|
|
|
18984
19941
|
kept: deps.kept.stats()
|
|
18985
19942
|
};
|
|
18986
19943
|
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) return stats;
|
|
18987
|
-
|
|
19944
|
+
const dev = {
|
|
18988
19945
|
...stats,
|
|
18989
19946
|
drawCalls: state.draws.last,
|
|
18990
19947
|
redraws: roundTo(state.redraws, 1),
|
|
18991
19948
|
rebuilds: roundTo(state.rebuilds, 1)
|
|
18992
19949
|
};
|
|
19950
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) return {
|
|
19951
|
+
...dev,
|
|
19952
|
+
counts: readCounts(pc)
|
|
19953
|
+
};
|
|
19954
|
+
return dev;
|
|
18993
19955
|
},
|
|
18994
19956
|
capture: async (options = {}) => {
|
|
18995
19957
|
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) return;
|
|
@@ -19015,6 +19977,10 @@ function createMonitorApi(ctx, deps) {
|
|
|
19015
19977
|
endAgent: () => {
|
|
19016
19978
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) endAgent(pc);
|
|
19017
19979
|
},
|
|
19980
|
+
counts: { register: (name, read) => {
|
|
19981
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) return registerCount(pc.perf.counts, name, read);
|
|
19982
|
+
return noCount;
|
|
19983
|
+
} },
|
|
19018
19984
|
setResolution: (resolution) => {
|
|
19019
19985
|
if (!takesResolution(resolution)) throw new RangeError(resolutionMessage(resolution));
|
|
19020
19986
|
return deps.host.overrideResolution(resolution);
|
|
@@ -19062,7 +20028,7 @@ function createMonitorApi(ctx, deps) {
|
|
|
19062
20028
|
count: (_counter, amount) => {
|
|
19063
20029
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) {
|
|
19064
20030
|
pc.perf.produced |= 1;
|
|
19065
|
-
pc.sinks.counts[COUNT.rebuilds] =
|
|
20031
|
+
pc.sinks.counts[COUNT.rebuilds] = cellF64(pc.sinks.counts, COUNT.rebuilds) + amount;
|
|
19066
20032
|
}
|
|
19067
20033
|
},
|
|
19068
20034
|
path: (path, rest) => {
|
|
@@ -19509,21 +20475,33 @@ function filtersOf(state, entity) {
|
|
|
19509
20475
|
return state.filters.get(entity) ?? [];
|
|
19510
20476
|
}
|
|
19511
20477
|
/**
|
|
19512
|
-
*
|
|
19513
|
-
*
|
|
19514
|
-
*
|
|
20478
|
+
* The Pixi filters a slot draws through: the filter itself, the stage filters of its inputs and, for a blur, the two
|
|
20479
|
+
* pass filters Pixi applies in its place.
|
|
20480
|
+
*
|
|
20481
|
+
* @param slot - The slot.
|
|
20482
|
+
* @returns The filters.
|
|
20483
|
+
*/
|
|
20484
|
+
function filtersIn(slot) {
|
|
20485
|
+
const { filter } = slot;
|
|
20486
|
+
const found = [filter, ...slot.inputs?.stages ?? []];
|
|
20487
|
+
if ("blurXFilter" in filter && isFilterObject(filter.blurXFilter)) found.push(filter.blurXFilter);
|
|
20488
|
+
if ("blurYFilter" in filter && isFilterObject(filter.blurYFilter)) found.push(filter.blurYFilter);
|
|
20489
|
+
return found;
|
|
20490
|
+
}
|
|
20491
|
+
/**
|
|
20492
|
+
* Notes the kind and the owner of a slot's filters for the performance log: every filter it draws through. A slot
|
|
20493
|
+
* without a kind counts as "other".
|
|
19515
20494
|
*
|
|
19516
20495
|
* @param state - The sync branch of the plugin state.
|
|
20496
|
+
* @param entity - The entity the slot hangs on.
|
|
19517
20497
|
* @param slot - The slot.
|
|
19518
20498
|
*/
|
|
19519
|
-
function
|
|
19520
|
-
const {
|
|
19521
|
-
|
|
19522
|
-
|
|
19523
|
-
|
|
19524
|
-
|
|
19525
|
-
if ("blurXFilter" in filter && isFilterObject(filter.blurXFilter)) state.filterKinds.set(filter.blurXFilter, kind);
|
|
19526
|
-
if ("blurYFilter" in filter && isFilterObject(filter.blurYFilter)) state.filterKinds.set(filter.blurYFilter, kind);
|
|
20499
|
+
function nameFilters(state, entity, slot) {
|
|
20500
|
+
const { kind } = slot;
|
|
20501
|
+
for (const filter of filtersIn(slot)) {
|
|
20502
|
+
state.filterOwners.set(filter, entity);
|
|
20503
|
+
if (kind !== void 0) state.filterKinds.set(filter, kind);
|
|
20504
|
+
}
|
|
19527
20505
|
}
|
|
19528
20506
|
/**
|
|
19529
20507
|
* Tells whether a property a filter may carry holds an object: the pass filter Pixi's blur keeps under it. Pure.
|
|
@@ -19552,7 +20530,7 @@ function isFilterObject(value) {
|
|
|
19552
20530
|
function setFilters(state, entity, slots, mark, kept) {
|
|
19553
20531
|
if (slots.length === 0) state.filters.delete(entity);
|
|
19554
20532
|
else state.filters.set(entity, [...slots]);
|
|
19555
|
-
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) for (const slot of slots)
|
|
20533
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) for (const slot of slots) nameFilters(state, entity, slot);
|
|
19556
20534
|
const view = state.views.get(entity);
|
|
19557
20535
|
if (view !== void 0) applyFilters(view, slots, () => mark(entity), kept.filterTarget(view));
|
|
19558
20536
|
else if (slots.length > 0) state.viewlessEntries = true;
|
|
@@ -23488,6 +24466,7 @@ function stopSync(state) {
|
|
|
23488
24466
|
stopWarm(state);
|
|
23489
24467
|
state.held.clear();
|
|
23490
24468
|
state.redraws = 0;
|
|
24469
|
+
state.overdraw.listeners.length = 0;
|
|
23491
24470
|
clearLayers(state);
|
|
23492
24471
|
destroyPools(state);
|
|
23493
24472
|
state.root?.destroy({ children: true });
|
|
@@ -23633,7 +24612,8 @@ function withDeps$8(ctx) {
|
|
|
23633
24612
|
* oracle; `demand` settles the
|
|
23634
24613
|
* frame (the dev check, the pixel oracle), and `monitor` closes it and hands a drawn one to a
|
|
23635
24614
|
* waiting capture; last, `host` trims Pixi's texture pool when a rest or a scene change is due. The display objects
|
|
23636
|
-
* are written by the `sync` system.
|
|
24615
|
+
* are written by the `sync` system. While the overdraw heat map of a dev or debug build is on, every frame asks to be
|
|
24616
|
+
* drawn and its stage pass is drawn as the map (`sync.drawOverdraw`).
|
|
23637
24617
|
*
|
|
23638
24618
|
* With `onDemand`, a frame in which nothing visible changed is not drawn: the canvas keeps the
|
|
23639
24619
|
* last picture (WebGPU presents a canvas texture only when one is rendered; a WebGL canvas keeps
|
|
@@ -23655,11 +24635,13 @@ function renderFrame(ctx, modules) {
|
|
|
23655
24635
|
modules.sync.stepOverlays();
|
|
23656
24636
|
modules.sync.drainOverlayEvents(modules.monitor);
|
|
23657
24637
|
modules.kept.collect();
|
|
24638
|
+
const overdraw = modules.sync.overdrawing();
|
|
24639
|
+
if (overdraw) modules.demand.request();
|
|
23658
24640
|
const drawn = modules.demand.decide(modules.monitor.waiting());
|
|
23659
24641
|
if (drawn) {
|
|
23660
24642
|
const probed = modules.sync.warmStep();
|
|
23661
24643
|
modules.kept.flush();
|
|
23662
|
-
if (modules.host.render()) {
|
|
24644
|
+
if (overdraw ? modules.sync.drawOverdraw(modules.host.render) : modules.host.render()) {
|
|
23663
24645
|
ctx.deps.time.frameDrawn();
|
|
23664
24646
|
if (probed) ctx.deps.time.frameProbed();
|
|
23665
24647
|
}
|
|
@@ -23753,6 +24735,61 @@ function internKind(sinks, kind) {
|
|
|
23753
24735
|
return sinks.kindNames.length - 1;
|
|
23754
24736
|
}
|
|
23755
24737
|
/**
|
|
24738
|
+
* The slot of an owner in the owner table of the frame: its own slot, else the first free one, which it takes. An
|
|
24739
|
+
* indexed search over at most 32 slots: no allocation. A full table tracks no more owners.
|
|
24740
|
+
*
|
|
24741
|
+
* @param owners - The owner table.
|
|
24742
|
+
* @param owner - The entity, not 0.
|
|
24743
|
+
* @returns The slot, or -1 when the table is full.
|
|
24744
|
+
*/
|
|
24745
|
+
function ownerSlot(owners, owner) {
|
|
24746
|
+
let free = -1;
|
|
24747
|
+
for (let slot = 0; slot < 32; slot += 1) {
|
|
24748
|
+
const id = cellF64(owners.ids, slot);
|
|
24749
|
+
if (id === owner) return slot;
|
|
24750
|
+
if (id === 0 && free === -1) free = slot;
|
|
24751
|
+
}
|
|
24752
|
+
if (free !== -1) owners.ids[free] = owner;
|
|
24753
|
+
return free;
|
|
24754
|
+
}
|
|
24755
|
+
/**
|
|
24756
|
+
* Counts one filter apply of a frame: its pixels (width times height times resolution squared), its interned kind and
|
|
24757
|
+
* its owner, into the frame's counters, the kinds' and the owner table, and notes the kind and the owner being applied
|
|
24758
|
+
* so a pass begun inside is named by them. A plain function apart from the class: indexed stores only.
|
|
24759
|
+
*
|
|
24760
|
+
* @param sinks - The sinks.
|
|
24761
|
+
* @param filter - The filter applied.
|
|
24762
|
+
* @param input - Its input texture.
|
|
24763
|
+
*/
|
|
24764
|
+
function countApply(sinks, filter, input) {
|
|
24765
|
+
const { counts, owners } = sinks;
|
|
24766
|
+
const resolution = input.source.resolution;
|
|
24767
|
+
const pixels = input.frame.width * input.frame.height * resolution * resolution;
|
|
24768
|
+
const kind = internKind(sinks, sinks.filterKinds?.get(filter));
|
|
24769
|
+
const owner = sinks.filterOwners?.get(filter) ?? 0;
|
|
24770
|
+
const slot = owner === 0 ? -1 : ownerSlot(owners, owner);
|
|
24771
|
+
counts[COUNT.filterApplies] = cellF64(counts, COUNT.filterApplies) + 1;
|
|
24772
|
+
counts[COUNT.filterPixels] = cellF64(counts, COUNT.filterPixels) + pixels;
|
|
24773
|
+
sinks.kindApplies[kind] = cellF64(sinks.kindApplies, kind) + 1;
|
|
24774
|
+
sinks.kindPixels[kind] = cellF64(sinks.kindPixels, kind) + pixels;
|
|
24775
|
+
sinks.filterKind = kind;
|
|
24776
|
+
sinks.filterOwner = owner;
|
|
24777
|
+
if (slot !== -1) {
|
|
24778
|
+
owners.applies[slot] = cellF64(owners.applies, slot) + 1;
|
|
24779
|
+
owners.pixels[slot] = cellF64(owners.pixels, slot) + pixels;
|
|
24780
|
+
owners.kinds[slot] = kind + 1;
|
|
24781
|
+
}
|
|
24782
|
+
}
|
|
24783
|
+
/**
|
|
24784
|
+
* Tells whether a source is a render target: it has no upload method, as `countUpload` tells them apart.
|
|
24785
|
+
*
|
|
24786
|
+
* @param source - The source.
|
|
24787
|
+
* @returns True for a render target.
|
|
24788
|
+
*/
|
|
24789
|
+
function isTarget(source) {
|
|
24790
|
+
return source.uploadMethodId === "unknown";
|
|
24791
|
+
}
|
|
24792
|
+
/**
|
|
23756
24793
|
* The bytes one upload of a source sends: 4 a pixel, or for a compressed source the sum of its
|
|
23757
24794
|
* levels' `byteLength`.
|
|
23758
24795
|
*
|
|
@@ -23778,8 +24815,8 @@ function countUpload(sinks, source) {
|
|
|
23778
24815
|
if (source.uploadMethodId === "unknown") return;
|
|
23779
24816
|
if (source.resource === null || source.resource === void 0) return;
|
|
23780
24817
|
const { counts } = sinks;
|
|
23781
|
-
counts[COUNT.uploads] =
|
|
23782
|
-
counts[COUNT.uploadBytes] =
|
|
24818
|
+
counts[COUNT.uploads] = cellF64(counts, COUNT.uploads) + 1;
|
|
24819
|
+
counts[COUNT.uploadBytes] = cellF64(counts, COUNT.uploadBytes) + uploadBytes(source);
|
|
23783
24820
|
}
|
|
23784
24821
|
/**
|
|
23785
24822
|
* Builds the seven counting subclasses from the module object. They are made at call time,
|
|
@@ -23804,7 +24841,7 @@ function createCountingClasses(pixi, sinks) {
|
|
|
23804
24841
|
* @param instructionSet - The instruction set being rebuilt.
|
|
23805
24842
|
*/
|
|
23806
24843
|
buildStart(instructionSet) {
|
|
23807
|
-
if (sinks.inFrame) sinks.counts[COUNT.rebuilds] =
|
|
24844
|
+
if (sinks.inFrame) sinks.counts[COUNT.rebuilds] = cellF64(sinks.counts, COUNT.rebuilds) + 1;
|
|
23808
24845
|
sinks.rebuildsWindow += 1;
|
|
23809
24846
|
super.buildStart(instructionSet);
|
|
23810
24847
|
}
|
|
@@ -23885,8 +24922,8 @@ function createCountingClasses(pixi, sinks) {
|
|
|
23885
24922
|
if (sinks.inFrame) {
|
|
23886
24923
|
const { counts } = sinks;
|
|
23887
24924
|
const { descriptor } = gpuRenderTarget;
|
|
23888
|
-
counts[COUNT.passes] =
|
|
23889
|
-
if (descriptor.depthStencilAttachment !== void 0) counts[COUNT.stencilPasses] =
|
|
24925
|
+
counts[COUNT.passes] = cellF64(counts, COUNT.passes) + 1;
|
|
24926
|
+
if (descriptor.depthStencilAttachment !== void 0) counts[COUNT.stencilPasses] = cellF64(counts, COUNT.stencilPasses) + 1;
|
|
23890
24927
|
sinks.passes?.begin(descriptor, this.owner.renderTarget.renderTarget);
|
|
23891
24928
|
}
|
|
23892
24929
|
return super.beginRenderPass(gpuRenderTarget);
|
|
@@ -23905,34 +24942,50 @@ function createCountingClasses(pixi, sinks) {
|
|
|
23905
24942
|
class CountingFilterSystem extends pixi.FilterSystem {
|
|
23906
24943
|
static extension = pixi.FilterSystem.extension;
|
|
23907
24944
|
/**
|
|
23908
|
-
* Counts the apply and its area inside a frame, notes the kind being applied so a pass begun
|
|
23909
|
-
* inside is a filter pass named by
|
|
24945
|
+
* Counts the apply and its area inside a frame, notes the kind and the owner being applied so a pass begun
|
|
24946
|
+
* inside is a filter pass named by them, then applies. Its four arguments are named, not spread: a rest array
|
|
24947
|
+
* would be made at every apply.
|
|
23910
24948
|
*
|
|
23911
|
-
* @param
|
|
24949
|
+
* @param filter - The filter.
|
|
24950
|
+
* @param input - Its input texture.
|
|
24951
|
+
* @param output - Where it draws.
|
|
24952
|
+
* @param clear - Whether the output is cleared first.
|
|
23912
24953
|
*/
|
|
23913
|
-
applyFilter(
|
|
23914
|
-
|
|
23915
|
-
if (sinks.inFrame) {
|
|
23916
|
-
const { counts } = sinks;
|
|
23917
|
-
const pixels = input.frame.width * input.frame.height * input.source.resolution ** 2;
|
|
23918
|
-
const kind = internKind(sinks, sinks.filterKinds?.get(filter));
|
|
23919
|
-
counts[COUNT.filterApplies] = cell(counts, COUNT.filterApplies) + 1;
|
|
23920
|
-
counts[COUNT.filterPixels] = cell(counts, COUNT.filterPixels) + pixels;
|
|
23921
|
-
sinks.kindApplies[kind] = cell(sinks.kindApplies, kind) + 1;
|
|
23922
|
-
sinks.kindPixels[kind] = cell(sinks.kindPixels, kind) + pixels;
|
|
23923
|
-
sinks.filterKind = kind;
|
|
23924
|
-
}
|
|
24954
|
+
applyFilter(filter, input, output, clear) {
|
|
24955
|
+
if (sinks.inFrame) countApply(sinks, filter, input);
|
|
23925
24956
|
sinks.filterDepth += 1;
|
|
23926
24957
|
try {
|
|
23927
|
-
super.applyFilter(
|
|
24958
|
+
super.applyFilter(filter, input, output, clear);
|
|
23928
24959
|
} finally {
|
|
23929
24960
|
sinks.filterDepth -= 1;
|
|
23930
24961
|
}
|
|
23931
24962
|
}
|
|
23932
24963
|
}
|
|
23933
|
-
/**
|
|
24964
|
+
/**
|
|
24965
|
+
* Counts every upload of the WebGPU texture system, and every render target it creates and drops: a target's
|
|
24966
|
+
* source is created once in `initSource` and dropped once in `onSourceUnload` (a destroy, a pool trim, a lost
|
|
24967
|
+
* device). Pixi calls `initSource` again for a source it already made (it answers the same texture then): the set of
|
|
24968
|
+
* the targets made tells a new one.
|
|
24969
|
+
*/
|
|
23934
24970
|
class CountingGpuTextureSystem extends pixi.GpuTextureSystem {
|
|
23935
24971
|
static extension = pixi.GpuTextureSystem.extension;
|
|
24972
|
+
/** The render-target sources this system made and has not dropped. */
|
|
24973
|
+
targets = /* @__PURE__ */ new WeakSet();
|
|
24974
|
+
/**
|
|
24975
|
+
* Makes the GPU texture of a source, and counts a render target made for the first time.
|
|
24976
|
+
*
|
|
24977
|
+
* @param source - The source.
|
|
24978
|
+
* @returns The GPU texture.
|
|
24979
|
+
*/
|
|
24980
|
+
initSource(source) {
|
|
24981
|
+
const made = isTarget(source) && !this.targets.has(source);
|
|
24982
|
+
const texture = super.initSource(source);
|
|
24983
|
+
if (made) {
|
|
24984
|
+
this.targets.add(source);
|
|
24985
|
+
sinks.counts[COUNT.targetsCreated] = cellF64(sinks.counts, COUNT.targetsCreated) + 1;
|
|
24986
|
+
}
|
|
24987
|
+
return texture;
|
|
24988
|
+
}
|
|
23936
24989
|
/**
|
|
23937
24990
|
* Uploads the source, then counts it; the first upload goes through here too.
|
|
23938
24991
|
*
|
|
@@ -23942,6 +24995,15 @@ function createCountingClasses(pixi, sinks) {
|
|
|
23942
24995
|
super.onSourceUpdate(source);
|
|
23943
24996
|
countUpload(sinks, source);
|
|
23944
24997
|
}
|
|
24998
|
+
/**
|
|
24999
|
+
* Drops the source's GPU texture, and counts a render target dropped.
|
|
25000
|
+
*
|
|
25001
|
+
* @param source - The source.
|
|
25002
|
+
*/
|
|
25003
|
+
onSourceUnload(source) {
|
|
25004
|
+
if (this.targets.delete(source)) sinks.counts[COUNT.targetsDestroyed] = cellF64(sinks.counts, COUNT.targetsDestroyed) + 1;
|
|
25005
|
+
super.onSourceUnload(source);
|
|
25006
|
+
}
|
|
23945
25007
|
}
|
|
23946
25008
|
/** Counts every upload of the WebGL texture system. */
|
|
23947
25009
|
class CountingGlTextureSystem extends pixi.GlTextureSystem {
|
|
@@ -24034,7 +25096,7 @@ function installPassCounting(pixi, sinks) {
|
|
|
24034
25096
|
* @returns The pass encoder.
|
|
24035
25097
|
*/
|
|
24036
25098
|
beginRenderPass(gpuRenderTarget) {
|
|
24037
|
-
if (sinks.inFrame) sinks.counts[COUNT.passes] =
|
|
25099
|
+
if (sinks.inFrame) sinks.counts[COUNT.passes] = cellF64(sinks.counts, COUNT.passes) + 1;
|
|
24038
25100
|
return super.beginRenderPass(gpuRenderTarget);
|
|
24039
25101
|
}
|
|
24040
25102
|
}
|
|
@@ -24370,6 +25432,162 @@ function layerOf(sctx, entity) {
|
|
|
24370
25432
|
return ecs.get(top, Layer)?.name;
|
|
24371
25433
|
}
|
|
24372
25434
|
//#endregion
|
|
25435
|
+
//#region src/plugins/renderer/sync/overdraw.ts
|
|
25436
|
+
/** The alpha one layer adds: 16 layers make white. */
|
|
25437
|
+
const LAYER_ALPHA = 1 / 16;
|
|
25438
|
+
/** The blend mode whose alpha adds up: `src + dst`. */
|
|
25439
|
+
const ADDITIVE = "add-npm";
|
|
25440
|
+
/**
|
|
25441
|
+
* The colour matrix of the heat map: red, green and blue are the frame's alpha, the alpha is 1. Rows of five: the
|
|
25442
|
+
* weights of red, green, blue and alpha, and an offset.
|
|
25443
|
+
*
|
|
25444
|
+
* @returns A fresh matrix.
|
|
25445
|
+
*/
|
|
25446
|
+
function alphaToGrey() {
|
|
25447
|
+
return [
|
|
25448
|
+
0,
|
|
25449
|
+
0,
|
|
25450
|
+
0,
|
|
25451
|
+
1,
|
|
25452
|
+
0,
|
|
25453
|
+
0,
|
|
25454
|
+
0,
|
|
25455
|
+
0,
|
|
25456
|
+
1,
|
|
25457
|
+
0,
|
|
25458
|
+
0,
|
|
25459
|
+
0,
|
|
25460
|
+
0,
|
|
25461
|
+
1,
|
|
25462
|
+
0,
|
|
25463
|
+
0,
|
|
25464
|
+
0,
|
|
25465
|
+
0,
|
|
25466
|
+
0,
|
|
25467
|
+
1
|
|
25468
|
+
];
|
|
25469
|
+
}
|
|
25470
|
+
/**
|
|
25471
|
+
* Creates the overdraw state: off, an empty restore table, no matrix yet.
|
|
25472
|
+
*
|
|
25473
|
+
* @returns The state.
|
|
25474
|
+
*/
|
|
25475
|
+
function createOverdrawState() {
|
|
25476
|
+
return {
|
|
25477
|
+
nodes: [],
|
|
25478
|
+
alphas: [],
|
|
25479
|
+
blends: [],
|
|
25480
|
+
filters: [],
|
|
25481
|
+
rootFilters: void 0,
|
|
25482
|
+
matrix: void 0,
|
|
25483
|
+
listeners: []
|
|
25484
|
+
};
|
|
25485
|
+
}
|
|
25486
|
+
/**
|
|
25487
|
+
* The filters a node holds now: Pixi answers `undefined` before the first write and `null` after a clear.
|
|
25488
|
+
*
|
|
25489
|
+
* @param node - The node.
|
|
25490
|
+
* @returns The filters, or `undefined` when it holds none.
|
|
25491
|
+
*/
|
|
25492
|
+
function heldBy(node) {
|
|
25493
|
+
const held = node.filters;
|
|
25494
|
+
return held === null || held === void 0 || held.length === 0 ? void 0 : held;
|
|
25495
|
+
}
|
|
25496
|
+
/**
|
|
25497
|
+
* Notes a node's alpha, blend mode and filters in the restore table and gives it the overdraw state.
|
|
25498
|
+
*
|
|
25499
|
+
* @param state - The overdraw state.
|
|
25500
|
+
* @param node - The node.
|
|
25501
|
+
* @param alpha - Its overdraw alpha when it draws at all.
|
|
25502
|
+
*/
|
|
25503
|
+
function swapNode(state, node, alpha) {
|
|
25504
|
+
const filters = heldBy(node);
|
|
25505
|
+
state.nodes.push(node);
|
|
25506
|
+
state.alphas.push(node.alpha);
|
|
25507
|
+
state.blends.push(node.blendMode);
|
|
25508
|
+
state.filters.push(filters);
|
|
25509
|
+
if (node.alpha > 0) node.alpha = alpha;
|
|
25510
|
+
node.blendMode = ADDITIVE;
|
|
25511
|
+
if (filters !== void 0) node.filters = null;
|
|
25512
|
+
}
|
|
25513
|
+
/**
|
|
25514
|
+
* Gives a subtree the overdraw state, a node that is not drawn (invisible, or not renderable) left as it is with its
|
|
25515
|
+
* children.
|
|
25516
|
+
*
|
|
25517
|
+
* @param state - The overdraw state.
|
|
25518
|
+
* @param top - The subtree's top.
|
|
25519
|
+
* @param alpha - The top's overdraw alpha.
|
|
25520
|
+
*/
|
|
25521
|
+
function swapTree(state, top, alpha) {
|
|
25522
|
+
const open = [top];
|
|
25523
|
+
for (let node = open.pop(); node !== void 0; node = open.pop()) {
|
|
25524
|
+
if (!node.visible || !node.renderable) continue;
|
|
25525
|
+
swapNode(state, node, node === top ? alpha : 1);
|
|
25526
|
+
for (const child of node.children) open.push(child);
|
|
25527
|
+
}
|
|
25528
|
+
}
|
|
25529
|
+
/**
|
|
25530
|
+
* The colour matrix of the heat map, made once per Pixi module.
|
|
25531
|
+
*
|
|
25532
|
+
* @param state - The overdraw state.
|
|
25533
|
+
* @param pixi - The Pixi module.
|
|
25534
|
+
* @returns The filter.
|
|
25535
|
+
*/
|
|
25536
|
+
function matrixOf(state, pixi) {
|
|
25537
|
+
if (state.matrix !== void 0) return state.matrix;
|
|
25538
|
+
const matrix = new pixi.ColorMatrixFilter();
|
|
25539
|
+
matrix.matrix = alphaToGrey();
|
|
25540
|
+
state.matrix = matrix;
|
|
25541
|
+
return matrix;
|
|
25542
|
+
}
|
|
25543
|
+
/**
|
|
25544
|
+
* Puts every node back as the restore table holds it, the root's filters last; the table is empty after.
|
|
25545
|
+
*
|
|
25546
|
+
* @param state - The overdraw state.
|
|
25547
|
+
* @param root - The root.
|
|
25548
|
+
*/
|
|
25549
|
+
function restoreAll(state, root) {
|
|
25550
|
+
for (let index = state.nodes.length - 1; index >= 0; index -= 1) {
|
|
25551
|
+
const node = state.nodes[index];
|
|
25552
|
+
if (node === void 0) continue;
|
|
25553
|
+
node.alpha = state.alphas[index] ?? 1;
|
|
25554
|
+
node.blendMode = state.blends[index] ?? "inherit";
|
|
25555
|
+
const filters = state.filters[index];
|
|
25556
|
+
if (filters !== void 0) node.filters = [...filters];
|
|
25557
|
+
}
|
|
25558
|
+
root.filters = state.rootFilters === void 0 ? null : [...state.rootFilters];
|
|
25559
|
+
state.nodes.length = 0;
|
|
25560
|
+
state.alphas.length = 0;
|
|
25561
|
+
state.blends.length = 0;
|
|
25562
|
+
state.filters.length = 0;
|
|
25563
|
+
state.rootFilters = void 0;
|
|
25564
|
+
}
|
|
25565
|
+
/**
|
|
25566
|
+
* Draws the stage pass as the overdraw heat map: every kept picture live, every layer and node in the overdraw state
|
|
25567
|
+
* and the matrix on the root for the one `render` call, and everything back right after it, also when it throws.
|
|
25568
|
+
* Without a root or a Pixi module it only renders.
|
|
25569
|
+
*
|
|
25570
|
+
* @param sctx - Domain context of the sync module.
|
|
25571
|
+
* @param render - The stage pass.
|
|
25572
|
+
* @returns What `render` answered.
|
|
25573
|
+
*/
|
|
25574
|
+
function drawOverdraw(sctx, render) {
|
|
25575
|
+
const { root } = sctx.ctx.state.sync;
|
|
25576
|
+
const pixi = sctx.deps.host.pixi();
|
|
25577
|
+
if (root === void 0 || pixi === void 0) return render();
|
|
25578
|
+
const state = sctx.ctx.state.sync.overdraw;
|
|
25579
|
+
return sctx.deps.kept.live(() => {
|
|
25580
|
+
try {
|
|
25581
|
+
state.rootFilters = heldBy(root);
|
|
25582
|
+
for (const layer of root.children) swapTree(state, layer, LAYER_ALPHA);
|
|
25583
|
+
root.filters = [matrixOf(state, pixi)];
|
|
25584
|
+
return render();
|
|
25585
|
+
} finally {
|
|
25586
|
+
restoreAll(state, root);
|
|
25587
|
+
}
|
|
25588
|
+
});
|
|
25589
|
+
}
|
|
25590
|
+
//#endregion
|
|
24373
25591
|
//#region src/plugins/renderer/sync/overlay-look.ts
|
|
24374
25592
|
/**
|
|
24375
25593
|
* Makes the dim of one overlay: a sprite of Pixi's white texture, never a view.
|
|
@@ -24569,7 +25787,7 @@ function stepParts(sctx) {
|
|
|
24569
25787
|
if (overlay.parts.size === 0 && overlay.offTops.size === 0) return;
|
|
24570
25788
|
const hud = hudLayerOf(state);
|
|
24571
25789
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
24572
|
-
overlay.nextOff.clear();
|
|
25790
|
+
if (overlay.nextOff.size > 0) overlay.nextOff.clear();
|
|
24573
25791
|
for (const entity of overlay.parts) {
|
|
24574
25792
|
const view = state.views.get(entity);
|
|
24575
25793
|
if (view === void 0) continue;
|
|
@@ -24942,7 +26160,7 @@ function stepOverlays(sctx) {
|
|
|
24942
26160
|
const overlay = state.overlay;
|
|
24943
26161
|
if (state.root === void 0 || overlaysIdle(overlay)) return;
|
|
24944
26162
|
const wholes = sctx.deps.demand.wholes();
|
|
24945
|
-
overlay.flipped.clear();
|
|
26163
|
+
if (overlay.flipped.size > 0) overlay.flipped.clear();
|
|
24946
26164
|
if (wholes.has("layers")) clearRange(sctx);
|
|
24947
26165
|
for (const record of overlay.records.values()) record.seen = false;
|
|
24948
26166
|
for (const entity of overlay.entities) seeOverlay(sctx, entity);
|
|
@@ -25445,7 +26663,7 @@ function createSyncApi(ctx, deps) {
|
|
|
25445
26663
|
* view. A still frame (nothing added, removed or left without a view) builds no set and walks no entry.
|
|
25446
26664
|
*/
|
|
25447
26665
|
const pass = () => {
|
|
25448
|
-
state.held.clear();
|
|
26666
|
+
if (state.held.size > 0) state.held.clear();
|
|
25449
26667
|
if (state.removed.size > 0) {
|
|
25450
26668
|
for (const entity of state.removed) {
|
|
25451
26669
|
guard(entity, () => dropView(sctx, entity));
|
|
@@ -25561,6 +26779,16 @@ function createSyncApi(ctx, deps) {
|
|
|
25561
26779
|
},
|
|
25562
26780
|
verifyFrames: (on) => deps.demand.verifyFrames(on),
|
|
25563
26781
|
kept: (on) => deps.kept.enabled(on),
|
|
26782
|
+
overdraw: (on) => {
|
|
26783
|
+
const next = on && (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__);
|
|
26784
|
+
if (state.debug.overdraw === next) return;
|
|
26785
|
+
state.debug.overdraw = next;
|
|
26786
|
+
deps.demand.request();
|
|
26787
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
26788
|
+
ctx.log.info("renderer:overdraw", { on: next });
|
|
26789
|
+
for (const listener of state.overdraw.listeners) listener(next);
|
|
26790
|
+
}
|
|
26791
|
+
},
|
|
25564
26792
|
overlays: () => overlayReports(sctx),
|
|
25565
26793
|
state: () => ({
|
|
25566
26794
|
...state.debug,
|
|
@@ -25583,6 +26811,14 @@ function createSyncApi(ctx, deps) {
|
|
|
25583
26811
|
onWarm: (fn) => {
|
|
25584
26812
|
state.warm.listeners.push(fn);
|
|
25585
26813
|
},
|
|
26814
|
+
overdrawing: () => state.debug.overdraw,
|
|
26815
|
+
drawOverdraw: (render) => {
|
|
26816
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) return drawOverdraw(sctx, render);
|
|
26817
|
+
return render();
|
|
26818
|
+
},
|
|
26819
|
+
onOverdraw: (fn) => {
|
|
26820
|
+
state.overdraw.listeners.push(fn);
|
|
26821
|
+
},
|
|
25586
26822
|
loopsHeld: (entity, reveals) => loopsHeld(sctx, entity) || unseenHolds(sctx, entity, reveals),
|
|
25587
26823
|
takeRedraws: () => {
|
|
25588
26824
|
const redraws = state.redraws;
|
|
@@ -26184,7 +27420,8 @@ function createRendererApi(ctx) {
|
|
|
26184
27420
|
event: monitor.event,
|
|
26185
27421
|
beginAgent: monitor.beginAgent,
|
|
26186
27422
|
endAgent: monitor.endAgent,
|
|
26187
|
-
setResolution: monitor.setResolution
|
|
27423
|
+
setResolution: monitor.setResolution,
|
|
27424
|
+
counts: monitor.counts
|
|
26188
27425
|
},
|
|
26189
27426
|
invalidate: demand.invalidate,
|
|
26190
27427
|
stats: monitor.stats,
|
|
@@ -26268,6 +27505,9 @@ function createHandlers$5(ctx) {
|
|
|
26268
27505
|
//#endregion
|
|
26269
27506
|
//#region src/plugins/renderer/sync/state.ts
|
|
26270
27507
|
/**
|
|
27508
|
+
* @file renderer/sync — state factory.
|
|
27509
|
+
*/
|
|
27510
|
+
/**
|
|
26271
27511
|
* Creates an empty collection. It lives in its own non-exported function because lint rule L5
|
|
26272
27512
|
* refuses a collection built inside an exported declaration.
|
|
26273
27513
|
*
|
|
@@ -26354,6 +27594,7 @@ function createWarmState() {
|
|
|
26354
27594
|
* @returns The sync branch of the plugin state.
|
|
26355
27595
|
*/
|
|
26356
27596
|
function createSyncState(debug) {
|
|
27597
|
+
const overdrawInDebugBuild = debug.overdraw === true && (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__);
|
|
26357
27598
|
return {
|
|
26358
27599
|
root: void 0,
|
|
26359
27600
|
layerList: void 0,
|
|
@@ -26376,13 +27617,18 @@ function createSyncState(debug) {
|
|
|
26376
27617
|
reparented: emptySet$4(),
|
|
26377
27618
|
orphans: emptySet$4(),
|
|
26378
27619
|
mismatched: emptySet$4(),
|
|
26379
|
-
debug: {
|
|
27620
|
+
debug: {
|
|
27621
|
+
nineSlice: debug.nineSlice === true,
|
|
27622
|
+
overdraw: overdrawInDebugBuild
|
|
27623
|
+
},
|
|
27624
|
+
overdraw: createOverdrawState(),
|
|
26380
27625
|
outlinesStale: false,
|
|
26381
27626
|
filters: emptyMap$6(),
|
|
26382
27627
|
areas: emptyMap$6(),
|
|
26383
27628
|
viewlessEntries: false,
|
|
26384
27629
|
warm: createWarmState(),
|
|
26385
27630
|
filterKinds: emptyWeakMap$1(),
|
|
27631
|
+
filterOwners: emptyWeakMap$1(),
|
|
26386
27632
|
objectHidden: emptyWeakMap$1(),
|
|
26387
27633
|
viewOfTop: emptyWeakMap$1(),
|
|
26388
27634
|
overlay: createOverlayState(),
|
|
@@ -26553,11 +27799,17 @@ const rendererPlugin = /*#__PURE__*/ createPlugin$1("renderer", {
|
|
|
26553
27799
|
debug: {
|
|
26554
27800
|
nineSlice: false,
|
|
26555
27801
|
verifyFrames: false,
|
|
26556
|
-
kept: true
|
|
27802
|
+
kept: true,
|
|
27803
|
+
overdraw: false
|
|
26557
27804
|
},
|
|
26558
27805
|
monitor: {
|
|
26559
27806
|
budgetMs: "rate",
|
|
26560
|
-
frames: 1024
|
|
27807
|
+
frames: 1024,
|
|
27808
|
+
stillFrames: 60,
|
|
27809
|
+
stillMpx: .25,
|
|
27810
|
+
churnPerSecond: 4,
|
|
27811
|
+
churnSeconds: 3,
|
|
27812
|
+
churnAfterTrimMs: 1e3
|
|
26561
27813
|
},
|
|
26562
27814
|
onDemand: false,
|
|
26563
27815
|
keptMb: 64,
|
|
@@ -41466,11 +42718,72 @@ function stepGestures(ctx, time) {
|
|
|
41466
42718
|
pointer.id = ctx.state.pointerId ?? -1;
|
|
41467
42719
|
}
|
|
41468
42720
|
//#endregion
|
|
42721
|
+
//#region src/plugins/input/state.ts
|
|
42722
|
+
/**
|
|
42723
|
+
* Creates the initial input state: an empty sample queue, no active pointer and no gesture. Every
|
|
42724
|
+
* duration starts at zero; the plugin never reads a device clock.
|
|
42725
|
+
*
|
|
42726
|
+
* @param _ctx - Minimal context.
|
|
42727
|
+
* @param _ctx.config - Resolved plugin config.
|
|
42728
|
+
* @returns A fresh state, owned by one app.
|
|
42729
|
+
*/
|
|
42730
|
+
function createInputState(_ctx) {
|
|
42731
|
+
return {
|
|
42732
|
+
samples: [],
|
|
42733
|
+
phase: "idle",
|
|
42734
|
+
pointerId: void 0,
|
|
42735
|
+
moved: false,
|
|
42736
|
+
entity: void 0,
|
|
42737
|
+
key: void 0,
|
|
42738
|
+
start: {
|
|
42739
|
+
x: 0,
|
|
42740
|
+
y: 0
|
|
42741
|
+
},
|
|
42742
|
+
grabOffset: {
|
|
42743
|
+
x: 0,
|
|
42744
|
+
y: 0
|
|
42745
|
+
},
|
|
42746
|
+
pressedMs: 0,
|
|
42747
|
+
hovered: void 0,
|
|
42748
|
+
pointerOver: void 0,
|
|
42749
|
+
parent: void 0,
|
|
42750
|
+
restScale: void 0,
|
|
42751
|
+
heldPose: void 0,
|
|
42752
|
+
unmute: void 0,
|
|
42753
|
+
carried: [],
|
|
42754
|
+
path: [],
|
|
42755
|
+
traceIntent: void 0,
|
|
42756
|
+
traceClosed: false,
|
|
42757
|
+
lastPoint: void 0,
|
|
42758
|
+
canvas: void 0,
|
|
42759
|
+
offFrame: void 0,
|
|
42760
|
+
detach: void 0,
|
|
42761
|
+
tapListeners: [],
|
|
42762
|
+
keyListeners: [],
|
|
42763
|
+
pointerListeners: [],
|
|
42764
|
+
detachKeys: void 0,
|
|
42765
|
+
wake: void 0,
|
|
42766
|
+
cursor: void 0,
|
|
42767
|
+
controls: []
|
|
42768
|
+
};
|
|
42769
|
+
}
|
|
42770
|
+
/**
|
|
42771
|
+
* Counts the listeners and controls other code registered: `onTap`, `onPointer`, `onKey` and
|
|
42772
|
+
* `controls.add`, four lengths, nothing allocated.
|
|
42773
|
+
*
|
|
42774
|
+
* @param state - The input state.
|
|
42775
|
+
* @returns The number of registrations whose remover has not run.
|
|
42776
|
+
*/
|
|
42777
|
+
function countListeners(state) {
|
|
42778
|
+
return state.tapListeners.length + state.pointerListeners.length + state.keyListeners.length + state.controls.length;
|
|
42779
|
+
}
|
|
42780
|
+
//#endregion
|
|
41469
42781
|
//#region src/plugins/input/lifecycle.ts
|
|
41470
42782
|
/**
|
|
41471
42783
|
* @file input plugin — lifecycle functions: dependency resolution, the frame step registered in
|
|
41472
|
-
* `onInit`, the pointer listeners opened in `onStart` and
|
|
41473
|
-
*
|
|
42784
|
+
* `onInit`, the pointer listeners opened in `onStart` (and, in a debug build, the count of the
|
|
42785
|
+
* listeners other code registered, pushed into the renderer's monitor) and the teardown that
|
|
42786
|
+
* closes exactly what was opened.
|
|
41474
42787
|
*/
|
|
41475
42788
|
/**
|
|
41476
42789
|
* Resolves the dependency APIs `time`, `flow`, `world` and `renderer` with `ctx.require`.
|
|
@@ -41514,12 +42827,17 @@ function initInput(ctx) {
|
|
|
41514
42827
|
/**
|
|
41515
42828
|
* Opens the pointer listeners on the canvas the renderer created and the one `keydown` listener
|
|
41516
42829
|
* on `window`. Without a DOM the renderer has no canvas: nothing is attached, the plugin is
|
|
41517
|
-
* inert, and `app.input.*` still answers the gate.
|
|
42830
|
+
* inert, and `app.input.*` still answers the gate. In an agent build the count of the listeners
|
|
42831
|
+
* and controls other code registered goes into the renderer monitor's leak counts as
|
|
42832
|
+
* `inputListeners`, pushed: the renderer cannot depend on input, which depends on it. The
|
|
42833
|
+
* renderer's `onStop` drops the registration.
|
|
41518
42834
|
*
|
|
41519
42835
|
* @param ctx - Kernel context of the input plugin.
|
|
41520
42836
|
*/
|
|
41521
42837
|
function startInput(ctx) {
|
|
41522
|
-
const
|
|
42838
|
+
const renderer = ctx.require(rendererPlugin);
|
|
42839
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) renderer.monitor.counts.register("inputListeners", () => countListeners(ctx.state));
|
|
42840
|
+
const canvas = renderer.host.canvas();
|
|
41523
42841
|
ctx.state.canvas = canvas;
|
|
41524
42842
|
if (canvas === void 0) return;
|
|
41525
42843
|
attach(canvas, ctx);
|
|
@@ -41677,7 +42995,7 @@ function answerTrace(ctx, path) {
|
|
|
41677
42995
|
}
|
|
41678
42996
|
/**
|
|
41679
42997
|
* Creates the input API: `app.input.tap`, `.press`, `.drag`, `.swipe`, `.trace`, `.onTap`,
|
|
41680
|
-
* `.onPointer`, `.onKey`, `.pressKey`, `.cursor` and `.controls`.
|
|
42998
|
+
* `.onPointer`, `.onKey`, `.pressKey`, `.cursor`, `.listeners` and `.controls`.
|
|
41681
42999
|
*
|
|
41682
43000
|
* @param ctx - Kernel context of the input plugin.
|
|
41683
43001
|
* @returns The plugin API.
|
|
@@ -41698,59 +43016,10 @@ function createInputApi(ctx) {
|
|
|
41698
43016
|
shift: options?.shift ?? false
|
|
41699
43017
|
}),
|
|
41700
43018
|
cursor: () => inputCtx.state.cursor ?? inputCtx.config.cursor.idle,
|
|
43019
|
+
listeners: () => countListeners(inputCtx.state),
|
|
41701
43020
|
controls: { add: (component) => addControl(inputCtx.state, component) }
|
|
41702
43021
|
};
|
|
41703
43022
|
}
|
|
41704
|
-
//#endregion
|
|
41705
|
-
//#region src/plugins/input/state.ts
|
|
41706
|
-
/**
|
|
41707
|
-
* Creates the initial input state: an empty sample queue, no active pointer and no gesture. Every
|
|
41708
|
-
* duration starts at zero; the plugin never reads a device clock.
|
|
41709
|
-
*
|
|
41710
|
-
* @param _ctx - Minimal context.
|
|
41711
|
-
* @param _ctx.config - Resolved plugin config.
|
|
41712
|
-
* @returns A fresh state, owned by one app.
|
|
41713
|
-
*/
|
|
41714
|
-
function createInputState(_ctx) {
|
|
41715
|
-
return {
|
|
41716
|
-
samples: [],
|
|
41717
|
-
phase: "idle",
|
|
41718
|
-
pointerId: void 0,
|
|
41719
|
-
moved: false,
|
|
41720
|
-
entity: void 0,
|
|
41721
|
-
key: void 0,
|
|
41722
|
-
start: {
|
|
41723
|
-
x: 0,
|
|
41724
|
-
y: 0
|
|
41725
|
-
},
|
|
41726
|
-
grabOffset: {
|
|
41727
|
-
x: 0,
|
|
41728
|
-
y: 0
|
|
41729
|
-
},
|
|
41730
|
-
pressedMs: 0,
|
|
41731
|
-
hovered: void 0,
|
|
41732
|
-
pointerOver: void 0,
|
|
41733
|
-
parent: void 0,
|
|
41734
|
-
restScale: void 0,
|
|
41735
|
-
heldPose: void 0,
|
|
41736
|
-
unmute: void 0,
|
|
41737
|
-
carried: [],
|
|
41738
|
-
path: [],
|
|
41739
|
-
traceIntent: void 0,
|
|
41740
|
-
traceClosed: false,
|
|
41741
|
-
lastPoint: void 0,
|
|
41742
|
-
canvas: void 0,
|
|
41743
|
-
offFrame: void 0,
|
|
41744
|
-
detach: void 0,
|
|
41745
|
-
tapListeners: [],
|
|
41746
|
-
keyListeners: [],
|
|
41747
|
-
pointerListeners: [],
|
|
41748
|
-
detachKeys: void 0,
|
|
41749
|
-
wake: void 0,
|
|
41750
|
-
cursor: void 0,
|
|
41751
|
-
controls: []
|
|
41752
|
-
};
|
|
41753
|
-
}
|
|
41754
43023
|
/**
|
|
41755
43024
|
* Input plugin: `app.input.tap(target)`, `app.input.drag(from, to)`.
|
|
41756
43025
|
*
|
|
@@ -54381,9 +55650,9 @@ function defineEffect(kind, options = {}) {
|
|
|
54381
55650
|
* | renderer | poolLimit | 256 |
|
|
54382
55651
|
* | renderer | unsupportedMessage | "This device cannot run the game." |
|
|
54383
55652
|
* | renderer | loadPixi | () => import("pixi.js") |
|
|
54384
|
-
* | renderer | debug | { nineSlice: false, verifyFrames: false, kept: true }; shallow merge: a game that sets `debug` replaces the whole object, and a switch it leaves out is off, except `kept`, which stays on unless set to false |
|
|
55653
|
+
* | renderer | debug | { nineSlice: false, verifyFrames: false, kept: true, overdraw: false }; shallow merge: a game that sets `debug` replaces the whole object, and a switch it leaves out is off, except `kept`, which stays on unless set to false; `verifyFrames` and `overdraw` work in dev and debug builds only |
|
|
54385
55654
|
* | renderer | onDemand | false, every frame is drawn; true draws only a frame in which something changed |
|
|
54386
|
-
* | renderer | monitor | { budgetMs: "rate", frames: 1024 }, the performance log of an agent build; shallow merge: a game that sets `monitor` gives
|
|
55655
|
+
* | renderer | monitor | { budgetMs: "rate", frames: 1024, stillFrames: 60, stillMpx: 0.25, churnPerSecond: 4, churnSeconds: 3, churnAfterTrimMs: 1000 }, the performance log of an agent build and the thresholds of its `problem` rules; shallow merge: a game that sets `monitor` gives `budgetMs` and `frames`, a threshold it leaves out keeps its default |
|
|
54387
55656
|
* | renderer | keptMb | 64, MiB the kept pictures may hold: a filtered view that did not change is drawn from its picture, the least recently drawn goes first; 0 keeps none |
|
|
54388
55657
|
* | renderer | warm | 1, probe draws a drawn frame that create the GPU pipelines while the game loads; 0 creates each in the first frame that needs it |
|
|
54389
55658
|
* | input | tapSlopPx | 12 |
|
|
@@ -54543,4 +55812,4 @@ const screen = [
|
|
|
54543
55812
|
uiPlugin
|
|
54544
55813
|
];
|
|
54545
55814
|
//#endregion
|
|
54546
|
-
export { parallel as $,
|
|
55815
|
+
export { parallel as $, DEMAND_REASONS as $t, types_exports$12 as A, Blur as At, DebugTrigger as B, assetsPlugin as Bt, types_exports$5 as C, Traced as Ct, types_exports$9 as D, effectsPlugin as Dt, types_exports$8 as E, messageDuration as Et, types_exports$17 as F, Outline as Ft, textPlugin as G, rendererPlugin as Gt, popup as H, AnimPlayer as Ht, uiPlugin as I, defineFilter as It, defineAnimation as J, PERF_CAUSES as Jt, platformPlugin as K, PASS_KINDS as Kt, resolve as L, debugPlugin as Lt, types_exports$14 as M, Displacement as Mt, types_exports$15 as N, Glow as Nt, types_exports$10 as O, Emitter as Ot, types_exports$16 as P, Noise as Pt, mark as Q, SHADER_WHATS as Qt, defineMotion as R, audioPlugin as Rt, types_exports$4 as S, Traceable as St, types_exports$7 as T, messageArgument as Tt, defineTokens as U, Animation as Ut, LocalWrite as V, animPlugin as Vt, defineStyle as W, Frames as Wt, frames as X, PERF_LOG_VERSION as Xt, external as Y, PERF_GAP_BUCKETS as Yt, haptic as Z, PROBLEM_RULES as Zt, load as _, Pressable as _t, defineEffect as a, to as an, spawned as at, types_exports$2 as b, Tappable as bt, schedule as c, SaveUnreadableError as cn, use as ct, tr as d, clockPlugin as dn, Draggable as dt, worldPlugin as en, play$1 as et, defineFeature as f, DropTarget as ft, defineBundles as g, PointerOver as gt, music as h, Pointer as ht, screen as i, slot as in, spawn as it, types_exports$13 as j, ColorMatrix as jt, types_exports$11 as k, Alpha as kt, projection as l, lifecyclePlugin as ln, wait as lt, defineEmitter as m, Hovered as mt, createPlugin as n, refuseStart as nn, set as nt, guide as o, type as on, stagger as ot, flattenFeatures as p, Held as pt, HAPTIC_KINDS as q, frameAt as qt, defineGame as r, exit as rn, sfx as rt, hint as s, modelPlugin as sn, tween as st, createApp as t, flowPlugin as tn, sequence as tt, defineScene as u, timePlugin as un, inputPlugin as ut, types_exports as v, Pressed as vt, types_exports$6 as w, i18nPlugin as wt, types_exports$3 as x, Touchable as xt, types_exports$1 as y, Swipeable as yt, Box as z, scenesPlugin as zt };
|