@moku-labs/game 0.15.6 → 0.15.7
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/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/{commands-CAEwtEAy.d.mts → commands-C8aWbThW.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/control.mjs +1 -1
- package/dist/{define-CNctRrea.d.mts → define-Bebctfvs.d.mts} +2 -2
- package/dist/{headless-DDkRwLnJ.d.mts → headless-DobgwTKK.d.mts} +1 -1
- package/dist/{index-DytzVJFz.d.mts → index-CU4Oc2Qv.d.mts} +10 -8
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +6 -6
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{run-C07NXds7.mjs → run-DlxDjgOO.mjs} +1 -1
- package/dist/{runtime-BGsDXKuO.d.mts → runtime-Bm_guAJR.d.mts} +1 -1
- package/dist/{src-DWJabPi4.mjs → src-nJYt0LMr.mjs} +1223 -225
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-C2LZJIxH.d.mts → types-BkcgFOoB.d.mts} +33 -11
- package/dist/{types-DnttyLeu.d.mts → types-C4D9UVgb.d.mts} +121 -35
- package/dist/{types-CWSMo70I.d.mts → types-CZGuMhV8.d.mts} +1 -1
- package/dist/{types-DFoxDq-g.d.mts → types-Mj75CDO6.d.mts} +2 -2
- package/dist/{types-CRgEt4wq.d.mts → types-MocH_igX.d.mts} +4 -4
- package/dist/{visual-gTuXlmvH.d.mts → visual-BV2A3kpr.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +1 -1
- package/package.json +1 -1
|
@@ -598,6 +598,16 @@ const DROP_SHARE = .1;
|
|
|
598
598
|
const DWELL_BASE_MS = 5e3;
|
|
599
599
|
/** The dwell never grows past this, ms. */
|
|
600
600
|
const DWELL_MAX_MS = 12e4;
|
|
601
|
+
/** The render scales the adaptive rule steps through, highest first: shares of the canvas resolution. */
|
|
602
|
+
const SCALE_STEPS = Object.freeze([
|
|
603
|
+
1,
|
|
604
|
+
.85,
|
|
605
|
+
.75,
|
|
606
|
+
.6,
|
|
607
|
+
.5
|
|
608
|
+
]);
|
|
609
|
+
/** No scale to step through: the renderer allows none. */
|
|
610
|
+
const NO_SCALES = Object.freeze([]);
|
|
601
611
|
/** The thermal statuses in order of heat: Android's `THERMAL_STATUS_*` numbers are their indexes. */
|
|
602
612
|
const THERMAL_STATUSES = [
|
|
603
613
|
"none",
|
|
@@ -632,6 +642,22 @@ function ladderOf(displayHz, maxFps, floor) {
|
|
|
632
642
|
return Object.freeze(steps);
|
|
633
643
|
}
|
|
634
644
|
/**
|
|
645
|
+
* The render scales the rule may choose down to a floor, highest first, frozen; none when 1 alone reaches it. A floor
|
|
646
|
+
* between two steps stops at the step above it (the renderer's config only takes a step). Allocates: called when the
|
|
647
|
+
* renderer allows a floor, never per frame.
|
|
648
|
+
*
|
|
649
|
+
* @param floor - The lowest scale the renderer allows.
|
|
650
|
+
* @returns The scales.
|
|
651
|
+
* @example
|
|
652
|
+
* ```ts
|
|
653
|
+
* scaleStepsOf(0.75); // [1, 0.85, 0.75]
|
|
654
|
+
* ```
|
|
655
|
+
*/
|
|
656
|
+
function scaleStepsOf(floor) {
|
|
657
|
+
const steps = SCALE_STEPS.filter((step) => step >= floor);
|
|
658
|
+
return steps.length > 1 ? Object.freeze(steps) : NO_SCALES;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
635
661
|
* The index in `DISPLAY_RATES` a tick interval votes for: the rate whose period is within `DISPLAY_TOLERANCE` of
|
|
636
662
|
* it, or -1. An indexed loop: no allocation.
|
|
637
663
|
*
|
|
@@ -789,17 +815,20 @@ function thermalTarget(ladder, status, fps) {
|
|
|
789
815
|
//#region src/plugins/time/rate.ts
|
|
790
816
|
/**
|
|
791
817
|
* @file time — the adaptive frame rate (spec 21 G3–G6): the governor that drops to the next divisor of the display
|
|
792
|
-
* rate when drawn frames come late and returns one step after steady room,
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
818
|
+
* rate when drawn frames come late and returns one step after steady room, on a GPU-bound screen the render scale
|
|
819
|
+
* stepped before the rate, the back-off of failed returns, the display rate learned from the ticks in every app (and
|
|
820
|
+
* with the governor from the native shell), the thermal input, and `setRate`. Every number of the per-frame path lives
|
|
821
|
+
* in the governor and the display state, allocated once; a frame allocates nothing here. Only a change allocates: its
|
|
822
|
+
* payload, the snapshot, the request.
|
|
796
823
|
*/
|
|
797
|
-
/** The ladder before the display rate is known, and always without `adaptive
|
|
824
|
+
/** The ladder before the display rate is known, and always without `adaptive`; the scales the renderer allows none of. */
|
|
798
825
|
const NO_STEPS = Object.freeze([]);
|
|
799
826
|
/** Bit of a sample that came late. */
|
|
800
827
|
const MISSED = 1;
|
|
801
828
|
/** Bit of a sample whose frame was heavy for the higher rate. */
|
|
802
829
|
const HEAVY = 2;
|
|
830
|
+
/** Bit of a late sample whose frame kept the main thread longer than its frame. */
|
|
831
|
+
const BUSY = 4;
|
|
803
832
|
/** The thermal rank from which the rule never steps up: `"moderate"` and every hotter status. */
|
|
804
833
|
const HEAT_LIMIT_RANK = 2;
|
|
805
834
|
/** Milliseconds in a second: turns a rate into the period of one frame. */
|
|
@@ -852,6 +881,7 @@ function createGovernor(floor) {
|
|
|
852
881
|
return {
|
|
853
882
|
floor,
|
|
854
883
|
ladder: NO_STEPS,
|
|
884
|
+
scales: NO_STEPS,
|
|
855
885
|
thermal: null,
|
|
856
886
|
lastDrawn: false,
|
|
857
887
|
lastIdle: false,
|
|
@@ -865,12 +895,14 @@ function createGovernor(floor) {
|
|
|
865
895
|
dropTail: 0,
|
|
866
896
|
dropDrawn: 0,
|
|
867
897
|
dropMissed: 0,
|
|
898
|
+
dropBusy: 0,
|
|
868
899
|
returnTail: 0,
|
|
869
900
|
returnDrawn: 0,
|
|
870
901
|
returnMissed: 0,
|
|
871
902
|
returnHeavy: 0,
|
|
872
903
|
switchedAt: Number.NEGATIVE_INFINITY,
|
|
873
904
|
lastReturnAt: Number.NEGATIVE_INFINITY,
|
|
905
|
+
climbed: false,
|
|
874
906
|
dwellMs: DWELL_BASE_MS,
|
|
875
907
|
requested: 0,
|
|
876
908
|
requestSeq: 0
|
|
@@ -906,7 +938,8 @@ function snapshotOf(rate) {
|
|
|
906
938
|
ladder: governor?.ladder ?? NO_STEPS,
|
|
907
939
|
thermal: governor?.thermal ?? null,
|
|
908
940
|
asked,
|
|
909
|
-
heldBy: asked !== null && hz !== null && asked > hz ? "panel" : null
|
|
941
|
+
heldBy: asked !== null && hz !== null && asked > hz ? "panel" : null,
|
|
942
|
+
scale: rate.scale
|
|
910
943
|
});
|
|
911
944
|
}
|
|
912
945
|
/**
|
|
@@ -923,6 +956,7 @@ function createRateState(config) {
|
|
|
923
956
|
const mode = floor === void 0 ? "fixed" : "auto";
|
|
924
957
|
const parts = {
|
|
925
958
|
fps: config.maxFps,
|
|
959
|
+
scale: 1,
|
|
926
960
|
mode,
|
|
927
961
|
reason: "start",
|
|
928
962
|
display: createDisplayState(),
|
|
@@ -953,6 +987,7 @@ function emptyWindows(g) {
|
|
|
953
987
|
g.dropTail = g.written;
|
|
954
988
|
g.dropDrawn = 0;
|
|
955
989
|
g.dropMissed = 0;
|
|
990
|
+
g.dropBusy = 0;
|
|
956
991
|
g.returnTail = g.written;
|
|
957
992
|
g.returnDrawn = 0;
|
|
958
993
|
g.returnMissed = 0;
|
|
@@ -967,6 +1002,7 @@ function dropOldest(g) {
|
|
|
967
1002
|
const flags = g.sampleFlags[g.dropTail % 512] ?? 0;
|
|
968
1003
|
g.dropDrawn -= 1;
|
|
969
1004
|
if ((flags & MISSED) !== 0) g.dropMissed -= 1;
|
|
1005
|
+
if ((flags & BUSY) !== 0) g.dropBusy -= 1;
|
|
970
1006
|
g.dropTail += 1;
|
|
971
1007
|
}
|
|
972
1008
|
/**
|
|
@@ -1022,8 +1058,19 @@ function evidenceOf(g, reason) {
|
|
|
1022
1058
|
};
|
|
1023
1059
|
}
|
|
1024
1060
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1061
|
+
* Starts the back-off over: the base dwell, no return to judge.
|
|
1062
|
+
*
|
|
1063
|
+
* @param g - The governor.
|
|
1064
|
+
*/
|
|
1065
|
+
function resetDwell(g) {
|
|
1066
|
+
g.dwellMs = DWELL_BASE_MS;
|
|
1067
|
+
g.lastReturnAt = Number.NEGATIVE_INFINITY;
|
|
1068
|
+
g.climbed = false;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* The dwell's bookkeeping of a switch: the first drop after a return judges it (held less than a minute it failed and
|
|
1072
|
+
* the dwell doubles, held longer the dwell starts over), the steps down after it leave the dwell, a return notes when
|
|
1073
|
+
* it happened, `setRate` starts it over; the display and the heat leave it.
|
|
1027
1074
|
*
|
|
1028
1075
|
* @param g - The governor.
|
|
1029
1076
|
* @param reason - The reason of the switch.
|
|
@@ -1031,19 +1078,16 @@ function evidenceOf(g, reason) {
|
|
|
1031
1078
|
*/
|
|
1032
1079
|
function keepDwell(g, reason, at) {
|
|
1033
1080
|
switch (reason) {
|
|
1034
|
-
case "missed":
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
else if (held >= 6e4) g.dwellMs = DWELL_BASE_MS;
|
|
1081
|
+
case "missed":
|
|
1082
|
+
if (g.climbed) g.dwellMs = at - g.lastReturnAt < 6e4 ? Math.min(2 * g.dwellMs, DWELL_MAX_MS) : DWELL_BASE_MS;
|
|
1083
|
+
g.climbed = false;
|
|
1038
1084
|
break;
|
|
1039
|
-
}
|
|
1040
1085
|
case "headroom":
|
|
1041
1086
|
g.lastReturnAt = at;
|
|
1087
|
+
g.climbed = true;
|
|
1042
1088
|
break;
|
|
1043
1089
|
case "forced":
|
|
1044
|
-
case "auto":
|
|
1045
|
-
g.dwellMs = DWELL_BASE_MS;
|
|
1046
|
-
g.lastReturnAt = Number.NEGATIVE_INFINITY;
|
|
1090
|
+
case "auto": resetDwell(g);
|
|
1047
1091
|
}
|
|
1048
1092
|
}
|
|
1049
1093
|
/**
|
|
@@ -1082,22 +1126,25 @@ function requestRate(ctx) {
|
|
|
1082
1126
|
followNativeRate(ctx, (answer) => readAnswer(ctx, g, seq, answer));
|
|
1083
1127
|
}
|
|
1084
1128
|
/**
|
|
1085
|
-
* The one place the rate or
|
|
1086
|
-
* when
|
|
1087
|
-
* snapshot is rebuilt, `time:rate` is emitted with the evidence, the change is logged and the shell is
|
|
1088
|
-
* new rate. The new cap applies from the next tick: the frame that is due still runs.
|
|
1129
|
+
* The one place the rate, the render scale or the mode changes (the governor, the display, the heat and `setRate`
|
|
1130
|
+
* call it). Nothing when all stay. Otherwise the dwell is kept, both windows are emptied and the next interval is
|
|
1131
|
+
* skipped, the snapshot is rebuilt, `time:rate` is emitted with the evidence, the change is logged and the shell is
|
|
1132
|
+
* asked for the new rate. The new cap applies from the next tick: the frame that is due still runs, at the new scale.
|
|
1089
1133
|
*
|
|
1090
1134
|
* @param ctx - Domain context of the time plugin.
|
|
1091
1135
|
* @param to - The new rate.
|
|
1136
|
+
* @param scale - The new render scale.
|
|
1092
1137
|
* @param mode - The new mode.
|
|
1093
1138
|
* @param reason - Why.
|
|
1094
1139
|
* @param at - When, on the page clock.
|
|
1095
1140
|
*/
|
|
1096
|
-
function switchRate(ctx, to, mode, reason, at) {
|
|
1141
|
+
function switchRate(ctx, to, scale, mode, reason, at) {
|
|
1097
1142
|
const { rate } = ctx.state;
|
|
1098
|
-
if (to === rate.fps && mode === rate.mode) return;
|
|
1143
|
+
if (to === rate.fps && scale === rate.scale && mode === rate.mode) return;
|
|
1099
1144
|
const g = rate.governor;
|
|
1100
1145
|
const from = rate.fps;
|
|
1146
|
+
const fromScale = rate.scale;
|
|
1147
|
+
const scaleOnly = to === from && mode === rate.mode;
|
|
1101
1148
|
const evidence = evidenceOf(g, reason);
|
|
1102
1149
|
if (g !== void 0) {
|
|
1103
1150
|
keepDwell(g, reason, at);
|
|
@@ -1106,6 +1153,7 @@ function switchRate(ctx, to, mode, reason, at) {
|
|
|
1106
1153
|
emptyWindows(g);
|
|
1107
1154
|
}
|
|
1108
1155
|
rate.fps = to;
|
|
1156
|
+
rate.scale = scale;
|
|
1109
1157
|
rate.mode = mode;
|
|
1110
1158
|
rate.reason = reason;
|
|
1111
1159
|
rate.snapshot = snapshotOf(rate);
|
|
@@ -1117,8 +1165,15 @@ function switchRate(ctx, to, mode, reason, at) {
|
|
|
1117
1165
|
displayHz: rate.display.hz,
|
|
1118
1166
|
thermal: g?.thermal ?? null,
|
|
1119
1167
|
window: evidence,
|
|
1120
|
-
dwellMs: g?.dwellMs ?? 0
|
|
1168
|
+
dwellMs: g?.dwellMs ?? 0,
|
|
1169
|
+
scale
|
|
1170
|
+
});
|
|
1171
|
+
if (scale !== fromScale) ctx.log.info("time: render scale", {
|
|
1172
|
+
from: fromScale,
|
|
1173
|
+
to: scale,
|
|
1174
|
+
reason
|
|
1121
1175
|
});
|
|
1176
|
+
if (scaleOnly) return;
|
|
1122
1177
|
ctx.log.info("time: frame rate", {
|
|
1123
1178
|
from,
|
|
1124
1179
|
to,
|
|
@@ -1137,7 +1192,7 @@ function applyThermal(ctx, g) {
|
|
|
1137
1192
|
const { rate } = ctx.state;
|
|
1138
1193
|
if (rate.mode !== "auto" || g.ladder.length === 0) return;
|
|
1139
1194
|
const to = thermalTarget(g.ladder, g.thermal, rate.fps);
|
|
1140
|
-
if (to < rate.fps) switchRate(ctx, to, "auto", "thermal", nowOf());
|
|
1195
|
+
if (to < rate.fps) switchRate(ctx, to, rate.scale, "auto", "thermal", nowOf());
|
|
1141
1196
|
}
|
|
1142
1197
|
/**
|
|
1143
1198
|
* Stores a new thermal status of the shell and applies it. A status the shell sent wrong, and the same status
|
|
@@ -1182,7 +1237,7 @@ function learnDisplay(ctx, hz, at) {
|
|
|
1182
1237
|
if (g !== void 0) g.ladder = ladderOf(hz, ctx.config.maxFps, g.floor);
|
|
1183
1238
|
rate.snapshot = snapshotOf(rate);
|
|
1184
1239
|
if (g === void 0 || rate.mode !== "auto") return;
|
|
1185
|
-
if (!g.ladder.includes(rate.fps)) switchRate(ctx, g.ladder.find((step) => step <= rate.fps) ?? g.ladder.at(-1) ?? rate.fps, "auto", "display", at);
|
|
1240
|
+
if (!g.ladder.includes(rate.fps)) switchRate(ctx, g.ladder.find((step) => step <= rate.fps) ?? g.ladder.at(-1) ?? rate.fps, rate.scale, "auto", "display", at);
|
|
1186
1241
|
if (first) applyThermal(ctx, g);
|
|
1187
1242
|
}
|
|
1188
1243
|
/**
|
|
@@ -1279,31 +1334,54 @@ function shouldDrop(g) {
|
|
|
1279
1334
|
return g.dropMissed >= Math.max(8, Math.ceil(DROP_SHARE * g.dropDrawn));
|
|
1280
1335
|
}
|
|
1281
1336
|
/**
|
|
1282
|
-
* Tells whether the
|
|
1283
|
-
*
|
|
1337
|
+
* Tells whether the late frames of the drop window are GPU-bound: fewer than half of them kept the main thread longer
|
|
1338
|
+
* than their frame.
|
|
1339
|
+
*
|
|
1340
|
+
* @param g - The governor.
|
|
1341
|
+
* @returns True when the scale, not the rate, should give.
|
|
1342
|
+
*/
|
|
1343
|
+
function gpuBound(g) {
|
|
1344
|
+
return 2 * g.dropBusy < g.dropMissed;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Tells whether the return window has room to step up: no heat, the dwell passed (twice the dwell for a step of the
|
|
1348
|
+
* scale), 90 drawn frames in three seconds with at most one late, and for a step of the rate a tenth heavy at most.
|
|
1284
1349
|
*
|
|
1285
1350
|
* @param g - The governor.
|
|
1286
1351
|
* @param at - The page time of the sample.
|
|
1287
|
-
* @
|
|
1352
|
+
* @param scaleStep - The step up is one of the render scale: it costs the GPU, not the main thread.
|
|
1353
|
+
* @returns True when the rule may return one step.
|
|
1288
1354
|
*/
|
|
1289
|
-
function canReturn(g, at) {
|
|
1290
|
-
return thermalRank(g.thermal) < HEAT_LIMIT_RANK && at - g.switchedAt >= g.dwellMs && g.returnDrawn >= 90 && g.returnMissed <= 1 && g.returnHeavy <= Math.floor(.1 * g.returnDrawn);
|
|
1355
|
+
function canReturn(g, at, scaleStep) {
|
|
1356
|
+
return thermalRank(g.thermal) < HEAT_LIMIT_RANK && at - g.switchedAt >= (scaleStep ? 2 * g.dwellMs : g.dwellMs) && g.returnDrawn >= 90 && g.returnMissed <= 1 && (scaleStep || g.returnHeavy <= Math.floor(.1 * g.returnDrawn));
|
|
1291
1357
|
}
|
|
1292
1358
|
/**
|
|
1293
|
-
* Decides on the windows after a sample
|
|
1294
|
-
*
|
|
1295
|
-
* no heat
|
|
1359
|
+
* Decides on the windows after a sample. Down one step when late intervals reach `max(8, 10 %)` of the second's: the
|
|
1360
|
+
* render scale when the window is GPU-bound and a lower scale is allowed, the rate otherwise. Up one step in the
|
|
1361
|
+
* reverse of the way down, with 90 steady drawn frames in three seconds, at most one late and no heat: the rate first,
|
|
1362
|
+
* at the scale it dropped at, after the dwell and with a tenth heavy at most, then the scale after twice the dwell. A
|
|
1363
|
+
* probe of the higher rate so never starts at full scale, and its failure changes no resolution.
|
|
1296
1364
|
*
|
|
1297
1365
|
* @param ctx - Domain context of the time plugin.
|
|
1298
1366
|
* @param g - The governor.
|
|
1299
1367
|
* @param at - The page time of the sample.
|
|
1300
1368
|
*/
|
|
1301
1369
|
function decide(ctx, g, at) {
|
|
1302
|
-
const { fps } = ctx.state.rate;
|
|
1303
|
-
|
|
1370
|
+
const { fps, scale } = ctx.state.rate;
|
|
1371
|
+
if (shouldDrop(g)) {
|
|
1372
|
+
const smaller = gpuBound(g) ? stepBelow(g.scales, scale) : void 0;
|
|
1373
|
+
const lower = stepBelow(g.ladder, fps);
|
|
1374
|
+
if (smaller !== void 0) switchRate(ctx, fps, smaller, "auto", "missed", at);
|
|
1375
|
+
else if (lower !== void 0) switchRate(ctx, lower, scale, "auto", "missed", at);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1304
1378
|
const higher = stepAbove(g.ladder, fps);
|
|
1305
|
-
if (
|
|
1306
|
-
|
|
1379
|
+
if (higher !== void 0 && canReturn(g, at, false)) {
|
|
1380
|
+
switchRate(ctx, higher, scale, "auto", "headroom", at);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const larger = stepAbove(g.scales, scale);
|
|
1384
|
+
if (larger !== void 0 && canReturn(g, at, true)) switchRate(ctx, fps, larger, "auto", "headroom", at);
|
|
1307
1385
|
}
|
|
1308
1386
|
/**
|
|
1309
1387
|
* Judges the interval from the last run frame's start to this one: the first after a change is skipped, one that is
|
|
@@ -1325,10 +1403,11 @@ function judgeFrame$1(ctx, g, intervalMs, at) {
|
|
|
1325
1403
|
const higher = stepAbove(g.ladder, fps);
|
|
1326
1404
|
const missed = intervalMs > missLineMs(fps, ctx.state.rate.display.hz ?? fps);
|
|
1327
1405
|
const heavy = higher !== void 0 && g.lastWorkMs > .5 * SECOND_MS$1 / higher;
|
|
1406
|
+
const busy = missed && g.lastWorkMs > SECOND_MS$1 / fps;
|
|
1328
1407
|
const slot = g.written % 512;
|
|
1329
1408
|
evict$2(g, at);
|
|
1330
1409
|
g.sampleAt[slot] = at;
|
|
1331
|
-
g.sampleFlags[slot] = (missed ? MISSED : 0) | (heavy ? HEAVY : 0);
|
|
1410
|
+
g.sampleFlags[slot] = (missed ? MISSED : 0) | (heavy ? HEAVY : 0) | (busy ? BUSY : 0);
|
|
1332
1411
|
g.written += 1;
|
|
1333
1412
|
g.dropDrawn += 1;
|
|
1334
1413
|
g.returnDrawn += 1;
|
|
@@ -1337,6 +1416,7 @@ function judgeFrame$1(ctx, g, intervalMs, at) {
|
|
|
1337
1416
|
g.returnMissed += 1;
|
|
1338
1417
|
}
|
|
1339
1418
|
if (heavy) g.returnHeavy += 1;
|
|
1419
|
+
if (busy) g.dropBusy += 1;
|
|
1340
1420
|
decide(ctx, g, at);
|
|
1341
1421
|
}
|
|
1342
1422
|
/**
|
|
@@ -1358,6 +1438,39 @@ function closeFrame(ctx, g, workMs) {
|
|
|
1358
1438
|
g.lastWorkMs = Math.max(0, workMs - agentMs);
|
|
1359
1439
|
}
|
|
1360
1440
|
/**
|
|
1441
|
+
* Starts the governor over for a new scene: the back-off of failed returns goes back to the base dwell, both windows
|
|
1442
|
+
* are emptied and the next interval is skipped, so the new screen is judged on its own frames. The rate and the scale
|
|
1443
|
+
* stay. Nothing without a governor.
|
|
1444
|
+
*
|
|
1445
|
+
* @param rate - The rate state.
|
|
1446
|
+
*/
|
|
1447
|
+
function sceneRate(rate) {
|
|
1448
|
+
const g = rate.governor;
|
|
1449
|
+
if (g === void 0) return;
|
|
1450
|
+
resetDwell(g);
|
|
1451
|
+
g.skipNext = true;
|
|
1452
|
+
emptyWindows(g);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Sets the render scales the rule may step through, down to `floor`. A scale under the new floor goes up to it at
|
|
1456
|
+
* once, quietly: the caller draws at what it decided. Nothing without a governor.
|
|
1457
|
+
*
|
|
1458
|
+
* @param rate - The rate state.
|
|
1459
|
+
* @param floor - The lowest scale the renderer allows; 1 allows none.
|
|
1460
|
+
* @returns The frame rate after the call.
|
|
1461
|
+
*/
|
|
1462
|
+
function allowScale(rate, floor) {
|
|
1463
|
+
const g = rate.governor;
|
|
1464
|
+
if (g === void 0) return rate.snapshot;
|
|
1465
|
+
g.scales = scaleStepsOf(floor);
|
|
1466
|
+
const lowest = g.scales.at(-1) ?? 1;
|
|
1467
|
+
if (rate.scale < lowest) {
|
|
1468
|
+
rate.scale = lowest;
|
|
1469
|
+
rate.snapshot = snapshotOf(rate);
|
|
1470
|
+
}
|
|
1471
|
+
return rate.snapshot;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1361
1474
|
* Restarts the tick history of the display after a resume and, with a governor, its windows, and skips the next
|
|
1362
1475
|
* interval: a return from the background is a new situation. The display rate already known stays.
|
|
1363
1476
|
*
|
|
@@ -1403,10 +1516,10 @@ function resumeAuto(ctx) {
|
|
|
1403
1516
|
const { maxFps } = ctx.config;
|
|
1404
1517
|
const g = ctx.state.rate.governor;
|
|
1405
1518
|
if (g === void 0) {
|
|
1406
|
-
switchRate(ctx, maxFps, "fixed", "auto", nowOf());
|
|
1519
|
+
switchRate(ctx, maxFps, 1, "fixed", "auto", nowOf());
|
|
1407
1520
|
return;
|
|
1408
1521
|
}
|
|
1409
|
-
switchRate(ctx, autoRate(g, maxFps), "auto", "auto", nowOf());
|
|
1522
|
+
switchRate(ctx, autoRate(g, maxFps), 1, "auto", "auto", nowOf());
|
|
1410
1523
|
}
|
|
1411
1524
|
/**
|
|
1412
1525
|
* Forces the rate (the exact cap, never above `maxFps`) or gives it back to the config with `"auto"`.
|
|
@@ -1420,7 +1533,7 @@ function setRateChoice(ctx, choice) {
|
|
|
1420
1533
|
const value = choice;
|
|
1421
1534
|
if (!isChoice(value)) throw new Error(`[game] time.setRate takes "auto", 30, 60 or 120.\n Pass one of them, not ${String(value)}.`);
|
|
1422
1535
|
const { rate } = ctx.state;
|
|
1423
|
-
if (value !== "auto") switchRate(ctx, Math.min(value, ctx.config.maxFps), "forced", "forced", nowOf());
|
|
1536
|
+
if (value !== "auto") switchRate(ctx, Math.min(value, ctx.config.maxFps), 1, "forced", "forced", nowOf());
|
|
1424
1537
|
else if (rate.mode === "forced") resumeAuto(ctx);
|
|
1425
1538
|
return rate.snapshot;
|
|
1426
1539
|
}
|
|
@@ -1694,7 +1807,7 @@ function countFrameListeners(ctx) {
|
|
|
1694
1807
|
}
|
|
1695
1808
|
/**
|
|
1696
1809
|
* Creates the time API: frame callbacks per phase, the `Time` resource, scale, pause and
|
|
1697
|
-
* resume, `wake` for the idle cap, the measurement of an agent build, the frame rate, and `step`
|
|
1810
|
+
* resume, `wake` for the idle cap, the measurement of an agent build, the frame rate and its render scale, and `step`
|
|
1698
1811
|
* for tests and tools.
|
|
1699
1812
|
*
|
|
1700
1813
|
* @param ctx - Domain context of the time plugin.
|
|
@@ -1744,6 +1857,8 @@ function createTimeApi(ctx) {
|
|
|
1744
1857
|
},
|
|
1745
1858
|
rate: () => ctx.state.rate.snapshot,
|
|
1746
1859
|
setRate: (choice) => setRateChoice(ctx, choice),
|
|
1860
|
+
allowScale: (floor) => allowScale(ctx.state.rate, floor),
|
|
1861
|
+
sceneChanged: () => sceneRate(ctx.state.rate),
|
|
1747
1862
|
frameDrawn: () => {
|
|
1748
1863
|
ctx.state.rate.drawnNow = true;
|
|
1749
1864
|
},
|
|
@@ -1907,7 +2022,7 @@ const timePlugin = /*#__PURE__*/ createPlugin$1("time", {
|
|
|
1907
2022
|
},
|
|
1908
2023
|
events: (register) => register.map({
|
|
1909
2024
|
"time:thermal": "The native shell reported a thermal reading",
|
|
1910
|
-
"time:rate": "The loop's frame rate changed"
|
|
2025
|
+
"time:rate": "The loop's frame rate or render scale changed"
|
|
1911
2026
|
}),
|
|
1912
2027
|
createState: createTimeState,
|
|
1913
2028
|
api: createTimeApi,
|
|
@@ -12845,7 +12960,541 @@ function installRebindingRenderGroupPipe(pixi) {
|
|
|
12845
12960
|
};
|
|
12846
12961
|
}
|
|
12847
12962
|
//#endregion
|
|
12963
|
+
//#region src/plugins/renderer/host/render-pass.ts
|
|
12964
|
+
/** Pixi's `CLEAR.NONE`. */
|
|
12965
|
+
const CLEAR_NONE = 0;
|
|
12966
|
+
/** Pixi's `CLEAR.ALL`: colour, depth and stencil. */
|
|
12967
|
+
const CLEAR_ALL = 17664;
|
|
12968
|
+
/** Pixi's `CLEAR.COLOR`. */
|
|
12969
|
+
const CLEAR_COLOR = 16384;
|
|
12970
|
+
/**
|
|
12971
|
+
* The `CLEAR` bits of a pass's clear, as Pixi reads a boolean. `clearBits(true); // 17664`
|
|
12972
|
+
*
|
|
12973
|
+
* @param clear - The clear Pixi was handed.
|
|
12974
|
+
* @returns The bits.
|
|
12975
|
+
*/
|
|
12976
|
+
function clearBits(clear) {
|
|
12977
|
+
if (clear === true) return CLEAR_ALL;
|
|
12978
|
+
if (clear === false) return CLEAR_NONE;
|
|
12979
|
+
return clear;
|
|
12980
|
+
}
|
|
12981
|
+
/**
|
|
12982
|
+
* Builds the classes of the lazy canvas pass from the module object, at call time, because the bases arrive with the
|
|
12983
|
+
* lazily loaded module. Each system keeps its base's `static extension` object as it is, so the registry files it
|
|
12984
|
+
* under the same type and name. They cost one test of two booleans per pipeline request, and nothing else per draw.
|
|
12985
|
+
*
|
|
12986
|
+
* @param pixi - The loaded Pixi module.
|
|
12987
|
+
* @returns The render target system and the pipeline system.
|
|
12988
|
+
*/
|
|
12989
|
+
function createLazyCanvasClasses(pixi) {
|
|
12990
|
+
/**
|
|
12991
|
+
* Pixi's WebGPU adaptor, which begins a pass on the canvas at its first draw and sets the canvas's stencil and depth
|
|
12992
|
+
* operations by the rule of the file. One per renderer.
|
|
12993
|
+
*/
|
|
12994
|
+
class LazyCanvasAdaptor extends pixi.GpuRenderTargetAdaptor {
|
|
12995
|
+
/** The canvas is bound and its pass has not begun: the pipeline system begins it at the first draw. */
|
|
12996
|
+
pending = false;
|
|
12997
|
+
/** A pipeline that tests or writes depth was asked for: the canvas keeps its depth from then on. */
|
|
12998
|
+
depthUsed = false;
|
|
12999
|
+
/** The renderer: Pixi's own `_renderer` is private. */
|
|
13000
|
+
gpu = void 0;
|
|
13001
|
+
/** The render target system: Pixi's own `_renderTargetSystem` is private. */
|
|
13002
|
+
targets = void 0;
|
|
13003
|
+
/** The renderer's pipeline system is the lazy one: only then does a pass wait for a draw. */
|
|
13004
|
+
hooked = false;
|
|
13005
|
+
/** The canvas whose pass is pending or whose clear is owed. */
|
|
13006
|
+
canvas = void 0;
|
|
13007
|
+
/** The `CLEAR` bits the canvas owes. */
|
|
13008
|
+
owed = CLEAR_NONE;
|
|
13009
|
+
/** The colour of the owed clear, copied, when one was given. */
|
|
13010
|
+
owedColor = [
|
|
13011
|
+
0,
|
|
13012
|
+
0,
|
|
13013
|
+
0,
|
|
13014
|
+
0
|
|
13015
|
+
];
|
|
13016
|
+
/** `owedColor` holds the colour of the owed clear. */
|
|
13017
|
+
owedColorSet = false;
|
|
13018
|
+
/** The viewport of the canvas's pass to come, copied from the bind that deferred it. */
|
|
13019
|
+
view = new pixi.Rectangle();
|
|
13020
|
+
/** The canvas whose pass this adaptor began and has not ended. */
|
|
13021
|
+
openCanvas = void 0;
|
|
13022
|
+
/** The open canvas pass stores its stencil. */
|
|
13023
|
+
keepStencil = false;
|
|
13024
|
+
/** The open canvas pass stores its depth. */
|
|
13025
|
+
keepDepth = false;
|
|
13026
|
+
/** The pass being begun stores its stencil. */
|
|
13027
|
+
planStencil = false;
|
|
13028
|
+
/** The pass being begun stores its depth. */
|
|
13029
|
+
planDepth = false;
|
|
13030
|
+
/** The canvas pass that ended last stored its depth. */
|
|
13031
|
+
depthStored = false;
|
|
13032
|
+
/** A canvas pass began since the last `renderEnd`. */
|
|
13033
|
+
acquired = false;
|
|
13034
|
+
/** A mask was pushed on the canvas in the last frame drawn on it. */
|
|
13035
|
+
maskedBefore = false;
|
|
13036
|
+
/** A mask was pushed on the canvas in this frame. */
|
|
13037
|
+
maskedNow = false;
|
|
13038
|
+
/** The root bind of the canvas emitted no target change: the stencil system hears of it when its pass begins. */
|
|
13039
|
+
changeOwed = false;
|
|
13040
|
+
/**
|
|
13041
|
+
* Keeps the renderer and the render target system, then lets Pixi subscribe the adaptor.
|
|
13042
|
+
*
|
|
13043
|
+
* @param renderer - The WebGPU renderer.
|
|
13044
|
+
* @param renderTargetSystem - Its render target system.
|
|
13045
|
+
*/
|
|
13046
|
+
init(renderer, renderTargetSystem) {
|
|
13047
|
+
super.init(renderer, renderTargetSystem);
|
|
13048
|
+
this.gpu = renderer;
|
|
13049
|
+
this.targets = renderTargetSystem;
|
|
13050
|
+
}
|
|
13051
|
+
/** Forgets every pass of the old device, then lets Pixi drop its restore helper. */
|
|
13052
|
+
contextChange() {
|
|
13053
|
+
super.contextChange();
|
|
13054
|
+
this.pending = false;
|
|
13055
|
+
this.canvas = void 0;
|
|
13056
|
+
this.owed = CLEAR_NONE;
|
|
13057
|
+
this.owedColorSet = false;
|
|
13058
|
+
this.openCanvas = void 0;
|
|
13059
|
+
this.depthStored = false;
|
|
13060
|
+
this.changeOwed = false;
|
|
13061
|
+
}
|
|
13062
|
+
/**
|
|
13063
|
+
* Tells whether the root bind of a render may skip its target change: the root is a canvas whose pass is deferred.
|
|
13064
|
+
* The adaptor then owes the stencil system the change, until the canvas pass begins or another target is bound.
|
|
13065
|
+
*
|
|
13066
|
+
* @param root - The root target of the render.
|
|
13067
|
+
* @returns True when the bind may skip the change.
|
|
13068
|
+
*/
|
|
13069
|
+
owesChange(root) {
|
|
13070
|
+
this.changeOwed = this.defers(root);
|
|
13071
|
+
return this.changeOwed;
|
|
13072
|
+
}
|
|
13073
|
+
/**
|
|
13074
|
+
* Begins a pass as Pixi does on any target but the canvas inside a frame. On the canvas it reuses an open pass as
|
|
13075
|
+
* Pixi does, and otherwise ends the open pass and defers the canvas's, its clear owed.
|
|
13076
|
+
*
|
|
13077
|
+
* @param renderTarget - The target.
|
|
13078
|
+
* @param clear - What to clear.
|
|
13079
|
+
* @param clearColor - The colour to clear to.
|
|
13080
|
+
* @param viewport - The viewport.
|
|
13081
|
+
* @param mipLevel - The mip level.
|
|
13082
|
+
* @param layer - The array layer.
|
|
13083
|
+
*/
|
|
13084
|
+
startRenderPass(renderTarget, clear = true, clearColor, viewport, mipLevel = 0, layer = 0) {
|
|
13085
|
+
if (viewport === void 0 || mipLevel !== 0 || layer !== 0 || !this.defers(renderTarget)) {
|
|
13086
|
+
this.pending = false;
|
|
13087
|
+
this.openCanvas = void 0;
|
|
13088
|
+
this.changeOwed = false;
|
|
13089
|
+
super.startRenderPass(renderTarget, clear, clearColor, viewport, mipLevel, layer);
|
|
13090
|
+
return;
|
|
13091
|
+
}
|
|
13092
|
+
const bits = clearBits(clear);
|
|
13093
|
+
if (this.canvas !== void 0 && this.canvas !== renderTarget) this.settleCanvas();
|
|
13094
|
+
if (bits === CLEAR_NONE && this.isOpen(renderTarget)) {
|
|
13095
|
+
this.beginCanvas(renderTarget, CLEAR_NONE, void 0, viewport);
|
|
13096
|
+
return;
|
|
13097
|
+
}
|
|
13098
|
+
this.finishRenderPass();
|
|
13099
|
+
this.canvas = renderTarget;
|
|
13100
|
+
this.pending = true;
|
|
13101
|
+
this.owe(bits, clearColor);
|
|
13102
|
+
this.view.copyFrom(viewport);
|
|
13103
|
+
}
|
|
13104
|
+
/** Ends the open pass, as Pixi does. */
|
|
13105
|
+
finishRenderPass() {
|
|
13106
|
+
super.finishRenderPass();
|
|
13107
|
+
this.openCanvas = void 0;
|
|
13108
|
+
}
|
|
13109
|
+
/**
|
|
13110
|
+
* Copies out of a render target, as Pixi does. A copy that reads or writes the canvas first pays the clear it owes,
|
|
13111
|
+
* or a first pass it never had; a canvas that had its pass in this render and owes nothing begins no empty one.
|
|
13112
|
+
*
|
|
13113
|
+
* @param source - The target copied from.
|
|
13114
|
+
* @param destination - The texture copied into.
|
|
13115
|
+
* @param from - The origin in the source.
|
|
13116
|
+
* @param size - The size of the copy.
|
|
13117
|
+
* @param to - The origin in the destination.
|
|
13118
|
+
* @returns The destination.
|
|
13119
|
+
*/
|
|
13120
|
+
copyToTexture(source, destination, from, size, to) {
|
|
13121
|
+
const canvas = this.canvas;
|
|
13122
|
+
if (canvas !== void 0 && (source === canvas || destination.source === canvas.colorTexture) && (this.owed !== CLEAR_NONE || !this.acquired)) this.payOwed(canvas);
|
|
13123
|
+
return super.copyToTexture(source, destination, from, size, to);
|
|
13124
|
+
}
|
|
13125
|
+
/**
|
|
13126
|
+
* Copies a render target's depth, as Pixi does; a copy of the canvas's depth makes the canvas keep its depth from
|
|
13127
|
+
* then on, and pays the clear it owes first.
|
|
13128
|
+
*
|
|
13129
|
+
* @param source - The target copied from.
|
|
13130
|
+
* @param destination - The depth texture copied into.
|
|
13131
|
+
* @param from - The origin in the source.
|
|
13132
|
+
* @param size - The size of the copy.
|
|
13133
|
+
* @param to - The origin in the destination.
|
|
13134
|
+
*/
|
|
13135
|
+
copyDepthTexture(source, destination, from, size, to) {
|
|
13136
|
+
if (this.isCanvas(source)) {
|
|
13137
|
+
this.depthUsed = true;
|
|
13138
|
+
if (source === this.canvas) this.payOwed(source);
|
|
13139
|
+
}
|
|
13140
|
+
super.copyDepthTexture(source, destination, from, size, to);
|
|
13141
|
+
}
|
|
13142
|
+
/** Begins the pending canvas pass, with the clear it owes: a draw is about to be recorded into it. */
|
|
13143
|
+
beginPending() {
|
|
13144
|
+
const target = this.canvas;
|
|
13145
|
+
if (!this.pending || target === void 0) return;
|
|
13146
|
+
const bits = this.owed;
|
|
13147
|
+
const color = this.owedColorSet ? this.owedColor : void 0;
|
|
13148
|
+
this.pending = false;
|
|
13149
|
+
this.canvas = void 0;
|
|
13150
|
+
this.owed = CLEAR_NONE;
|
|
13151
|
+
this.owedColorSet = false;
|
|
13152
|
+
this.beginCanvas(target, bits, color, this.view);
|
|
13153
|
+
}
|
|
13154
|
+
/**
|
|
13155
|
+
* A pipeline that tests or writes depth was asked for: the canvas keeps its depth from now on, and an open canvas
|
|
13156
|
+
* pass that discards it begins again, with the depth cleared, as nothing wrote it yet, and stored.
|
|
13157
|
+
*/
|
|
13158
|
+
useDepth() {
|
|
13159
|
+
this.depthUsed = true;
|
|
13160
|
+
const canvas = this.openCanvas;
|
|
13161
|
+
if (canvas?.depthStencilAttachment !== void 0 && !this.keepDepth && this.passOpen()) this.restart(canvas);
|
|
13162
|
+
}
|
|
13163
|
+
/**
|
|
13164
|
+
* A stencil mask is about to be pushed on the bound target: on the canvas, the stencil is kept from now on.
|
|
13165
|
+
*
|
|
13166
|
+
* @param target - The bound target.
|
|
13167
|
+
*/
|
|
13168
|
+
maskPushes(target) {
|
|
13169
|
+
if (this.isCanvas(target)) this.maskedNow = true;
|
|
13170
|
+
}
|
|
13171
|
+
/**
|
|
13172
|
+
* A stencil mask was pushed on the bound target: an open canvas pass that discards its stencil begins again, the
|
|
13173
|
+
* stencil cleared, as no mask was open, and stored, before the mask is drawn. A pending canvas pass whose target
|
|
13174
|
+
* change is owed begins now, so the stencil system knows its target before it sets the mask's mode.
|
|
13175
|
+
*
|
|
13176
|
+
* @param target - The bound target.
|
|
13177
|
+
*/
|
|
13178
|
+
maskPushed(target) {
|
|
13179
|
+
if (this.changeOwed && this.pending && this.canvas === target) this.beginPending();
|
|
13180
|
+
else if (this.openCanvas === target && !this.keepStencil && this.passOpen()) this.restart(target);
|
|
13181
|
+
}
|
|
13182
|
+
/**
|
|
13183
|
+
* The end of a render call: pays the clear the canvas owes, gives a canvas that saw no pass one, so the frame
|
|
13184
|
+
* clears and presents it as Pixi's does, and moves the mask memory on after a frame drawn on the canvas.
|
|
13185
|
+
*
|
|
13186
|
+
* @param root - The root target of the render call.
|
|
13187
|
+
*/
|
|
13188
|
+
settle(root) {
|
|
13189
|
+
this.settleCanvas();
|
|
13190
|
+
this.acquired = false;
|
|
13191
|
+
if (this.isCanvas(root)) {
|
|
13192
|
+
this.maskedBefore = this.maskedNow;
|
|
13193
|
+
this.maskedNow = false;
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
/**
|
|
13197
|
+
* Tells whether a pass on a target waits for its first draw: the target is a canvas without multisampling, and the
|
|
13198
|
+
* renderer's pipeline system is the lazy one that begins it.
|
|
13199
|
+
*
|
|
13200
|
+
* @param target - The target.
|
|
13201
|
+
* @returns True when the pass is deferred.
|
|
13202
|
+
*/
|
|
13203
|
+
defers(target) {
|
|
13204
|
+
if (!this.hooked) this.hooked = this.gpu?.pipeline instanceof LazyCanvasPipeline;
|
|
13205
|
+
return this.hooked && this.isCanvas(target);
|
|
13206
|
+
}
|
|
13207
|
+
/**
|
|
13208
|
+
* Tells whether a target is a canvas without multisampling: Pixi gives a canvas's GPU target its context.
|
|
13209
|
+
*
|
|
13210
|
+
* @param target - The target.
|
|
13211
|
+
* @returns True for such a canvas.
|
|
13212
|
+
*/
|
|
13213
|
+
isCanvas(target) {
|
|
13214
|
+
const targets = this.targets;
|
|
13215
|
+
if (targets === void 0) return false;
|
|
13216
|
+
const gpuTarget = targets.getGpuRenderTarget(target);
|
|
13217
|
+
return gpuTarget.contexts.length > 0 && !gpuTarget.msaa;
|
|
13218
|
+
}
|
|
13219
|
+
/**
|
|
13220
|
+
* Tells whether a pass of the encoder is open.
|
|
13221
|
+
*
|
|
13222
|
+
* @returns True while one is.
|
|
13223
|
+
*/
|
|
13224
|
+
passOpen() {
|
|
13225
|
+
const encoder = this.gpu?.encoder;
|
|
13226
|
+
return encoder !== void 0 && encoder.renderPassEncoder !== null;
|
|
13227
|
+
}
|
|
13228
|
+
/**
|
|
13229
|
+
* Tells whether the pass this adaptor began on a canvas is still open.
|
|
13230
|
+
*
|
|
13231
|
+
* @param target - The canvas.
|
|
13232
|
+
* @returns True when its pass is open.
|
|
13233
|
+
*/
|
|
13234
|
+
isOpen(target) {
|
|
13235
|
+
return this.openCanvas === target && this.passOpen();
|
|
13236
|
+
}
|
|
13237
|
+
/**
|
|
13238
|
+
* Adds a clear to the one the canvas owes; a colour clear given a colour keeps a copy of it.
|
|
13239
|
+
*
|
|
13240
|
+
* @param bits - The `CLEAR` bits.
|
|
13241
|
+
* @param color - The colour, when one was given.
|
|
13242
|
+
*/
|
|
13243
|
+
owe(bits, color) {
|
|
13244
|
+
const colour = bits & CLEAR_COLOR;
|
|
13245
|
+
this.owed |= bits;
|
|
13246
|
+
if (colour === 0) return;
|
|
13247
|
+
this.owedColorSet = Array.isArray(color);
|
|
13248
|
+
if (!Array.isArray(color)) return;
|
|
13249
|
+
this.owedColor[0] = color[0];
|
|
13250
|
+
this.owedColor[1] = color[1];
|
|
13251
|
+
this.owedColor[2] = color[2];
|
|
13252
|
+
this.owedColor[3] = color[3];
|
|
13253
|
+
}
|
|
13254
|
+
/**
|
|
13255
|
+
* Begins a pass on the canvas through Pixi, with the stencil and depth operations of the rule set on its
|
|
13256
|
+
* depth-stencil attachment first, where Pixi's descriptor reads them; a clear Pixi was asked for still wins. Pixi
|
|
13257
|
+
* reuses an open pass when nothing is cleared and nothing changed; a pass that really began is recorded.
|
|
13258
|
+
*
|
|
13259
|
+
* @param target - The canvas.
|
|
13260
|
+
* @param bits - The `CLEAR` bits.
|
|
13261
|
+
* @param color - The clear colour, when one was given.
|
|
13262
|
+
* @param viewport - The viewport.
|
|
13263
|
+
*/
|
|
13264
|
+
beginCanvas(target, bits, color, viewport) {
|
|
13265
|
+
const gpu = this.gpu;
|
|
13266
|
+
const targets = this.targets;
|
|
13267
|
+
if (gpu === void 0 || targets === void 0) return;
|
|
13268
|
+
this.planDepthStencil(target, targets);
|
|
13269
|
+
const before = gpu.encoder.renderPassEncoder;
|
|
13270
|
+
if (viewport !== this.view) this.view.copyFrom(viewport);
|
|
13271
|
+
super.startRenderPass(target, bits, color, viewport, 0, 0);
|
|
13272
|
+
this.openCanvas = target;
|
|
13273
|
+
if (gpu.encoder.renderPassEncoder !== before) this.began(target, targets);
|
|
13274
|
+
}
|
|
13275
|
+
/**
|
|
13276
|
+
* Sets the stencil and depth operations of the rule on the canvas's depth-stencil attachment, made first when
|
|
13277
|
+
* Pixi would make it, and keeps what the pass stores.
|
|
13278
|
+
*
|
|
13279
|
+
* @param target - The canvas.
|
|
13280
|
+
* @param targets - The render target system.
|
|
13281
|
+
*/
|
|
13282
|
+
planDepthStencil(target, targets) {
|
|
13283
|
+
if ((target.stencil || target.depth) && target.depthStencilAttachment === void 0) target.ensureDepthStencilTexture();
|
|
13284
|
+
const attachment = target.depthStencilAttachment;
|
|
13285
|
+
const masked = targets.getGpuRenderTarget(target).maskStackIndex !== 0;
|
|
13286
|
+
this.planStencil = masked || this.maskedBefore || this.maskedNow;
|
|
13287
|
+
this.planDepth = this.depthUsed && attachment !== void 0;
|
|
13288
|
+
if (attachment === void 0) return;
|
|
13289
|
+
attachment.stencilLoadOp = masked ? "load" : "clear";
|
|
13290
|
+
attachment.stencilStoreOp = this.planStencil ? "store" : "discard";
|
|
13291
|
+
attachment.depthLoadOp = this.depthStored ? "load" : "clear";
|
|
13292
|
+
attachment.depthStoreOp = this.planDepth ? "store" : "discard";
|
|
13293
|
+
}
|
|
13294
|
+
/**
|
|
13295
|
+
* Records a canvas pass that really began (Pixi did not reuse the open one), and tells the stencil system of the
|
|
13296
|
+
* canvas when the root bind owes it the change, now that there is a pass to set its reference on.
|
|
13297
|
+
*
|
|
13298
|
+
* @param target - The canvas.
|
|
13299
|
+
* @param targets - The render target system.
|
|
13300
|
+
*/
|
|
13301
|
+
began(target, targets) {
|
|
13302
|
+
this.acquired = true;
|
|
13303
|
+
this.keepStencil = this.planStencil;
|
|
13304
|
+
this.keepDepth = this.planDepth;
|
|
13305
|
+
this.depthStored = this.planDepth;
|
|
13306
|
+
if (this.changeOwed) {
|
|
13307
|
+
this.changeOwed = false;
|
|
13308
|
+
targets.onRenderTargetChange.emit(target);
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
/**
|
|
13312
|
+
* Begins and ends one pass on a canvas now, with the clear it owes, and leaves the canvas bound with nothing owed.
|
|
13313
|
+
*
|
|
13314
|
+
* @param target - The canvas.
|
|
13315
|
+
*/
|
|
13316
|
+
payOwed(target) {
|
|
13317
|
+
const bits = this.owed;
|
|
13318
|
+
const color = this.owedColorSet ? this.owedColor : void 0;
|
|
13319
|
+
this.owed = CLEAR_NONE;
|
|
13320
|
+
this.owedColorSet = false;
|
|
13321
|
+
this.beginCanvas(target, bits, color, this.view);
|
|
13322
|
+
this.finishRenderPass();
|
|
13323
|
+
}
|
|
13324
|
+
/**
|
|
13325
|
+
* Ends the canvas's wait: pays the clear it owes, or a first pass it never had, and forgets it.
|
|
13326
|
+
*/
|
|
13327
|
+
settleCanvas() {
|
|
13328
|
+
const target = this.canvas;
|
|
13329
|
+
if (target === void 0) return;
|
|
13330
|
+
if (this.owed !== CLEAR_NONE || !this.acquired) this.payOwed(target);
|
|
13331
|
+
this.pending = false;
|
|
13332
|
+
this.canvas = void 0;
|
|
13333
|
+
}
|
|
13334
|
+
/**
|
|
13335
|
+
* Begins the open canvas pass again with nothing cleared, by the rule as it stands now, and sets its stencil
|
|
13336
|
+
* reference again: a new pass starts from 0, and Pixi sets it only when the mode or the target changes.
|
|
13337
|
+
*
|
|
13338
|
+
* @param target - The canvas.
|
|
13339
|
+
*/
|
|
13340
|
+
restart(target) {
|
|
13341
|
+
const gpu = this.gpu;
|
|
13342
|
+
const targets = this.targets;
|
|
13343
|
+
if (gpu === void 0 || targets === void 0) return;
|
|
13344
|
+
this.finishRenderPass();
|
|
13345
|
+
this.beginCanvas(target, CLEAR_NONE, void 0, this.view);
|
|
13346
|
+
gpu.encoder.setStencilReference(targets.getGpuRenderTarget(target).stencilReference);
|
|
13347
|
+
}
|
|
13348
|
+
}
|
|
13349
|
+
/** Pixi's WebGPU render target system over the lazy adaptor, which settles the canvas at the end of a render. */
|
|
13350
|
+
class LazyCanvasTargets extends pixi.GpuRenderTargetSystem {
|
|
13351
|
+
static extension = pixi.GpuRenderTargetSystem.extension;
|
|
13352
|
+
/** The adaptor, typed as the lazy one. */
|
|
13353
|
+
lazy;
|
|
13354
|
+
/**
|
|
13355
|
+
* Makes Pixi's system, then puts the lazy adaptor in the place of Pixi's.
|
|
13356
|
+
*
|
|
13357
|
+
* @param renderer - The WebGPU renderer.
|
|
13358
|
+
*/
|
|
13359
|
+
constructor(renderer) {
|
|
13360
|
+
super(renderer);
|
|
13361
|
+
const lazy = new LazyCanvasAdaptor();
|
|
13362
|
+
lazy.init(renderer, this);
|
|
13363
|
+
this.adaptor = lazy;
|
|
13364
|
+
this.lazy = lazy;
|
|
13365
|
+
}
|
|
13366
|
+
/**
|
|
13367
|
+
* Starts a render as Pixi does; a root canvas whose pass is deferred is marked bound first, so Pixi's bind of it
|
|
13368
|
+
* emits no target change: the adaptor emits it when the pass begins.
|
|
13369
|
+
*
|
|
13370
|
+
* @param options - The root binding.
|
|
13371
|
+
*/
|
|
13372
|
+
renderStart(options) {
|
|
13373
|
+
const root = this.getRenderTarget(options.target);
|
|
13374
|
+
if (this.renderTarget !== root && Boolean(root.flipY) === Boolean(options.flipY) && this.lazy.owesChange(root)) this.renderTarget = root;
|
|
13375
|
+
super.renderStart(options);
|
|
13376
|
+
}
|
|
13377
|
+
/** The end of a render call, before the encoder submits it: the canvas settles. */
|
|
13378
|
+
renderEnd() {
|
|
13379
|
+
this.lazy.settle(this.rootRenderTarget);
|
|
13380
|
+
}
|
|
13381
|
+
/** Makes sure the bound target has a stencil for a mask about to be pushed, and keeps it by the rule. */
|
|
13382
|
+
ensureDepthStencil() {
|
|
13383
|
+
const target = this.renderTarget;
|
|
13384
|
+
this.lazy.maskPushes(target);
|
|
13385
|
+
super.ensureDepthStencil();
|
|
13386
|
+
this.lazy.maskPushed(target);
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
/** Pixi's WebGPU pipeline system, which begins a deferred canvas pass before a draw or a stencil reference needs it. */
|
|
13390
|
+
class LazyCanvasPipeline extends pixi.PipelineSystem {
|
|
13391
|
+
static extension = pixi.PipelineSystem.extension;
|
|
13392
|
+
/** The renderer: Pixi's own `_renderer` is private. */
|
|
13393
|
+
host;
|
|
13394
|
+
/** The lazy adaptor of the renderer, once found. */
|
|
13395
|
+
lazy = void 0;
|
|
13396
|
+
/**
|
|
13397
|
+
* Keeps the renderer it is made for.
|
|
13398
|
+
*
|
|
13399
|
+
* @param renderer - The WebGPU renderer.
|
|
13400
|
+
*/
|
|
13401
|
+
constructor(renderer) {
|
|
13402
|
+
super(renderer);
|
|
13403
|
+
this.host = renderer;
|
|
13404
|
+
}
|
|
13405
|
+
/**
|
|
13406
|
+
* Notes a state that tests or writes depth, begins a pending canvas pass, then answers Pixi's pipeline. Its five
|
|
13407
|
+
* arguments are named, not spread: a rest array would be made at every draw.
|
|
13408
|
+
*
|
|
13409
|
+
* @param geometry - The geometry.
|
|
13410
|
+
* @param program - The program.
|
|
13411
|
+
* @param state - The state.
|
|
13412
|
+
* @param topology - The topology.
|
|
13413
|
+
* @param overrides - The shader overrides.
|
|
13414
|
+
* @returns The pipeline.
|
|
13415
|
+
*/
|
|
13416
|
+
getPipeline(geometry, program, state, topology, overrides) {
|
|
13417
|
+
const lazy = this.lazy ?? this.link();
|
|
13418
|
+
if (lazy !== void 0) {
|
|
13419
|
+
if (!lazy.depthUsed && (state.depthTest || state.depthMask)) lazy.useDepth();
|
|
13420
|
+
if (lazy.pending) lazy.beginPending();
|
|
13421
|
+
}
|
|
13422
|
+
return super.getPipeline(geometry, program, state, topology, overrides);
|
|
13423
|
+
}
|
|
13424
|
+
/**
|
|
13425
|
+
* Begins a pending canvas pass, then answers Pixi's buffer names: the first call of a batch or a graphics draw.
|
|
13426
|
+
*
|
|
13427
|
+
* @param geometry - The geometry.
|
|
13428
|
+
* @param program - The program.
|
|
13429
|
+
* @returns The names of the buffers to bind.
|
|
13430
|
+
*/
|
|
13431
|
+
getBufferNamesToBind(geometry, program) {
|
|
13432
|
+
this.beginPending();
|
|
13433
|
+
return super.getBufferNamesToBind(geometry, program);
|
|
13434
|
+
}
|
|
13435
|
+
/**
|
|
13436
|
+
* Begins a pending canvas pass, then sets the stencil mode: the stencil system sets its reference on the pass next.
|
|
13437
|
+
*
|
|
13438
|
+
* @param stencilMode - The stencil mode.
|
|
13439
|
+
*/
|
|
13440
|
+
setStencilMode(stencilMode) {
|
|
13441
|
+
this.beginPending();
|
|
13442
|
+
super.setStencilMode(stencilMode);
|
|
13443
|
+
}
|
|
13444
|
+
/**
|
|
13445
|
+
* Begins a pending canvas pass, then answers Pixi's key: a render bundle is recorded or replayed next.
|
|
13446
|
+
*
|
|
13447
|
+
* @returns The key.
|
|
13448
|
+
*/
|
|
13449
|
+
get bundleStateKey() {
|
|
13450
|
+
this.beginPending();
|
|
13451
|
+
return super.bundleStateKey;
|
|
13452
|
+
}
|
|
13453
|
+
/** Begins the pending canvas pass of the renderer, when there is one. */
|
|
13454
|
+
beginPending() {
|
|
13455
|
+
const lazy = this.lazy ?? this.link();
|
|
13456
|
+
if (lazy?.pending === true) lazy.beginPending();
|
|
13457
|
+
}
|
|
13458
|
+
/**
|
|
13459
|
+
* Finds the renderer's lazy adaptor, once its render target system exists.
|
|
13460
|
+
*
|
|
13461
|
+
* @returns The adaptor, or `undefined` while there is none.
|
|
13462
|
+
*/
|
|
13463
|
+
link() {
|
|
13464
|
+
const adaptor = this.host.renderTarget.adaptor;
|
|
13465
|
+
if (adaptor instanceof LazyCanvasAdaptor) this.lazy = adaptor;
|
|
13466
|
+
return this.lazy;
|
|
13467
|
+
}
|
|
13468
|
+
}
|
|
13469
|
+
return {
|
|
13470
|
+
targets: LazyCanvasTargets,
|
|
13471
|
+
pipeline: LazyCanvasPipeline
|
|
13472
|
+
};
|
|
13473
|
+
}
|
|
13474
|
+
/**
|
|
13475
|
+
* Swaps Pixi's WebGPU render target and pipeline systems for the lazy canvas ones in the module's extension registry,
|
|
13476
|
+
* and registers Pixi's stencil system again after them: the registry appends a class it is given, and the stencil
|
|
13477
|
+
* system subscribes to the render target system when it is made, so it must come later. Every application made
|
|
13478
|
+
* afterwards defers its canvas pass. The registry is global to the module object, so the remover matters: it puts
|
|
13479
|
+
* Pixi's own classes back, the stencil system again last.
|
|
13480
|
+
*
|
|
13481
|
+
* @param pixi - The loaded Pixi module.
|
|
13482
|
+
* @returns The remover.
|
|
13483
|
+
* @throws {TypeError} When the module has none of the classes to subclass.
|
|
13484
|
+
*/
|
|
13485
|
+
function installLazyCanvasPass(pixi) {
|
|
13486
|
+
const lazy = createLazyCanvasClasses(pixi);
|
|
13487
|
+
pixi.extensions.remove(pixi.GpuRenderTargetSystem, pixi.GpuStencilSystem, pixi.PipelineSystem);
|
|
13488
|
+
pixi.extensions.add(lazy.targets, pixi.GpuStencilSystem, lazy.pipeline);
|
|
13489
|
+
return () => {
|
|
13490
|
+
pixi.extensions.remove(lazy.targets, pixi.GpuStencilSystem, lazy.pipeline);
|
|
13491
|
+
pixi.extensions.add(pixi.GpuRenderTargetSystem, pixi.GpuStencilSystem, pixi.PipelineSystem);
|
|
13492
|
+
};
|
|
13493
|
+
}
|
|
13494
|
+
//#endregion
|
|
12848
13495
|
//#region src/plugins/renderer/host/resolution.ts
|
|
13496
|
+
/** The short side, in CSS pixels, at or under which a coarse pointer is a phone: the rule of `effects.phone`. */
|
|
13497
|
+
const PHONE_SIDE$1 = 820;
|
|
12849
13498
|
/**
|
|
12850
13499
|
* Device pixels per CSS pixel of the window; 1 without a window. Moved here from `host/init.ts`.
|
|
12851
13500
|
*
|
|
@@ -12855,20 +13504,67 @@ function devicePixels() {
|
|
|
12855
13504
|
return typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
12856
13505
|
}
|
|
12857
13506
|
/**
|
|
12858
|
-
* The resolution to draw at: the override, else the device pixels under the cap.
|
|
13507
|
+
* The resolution to draw at: the override, else the device pixels under the cap at the render scale.
|
|
12859
13508
|
*
|
|
12860
13509
|
* @param state - The resolution state of the host.
|
|
12861
13510
|
* @param state.dpr - Device pixels per CSS pixel.
|
|
12862
13511
|
* @param state.cap - The cap of `maxResolution`.
|
|
12863
13512
|
* @param state.override - The switch of `monitor.setResolution`, when set.
|
|
13513
|
+
* @param state.scale - The render scale of the adaptive frame rate.
|
|
12864
13514
|
* @returns The target resolution.
|
|
12865
13515
|
* @example
|
|
12866
13516
|
* ```ts
|
|
12867
|
-
* targetResolution({ dpr: 2.81, cap: 2, override: undefined }); //
|
|
13517
|
+
* targetResolution({ dpr: 2.81, cap: 2, override: undefined, scale: 0.75 }); // 1.5
|
|
12868
13518
|
* ```
|
|
12869
13519
|
*/
|
|
12870
13520
|
function targetResolution(state) {
|
|
12871
|
-
return state.override ?? Math.min(state.dpr, state.cap);
|
|
13521
|
+
return state.override ?? Math.min(state.dpr, state.cap) * state.scale;
|
|
13522
|
+
}
|
|
13523
|
+
/**
|
|
13524
|
+
* Tells a phone: a coarse pointer and a short side of at most 820 CSS px, the rule `effects` reads for `phone: "auto"`.
|
|
13525
|
+
* False without a window.
|
|
13526
|
+
*
|
|
13527
|
+
* @returns True on a phone.
|
|
13528
|
+
*/
|
|
13529
|
+
function isPhone() {
|
|
13530
|
+
if (typeof globalThis.matchMedia !== "function") return false;
|
|
13531
|
+
return globalThis.matchMedia("(pointer: coarse)").matches && Math.min(globalThis.innerWidth, globalThis.innerHeight) <= PHONE_SIDE$1;
|
|
13532
|
+
}
|
|
13533
|
+
/**
|
|
13534
|
+
* The lowest render scale the adaptive frame rate may choose on this device: the floor of `adaptiveScale` where its
|
|
13535
|
+
* `devices` take this one, otherwise 1, none.
|
|
13536
|
+
*
|
|
13537
|
+
* @param config - The resolved plugin config.
|
|
13538
|
+
* @returns The floor; 1 allows no scale.
|
|
13539
|
+
*/
|
|
13540
|
+
function scaleFloorOf(config) {
|
|
13541
|
+
const { adaptiveScale } = config;
|
|
13542
|
+
if (adaptiveScale === false) return 1;
|
|
13543
|
+
return adaptiveScale.devices === "all" || isPhone() ? adaptiveScale.floor : 1;
|
|
13544
|
+
}
|
|
13545
|
+
/**
|
|
13546
|
+
* Follows the render scale `time:rate` carries: remembers it and asks for the target with reason `"rate"`. The
|
|
13547
|
+
* override still wins.
|
|
13548
|
+
*
|
|
13549
|
+
* @param state - The host branch of the plugin state.
|
|
13550
|
+
* @param scale - The render scale the adaptive frame rate chose.
|
|
13551
|
+
* @returns True when a change is now pending.
|
|
13552
|
+
*/
|
|
13553
|
+
function followScale(state, scale) {
|
|
13554
|
+
if (scale === state.resolution.scale) return false;
|
|
13555
|
+
state.resolution.scale = scale;
|
|
13556
|
+
return askResolution(state, targetResolution(state.resolution), "rate");
|
|
13557
|
+
}
|
|
13558
|
+
/**
|
|
13559
|
+
* Lets the adaptive frame rate step the render scale down to the floor of this device, or to none while the override
|
|
13560
|
+
* holds, and takes the scale it answers: a scale it withdrew is 1 again.
|
|
13561
|
+
*
|
|
13562
|
+
* @param ctx - Domain context of the renderer plugin.
|
|
13563
|
+
*/
|
|
13564
|
+
function allowRuleScale(ctx) {
|
|
13565
|
+
const { resolution } = ctx.state.host;
|
|
13566
|
+
const floor = resolution.override === void 0 ? resolution.floor : 1;
|
|
13567
|
+
resolution.scale = ctx.deps.time.allowScale(floor).scale;
|
|
12872
13568
|
}
|
|
12873
13569
|
/**
|
|
12874
13570
|
* The one mechanism: remembers a resolution for the next frame. Nothing is applied here. While no
|
|
@@ -13461,7 +14157,8 @@ function createPoolState() {
|
|
|
13461
14157
|
/**
|
|
13462
14158
|
* Creates the initial host state: no Pixi, no application, nothing drawn, nothing listening, no
|
|
13463
14159
|
* counting, no bitmap text pipe and no render-group pipe installed, the resolution of a page without a window (device
|
|
13464
|
-
* pixels 1, cap 2, no override, nothing pending), no own GPU, no kept picture
|
|
14160
|
+
* pixels 1, cap 2, no override, nothing pending, the whole scale, no scale allowed yet), no own GPU, no kept picture
|
|
14161
|
+
* and no pool trim yet.
|
|
13465
14162
|
*
|
|
13466
14163
|
* @returns The host branch of the plugin state.
|
|
13467
14164
|
*/
|
|
@@ -13483,11 +14180,14 @@ function createHostState() {
|
|
|
13483
14180
|
uninstallPasses: void 0,
|
|
13484
14181
|
uninstallSdfText: void 0,
|
|
13485
14182
|
uninstallRenderGroup: void 0,
|
|
14183
|
+
uninstallRenderPass: void 0,
|
|
13486
14184
|
resolution: {
|
|
13487
14185
|
dpr: 1,
|
|
13488
14186
|
cap: 2,
|
|
13489
14187
|
override: void 0,
|
|
13490
|
-
pending: void 0
|
|
14188
|
+
pending: void 0,
|
|
14189
|
+
scale: 1,
|
|
14190
|
+
floor: 1
|
|
13491
14191
|
},
|
|
13492
14192
|
onResolutionChange: [],
|
|
13493
14193
|
ownGpu: void 0,
|
|
@@ -13604,11 +14304,42 @@ function installRenderGroup(ctx, pixi) {
|
|
|
13604
14304
|
}
|
|
13605
14305
|
}
|
|
13606
14306
|
/**
|
|
14307
|
+
* Swaps Pixi's WebGPU render target and pipeline systems for the renderer's, which begin the canvas's render pass at
|
|
14308
|
+
* its first draw and keep the canvas's stencil and depth only where something reads them (B3). Like the batched text it
|
|
14309
|
+
* runs before `new Application()`, once per host state, in every build. A Pixi without the pieces draws on with its own
|
|
14310
|
+
* systems, with a warning.
|
|
14311
|
+
*
|
|
14312
|
+
* @param ctx - Domain context of the renderer plugin.
|
|
14313
|
+
* @param pixi - The module the application is built from.
|
|
14314
|
+
*/
|
|
14315
|
+
function installRenderPass(ctx, pixi) {
|
|
14316
|
+
const state = ctx.state.host;
|
|
14317
|
+
if (state.uninstallRenderPass !== void 0) return;
|
|
14318
|
+
try {
|
|
14319
|
+
state.uninstallRenderPass = installLazyCanvasPass(pixi);
|
|
14320
|
+
} catch (error) {
|
|
14321
|
+
ctx.log.warn("renderer: the canvas pass begins at render start", { error });
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14324
|
+
/**
|
|
14325
|
+
* Tells whether a feature brings a filter that reads what was drawn under its view (`defineFilter({ backdrop: true })`).
|
|
14326
|
+
* Pixi's WebGL renderer copies that from its back buffer, and skips such a filter, with a warning, without one; the back
|
|
14327
|
+
* buffer costs a full-screen copy per drawn frame, so it is asked for only then. WebGPU copies from the canvas and
|
|
14328
|
+
* ignores the option.
|
|
14329
|
+
*
|
|
14330
|
+
* @param features - Every feature of the game, its sub-features included.
|
|
14331
|
+
* @returns True when one of their filters is a backdrop.
|
|
14332
|
+
*/
|
|
14333
|
+
function wantsBackBuffer(features) {
|
|
14334
|
+
for (const feature of features) for (const entry of feature.description.filters ?? []) if ("backdrop" in entry.filter && entry.filter.backdrop === true) return true;
|
|
14335
|
+
return false;
|
|
14336
|
+
}
|
|
14337
|
+
/**
|
|
13607
14338
|
* Loads Pixi once, creates the application and appends its canvas, at the target resolution of
|
|
13608
14339
|
* now: a restore after a lost device comes back at it, override included. Pixi picks WebGL by
|
|
13609
14340
|
* itself when WebGPU is missing, so only a device with neither ends up in the catch of the
|
|
13610
14341
|
* caller. An agent build marks the Pixi load and the init on the page's timeline and hands Pixi
|
|
13611
|
-
* the engine's own device.
|
|
14342
|
+
* the engine's own device. A game with a backdrop filter gets Pixi's WebGL back buffer.
|
|
13612
14343
|
*
|
|
13613
14344
|
* @param ctx - Domain context of the renderer plugin.
|
|
13614
14345
|
* @param mount - Where the canvas goes.
|
|
@@ -13625,6 +14356,7 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13625
14356
|
installTextBatch(ctx, pixi);
|
|
13626
14357
|
installCounting$1(ctx, pixi, deps);
|
|
13627
14358
|
installRenderGroup(ctx, pixi);
|
|
14359
|
+
installRenderPass(ctx, pixi);
|
|
13628
14360
|
state.resolution.dpr = devicePixels();
|
|
13629
14361
|
state.resolution.cap = ctx.config.maxResolution;
|
|
13630
14362
|
state.resolution.pending = void 0;
|
|
@@ -13636,7 +14368,8 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13636
14368
|
resolution: targetResolution(state.resolution),
|
|
13637
14369
|
autoDensity: true,
|
|
13638
14370
|
autoStart: false,
|
|
13639
|
-
sharedTicker: false
|
|
14371
|
+
sharedTicker: false,
|
|
14372
|
+
useBackBuffer: wantsBackBuffer(ctx.deps.flow.features.all())
|
|
13640
14373
|
};
|
|
13641
14374
|
await (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? initTimed(ctx, app, options) : app.init(options));
|
|
13642
14375
|
state.app = app;
|
|
@@ -13668,8 +14401,8 @@ function destroyApplication(state) {
|
|
|
13668
14401
|
/**
|
|
13669
14402
|
* Undoes everything `init` created. Works on the state alone, because `onStop` has no context.
|
|
13670
14403
|
* `ready` drops first, so a device that goes away during the teardown is ignored. The draw
|
|
13671
|
-
* counter, the batched text
|
|
13672
|
-
* the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
14404
|
+
* counter, the batched text, the render-group pipe and the lazy canvas pass swap Pixi's own classes back, so the next
|
|
14405
|
+
* application on the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
13673
14406
|
*
|
|
13674
14407
|
* @param state - The host branch of the plugin state.
|
|
13675
14408
|
*/
|
|
@@ -13687,6 +14420,8 @@ function stopHost(state) {
|
|
|
13687
14420
|
state.uninstallSdfText = void 0;
|
|
13688
14421
|
state.uninstallRenderGroup?.();
|
|
13689
14422
|
state.uninstallRenderGroup = void 0;
|
|
14423
|
+
state.uninstallRenderPass?.();
|
|
14424
|
+
state.uninstallRenderPass = void 0;
|
|
13690
14425
|
state.unsupported?.remove();
|
|
13691
14426
|
state.unsupported = void 0;
|
|
13692
14427
|
state.onReady.length = 0;
|
|
@@ -14199,6 +14934,7 @@ function createHostApi(ctx, deps) {
|
|
|
14199
14934
|
setResolution: (value, reason) => askResolution(state, value, reason),
|
|
14200
14935
|
overrideResolution: (value) => {
|
|
14201
14936
|
state.resolution.override = value;
|
|
14937
|
+
allowRuleScale(ctx);
|
|
14202
14938
|
refreshResolution(ctx, "debug");
|
|
14203
14939
|
return targetResolution(state.resolution);
|
|
14204
14940
|
},
|
|
@@ -14256,6 +14992,8 @@ function createHostApi(ctx, deps) {
|
|
|
14256
14992
|
watchDevice(ctx, restart);
|
|
14257
14993
|
watchVisibility(ctx);
|
|
14258
14994
|
watchPixelRatio(ctx);
|
|
14995
|
+
state.resolution.floor = scaleFloorOf(ctx.config);
|
|
14996
|
+
allowRuleScale(ctx);
|
|
14259
14997
|
state.ready = true;
|
|
14260
14998
|
for (const fn of state.onReady) fn();
|
|
14261
14999
|
}
|
|
@@ -29683,9 +30421,9 @@ function createRendererApi(ctx) {
|
|
|
29683
30421
|
//#region src/plugins/renderer/handlers.ts
|
|
29684
30422
|
/**
|
|
29685
30423
|
* @file renderer plugin — the hooks: a pause hands the captures still waiting for a drawn frame
|
|
29686
|
-
* their picture, since no frame comes while the clock is paused, and trims Pixi's texture pool
|
|
29687
|
-
*
|
|
29688
|
-
* changes of the frame rate.
|
|
30424
|
+
* their picture, since no frame comes while the clock is paused, and trims Pixi's texture pool; a change of the frame
|
|
30425
|
+
* rate brings the render scale the adaptive rule chose. In an agent build the performance monitor also hears the pause
|
|
30426
|
+
* stack, the flow path, the thermal readings, the changes of the frame rate and of the scale.
|
|
29689
30427
|
*/
|
|
29690
30428
|
/**
|
|
29691
30429
|
* Creates the hooks of the renderer. `time` is resolved when a hook fires, not while this factory
|
|
@@ -29743,13 +30481,23 @@ function createHandlers$5(ctx) {
|
|
|
29743
30481
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) monitorOf().thermal(reading);
|
|
29744
30482
|
},
|
|
29745
30483
|
/**
|
|
29746
|
-
*
|
|
29747
|
-
*
|
|
30484
|
+
* Draws at the render scale the change carries from the next frame, in every build. An agent build writes the
|
|
30485
|
+
* change as a `rate` event line, the payload as its detail, and a move of the scale as a `scale` line.
|
|
29748
30486
|
*
|
|
29749
30487
|
* @param change - The change `time:rate` carried.
|
|
29750
30488
|
*/
|
|
29751
30489
|
"time:rate": (change) => {
|
|
29752
|
-
|
|
30490
|
+
const from = ctx.state.host.resolution.scale;
|
|
30491
|
+
followScale(ctx.state.host, change.scale);
|
|
30492
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
30493
|
+
monitorOf().event("rate", change);
|
|
30494
|
+
if (change.scale !== from) monitorOf().event("scale", {
|
|
30495
|
+
from,
|
|
30496
|
+
to: change.scale,
|
|
30497
|
+
reason: change.reason,
|
|
30498
|
+
fps: change.to
|
|
30499
|
+
});
|
|
30500
|
+
}
|
|
29753
30501
|
}
|
|
29754
30502
|
};
|
|
29755
30503
|
}
|
|
@@ -29974,12 +30722,28 @@ function checkPoolTrim(poolTrim) {
|
|
|
29974
30722
|
if (!isCount(aboveMb)) throw new Error(`[game] renderer.poolTrim.aboveMb is ${String(aboveMb)}.\n Give the MiB the targets must grow by, 0 or more.`);
|
|
29975
30723
|
}
|
|
29976
30724
|
/**
|
|
30725
|
+
* Checks the adaptive render scale of the config: `false`, or a floor that is a step of the scale and the devices it
|
|
30726
|
+
* applies to.
|
|
30727
|
+
*
|
|
30728
|
+
* @param adaptiveScale - What the config holds.
|
|
30729
|
+
* @throws {Error} When it is neither `false` nor an object, the floor is not 1, 0.85, 0.75, 0.6 or 0.5, or the
|
|
30730
|
+
* devices are neither `"phone"` nor `"all"`.
|
|
30731
|
+
*/
|
|
30732
|
+
function checkAdaptiveScale(adaptiveScale) {
|
|
30733
|
+
if (adaptiveScale === false) return;
|
|
30734
|
+
if (typeof adaptiveScale !== "object" || adaptiveScale === null) throw new Error(`[game] renderer.adaptiveScale is ${String(adaptiveScale)}.\n Give { floor, devices } or false.`);
|
|
30735
|
+
const floor = "floor" in adaptiveScale ? adaptiveScale.floor : void 0;
|
|
30736
|
+
const devices = "devices" in adaptiveScale ? adaptiveScale.devices : void 0;
|
|
30737
|
+
if (typeof floor !== "number" || !SCALE_STEPS.includes(floor)) throw new Error(`[game] renderer.adaptiveScale.floor is ${String(floor)}.\n Give a step of the scale: 1, 0.85, 0.75, 0.6 or 0.5.`);
|
|
30738
|
+
if (devices !== "phone" && devices !== "all") throw new Error(`[game] renderer.adaptiveScale.devices is ${String(devices)}.\n Give "phone" or "all".`);
|
|
30739
|
+
}
|
|
30740
|
+
/**
|
|
29977
30741
|
* Checks the numbers of the config a game can get wrong, at `createApp`, in every build: the
|
|
29978
|
-
* resolution cap, the pool trim, the performance monitor, the budget of the kept pictures
|
|
29979
|
-
* probe draws of the warm-up.
|
|
30742
|
+
* resolution cap, the pool trim, the adaptive render scale, the performance monitor, the budget of the kept pictures
|
|
30743
|
+
* and the probe draws of the warm-up.
|
|
29980
30744
|
*
|
|
29981
30745
|
* @param config - The resolved plugin config.
|
|
29982
|
-
* @throws {Error} When `maxResolution` is not a number above 0, `poolTrim` fails its check,
|
|
30746
|
+
* @throws {Error} When `maxResolution` is not a number above 0, `poolTrim` or `adaptiveScale` fails its check,
|
|
29983
30747
|
* `monitor.budgetMs` is neither `"rate"` nor a number above 0, `monitor.frames` is not a whole
|
|
29984
30748
|
* number from 64 to 16384, `keptMb` is not a finite number of at least 0, or `warm` is not a
|
|
29985
30749
|
* whole number from 0 to 16.
|
|
@@ -29989,6 +30753,7 @@ function checkConfig(config) {
|
|
|
29989
30753
|
const { budgetMs, frames } = config.monitor;
|
|
29990
30754
|
if (!Number.isFinite(maxResolution) || maxResolution <= 0) throw new Error(`[game] renderer.maxResolution is ${maxResolution}.\n Give a number above 0.`);
|
|
29991
30755
|
checkPoolTrim(config.poolTrim);
|
|
30756
|
+
checkAdaptiveScale(config.adaptiveScale);
|
|
29992
30757
|
if (budgetMs !== "rate" && !(Number.isFinite(budgetMs) && budgetMs > 0)) throw new Error(`[game] renderer.monitor.budgetMs is ${String(budgetMs)}.\n Give the frame budget in milliseconds, or "rate" for one frame of the current rate.`);
|
|
29993
30758
|
if (!Number.isInteger(frames) || frames < RING_FRAMES.min || frames > RING_FRAMES.max) throw new Error(`[game] renderer.monitor.frames is ${frames}.\n Give a whole number from 64 to 16384.`);
|
|
29994
30759
|
if (typeof keptMb !== "number" || !Number.isFinite(keptMb) || keptMb < 0) throw new Error(`[game] renderer.keptMb is ${String(keptMb)}.\n Give the MiB kept pictures may hold, or 0 to keep none.`);
|
|
@@ -30074,7 +30839,11 @@ const rendererPlugin = /*#__PURE__*/ createPlugin$1("renderer", {
|
|
|
30074
30839
|
},
|
|
30075
30840
|
onDemand: false,
|
|
30076
30841
|
keptMb: 64,
|
|
30077
|
-
warm: 1
|
|
30842
|
+
warm: 1,
|
|
30843
|
+
adaptiveScale: {
|
|
30844
|
+
floor: .75,
|
|
30845
|
+
devices: "phone"
|
|
30846
|
+
}
|
|
30078
30847
|
},
|
|
30079
30848
|
events: (register) => register.map({ "renderer:device-lost": "The GPU device or context was lost; the game is paused" }),
|
|
30080
30849
|
createState: createRendererState,
|
|
@@ -35880,7 +36649,7 @@ function leavingOf(previous, scene) {
|
|
|
35880
36649
|
* Builds the scene in one synchronous block: the layers, then the projections only the old scene
|
|
35881
36650
|
* lists out, then the list of the new scene in (a projection both list stays and takes its new
|
|
35882
36651
|
* place), the new scene to the world's systems, then the list the scene names to `assets`, then
|
|
35883
|
-
* the frame loop out of its idle cap, and only then the event.
|
|
36652
|
+
* the frame loop out of its idle cap and the adaptive frame rate told of the new screen, and only then the event.
|
|
35884
36653
|
*
|
|
35885
36654
|
* @param ctx - Domain context of the plugin.
|
|
35886
36655
|
* @param scene - The scene whose bundles are there.
|
|
@@ -35899,6 +36668,7 @@ function apply(ctx, scene, named) {
|
|
|
35899
36668
|
state.named = named;
|
|
35900
36669
|
ctx.deps.assets.sceneBundles(named);
|
|
35901
36670
|
ctx.deps.time.wake();
|
|
36671
|
+
ctx.deps.time.sceneChanged();
|
|
35902
36672
|
ctx.emit("scenes:changed", {
|
|
35903
36673
|
from,
|
|
35904
36674
|
to: scene.id,
|
|
@@ -37645,96 +38415,6 @@ function createEffectsApi(ctx) {
|
|
|
37645
38415
|
};
|
|
37646
38416
|
}
|
|
37647
38417
|
//#endregion
|
|
37648
|
-
//#region src/plugins/effects/filters/glsl.ts
|
|
37649
|
-
/**
|
|
37650
|
-
* The head of every filter's GLSL. `#version 300 es` comes first, so Pixi compiles both stages
|
|
37651
|
-
* as GLSL ES 3.0 and the dev check compiles the same text; `highp` matches the `f32` of the WGSL
|
|
37652
|
-
* twin and the precision of the uniforms the vertex stage shares. Then the texture coordinate of
|
|
37653
|
-
* `defaultFilterVert`, the output, the input texture and the global filter uniforms as `vec4`.
|
|
37654
|
-
*/
|
|
37655
|
-
const GLSL_HEADER = `#version 300 es
|
|
37656
|
-
precision highp float;
|
|
37657
|
-
|
|
37658
|
-
in vec2 vTextureCoord;
|
|
37659
|
-
out vec4 finalColor;
|
|
37660
|
-
|
|
37661
|
-
uniform sampler2D uTexture;
|
|
37662
|
-
uniform vec4 uInputSize;
|
|
37663
|
-
uniform vec4 uInputPixel;
|
|
37664
|
-
uniform vec4 uInputClamp;
|
|
37665
|
-
uniform vec4 uOutputFrame;
|
|
37666
|
-
uniform vec4 uGlobalFrame;
|
|
37667
|
-
uniform vec4 uOutputTexture;
|
|
37668
|
-
|
|
37669
|
-
`;
|
|
37670
|
-
/** The GLSL type of each uniform type: a number is a `float`, a colour or a tuple a vector. */
|
|
37671
|
-
const GLSL_TYPES = {
|
|
37672
|
-
f32: "float",
|
|
37673
|
-
"vec2<f32>": "vec2",
|
|
37674
|
-
"vec3<f32>": "vec3",
|
|
37675
|
-
"vec4<f32>": "vec4"
|
|
37676
|
-
};
|
|
37677
|
-
/**
|
|
37678
|
-
* One `uniform` line per uniform, in declaration order, under the bare names the group keys.
|
|
37679
|
-
* Nothing without uniforms.
|
|
37680
|
-
*
|
|
37681
|
-
* @param uniforms - The uniforms in declaration order.
|
|
37682
|
-
* @returns The GLSL lines, or `""`.
|
|
37683
|
-
* @example
|
|
37684
|
-
* ```ts
|
|
37685
|
-
* glslUniforms([{ name: "color", type: "vec3<f32>", form: "color", size: 3 }]); // "uniform vec3 color;\n\n"
|
|
37686
|
-
* ```
|
|
37687
|
-
*/
|
|
37688
|
-
function glslUniforms(uniforms) {
|
|
37689
|
-
if (uniforms.length === 0) return "";
|
|
37690
|
-
return `${uniforms.map((uniform) => `uniform ${GLSL_TYPES[uniform.type]} ${uniform.name};\n`).join("")}\n`;
|
|
37691
|
-
}
|
|
37692
|
-
/**
|
|
37693
|
-
* The engine's half of each input in GLSL: the texture, and the function that reads it at the body's uv, its scale
|
|
37694
|
-
* and its clamp applied. The two uniforms it reads are declared before it by `glslUniforms`. Nothing without inputs.
|
|
37695
|
-
*
|
|
37696
|
-
* @param inputs - The inputs in declaration order.
|
|
37697
|
-
* @returns The GLSL lines, or `""`.
|
|
37698
|
-
* @example
|
|
37699
|
-
* ```ts
|
|
37700
|
-
* glslInputs([blurInput]).startsWith("uniform sampler2D blurTexture;"); // true
|
|
37701
|
-
* ```
|
|
37702
|
-
*/
|
|
37703
|
-
function glslInputs(inputs) {
|
|
37704
|
-
return inputs.map((input) => `uniform sampler2D ${input.texture};\n\nvec4 ${input.sample}(vec2 uv) {\n return textureLod(${input.texture}, clamp(uv * uInputSize.xy * ${input.scale.name}, ${input.clamp.name}.xy, ${input.clamp.name}.zw), 0.0);\n}
|
|
37705
|
-
|
|
37706
|
-
`).join("");
|
|
37707
|
-
}
|
|
37708
|
-
/** The GLSL twin of `canvasPixel(uv)`: the pixel's canvas position in CSS px (spec 23 P8.3). */
|
|
37709
|
-
const GLSL_CANVAS_PIXEL = `vec2 canvasPixel(vec2 uv) {
|
|
37710
|
-
return uOutputFrame.xy + keptOrigin + uv * uInputSize.xy;
|
|
37711
|
-
}
|
|
37712
|
-
|
|
37713
|
-
`;
|
|
37714
|
-
/**
|
|
37715
|
-
* The whole GLSL of a filter: the header, the uniforms (then the scale and the clamp of each input, then the
|
|
37716
|
-
* engine's `keptOrigin` of a body that calls `canvasPixel`), the inputs, the helper, the body.
|
|
37717
|
-
*
|
|
37718
|
-
* @param uniforms - The uniforms in declaration order.
|
|
37719
|
-
* @param inputs - The inputs in declaration order.
|
|
37720
|
-
* @param body - The fragment body the game wrote.
|
|
37721
|
-
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
37722
|
-
* @returns The GLSL fragment source the `GlProgram` is built from.
|
|
37723
|
-
* @example
|
|
37724
|
-
* ```ts
|
|
37725
|
-
* assembleGlsl([], [], body).endsWith(body); // true
|
|
37726
|
-
* ```
|
|
37727
|
-
*/
|
|
37728
|
-
function assembleGlsl(uniforms, inputs, body, origin) {
|
|
37729
|
-
const declared = glslUniforms([
|
|
37730
|
-
...uniforms,
|
|
37731
|
-
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
37732
|
-
...origin === void 0 ? [] : [origin]
|
|
37733
|
-
]);
|
|
37734
|
-
const helper = origin === void 0 ? "" : GLSL_CANVAS_PIXEL;
|
|
37735
|
-
return `${GLSL_HEADER}${declared}${glslInputs(inputs)}${helper}${body}`;
|
|
37736
|
-
}
|
|
37737
|
-
//#endregion
|
|
37738
38418
|
//#region src/plugins/effects/filters/wgsl.ts
|
|
37739
38419
|
/**
|
|
37740
38420
|
* The head of every filter shader: the global filter uniforms and the input texture and sampler
|
|
@@ -37811,30 +38491,165 @@ const CANVAS_PIXEL = `fn canvasPixel(uv: vec2<f32>) -> vec2<f32> {
|
|
|
37811
38491
|
return gfu.uOutputFrame.xy + fu.keptOrigin + uv * gfu.uInputSize.xy;
|
|
37812
38492
|
}
|
|
37813
38493
|
|
|
38494
|
+
`;
|
|
38495
|
+
/**
|
|
38496
|
+
* The engine's uniform a backdrop's `sampleBack` scales its uv by: one over the size of the back texture's backing,
|
|
38497
|
+
* CSS px. Filled by every apply from the texture Pixi copied.
|
|
38498
|
+
*/
|
|
38499
|
+
const BACK_SCALE = Object.freeze({
|
|
38500
|
+
name: "backScale",
|
|
38501
|
+
type: "vec2<f32>",
|
|
38502
|
+
form: "vector",
|
|
38503
|
+
size: 2
|
|
38504
|
+
});
|
|
38505
|
+
/** The engine's uniform a backdrop's `sampleBack` clamps to: half a texel inside the copied part of the back texture. */
|
|
38506
|
+
const BACK_CLAMP = Object.freeze({
|
|
38507
|
+
name: "backClamp",
|
|
38508
|
+
type: "vec4<f32>",
|
|
38509
|
+
form: "vector",
|
|
38510
|
+
size: 4
|
|
38511
|
+
});
|
|
38512
|
+
/**
|
|
38513
|
+
* What a backdrop's body reads what was drawn under its view through: the texture Pixi's filter system copies the
|
|
38514
|
+
* canvas into and binds at group 0 binding 3 (`Filter.blendRequired`), sampled at the body's uv with the engine's
|
|
38515
|
+
* scale and clamp, as an input is.
|
|
38516
|
+
*/
|
|
38517
|
+
const SAMPLE_BACK = `@group(0) @binding(3) var uBackTexture: texture_2d<f32>;
|
|
38518
|
+
|
|
38519
|
+
fn sampleBack(uv: vec2<f32>) -> vec4<f32> {
|
|
38520
|
+
return textureSampleLevel(uBackTexture, uSampler, clamp(uv * gfu.uInputSize.xy * fu.backScale, fu.backClamp.xy, fu.backClamp.zw), 0.0);
|
|
38521
|
+
}
|
|
38522
|
+
|
|
37814
38523
|
`;
|
|
37815
38524
|
/**
|
|
37816
38525
|
* The whole source of a filter: the header, the uniform struct (the uniforms, then the scale and the clamp of each
|
|
37817
|
-
* input, then the engine's `keptOrigin` of a body that calls `canvasPixel`),
|
|
37818
|
-
*
|
|
38526
|
+
* input, then a backdrop's back scale and clamp, then the engine's `keptOrigin` of a body that calls `canvasPixel`),
|
|
38527
|
+
* the input bindings, a backdrop's `sampleBack`, the `canvasPixel` helper, the body. A body without either helper
|
|
38528
|
+
* assembles byte-identically to one without the cache and without the backdrop.
|
|
37819
38529
|
*
|
|
37820
38530
|
* @param uniforms - The uniforms in declaration order.
|
|
37821
38531
|
* @param inputs - The inputs in declaration order.
|
|
37822
38532
|
* @param body - The fragment body the game wrote.
|
|
37823
38533
|
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
38534
|
+
* @param backdrop - True for a filter that reads what was drawn under its view.
|
|
37824
38535
|
* @returns The WGSL source both stages are built from.
|
|
37825
38536
|
* @example
|
|
37826
38537
|
* ```ts
|
|
37827
|
-
* assemble([], [], body).
|
|
38538
|
+
* assemble([], [], body, undefined, true).includes("fn sampleBack(uv: vec2<f32>) -> vec4<f32>"); // true
|
|
37828
38539
|
* ```
|
|
37829
38540
|
*/
|
|
37830
|
-
function assemble(uniforms, inputs, body, origin) {
|
|
38541
|
+
function assemble(uniforms, inputs, body, origin, backdrop = false) {
|
|
37831
38542
|
const struct = uniformStruct([
|
|
37832
38543
|
...uniforms,
|
|
37833
38544
|
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
38545
|
+
...backdrop ? [BACK_SCALE, BACK_CLAMP] : [],
|
|
37834
38546
|
...origin === void 0 ? [] : [origin]
|
|
37835
38547
|
]);
|
|
38548
|
+
const back = backdrop ? SAMPLE_BACK : "";
|
|
37836
38549
|
const helper = origin === void 0 ? "" : CANVAS_PIXEL;
|
|
37837
|
-
return `${HEADER}${struct}${inputBindings(inputs)}${helper}${body}`;
|
|
38550
|
+
return `${HEADER}${struct}${inputBindings(inputs)}${back}${helper}${body}`;
|
|
38551
|
+
}
|
|
38552
|
+
//#endregion
|
|
38553
|
+
//#region src/plugins/effects/filters/glsl.ts
|
|
38554
|
+
/**
|
|
38555
|
+
* The head of every filter's GLSL. `#version 300 es` comes first, so Pixi compiles both stages
|
|
38556
|
+
* as GLSL ES 3.0 and the dev check compiles the same text; `highp` matches the `f32` of the WGSL
|
|
38557
|
+
* twin and the precision of the uniforms the vertex stage shares. Then the texture coordinate of
|
|
38558
|
+
* `defaultFilterVert`, the output, the input texture and the global filter uniforms as `vec4`.
|
|
38559
|
+
*/
|
|
38560
|
+
const GLSL_HEADER = `#version 300 es
|
|
38561
|
+
precision highp float;
|
|
38562
|
+
|
|
38563
|
+
in vec2 vTextureCoord;
|
|
38564
|
+
out vec4 finalColor;
|
|
38565
|
+
|
|
38566
|
+
uniform sampler2D uTexture;
|
|
38567
|
+
uniform vec4 uInputSize;
|
|
38568
|
+
uniform vec4 uInputPixel;
|
|
38569
|
+
uniform vec4 uInputClamp;
|
|
38570
|
+
uniform vec4 uOutputFrame;
|
|
38571
|
+
uniform vec4 uGlobalFrame;
|
|
38572
|
+
uniform vec4 uOutputTexture;
|
|
38573
|
+
|
|
38574
|
+
`;
|
|
38575
|
+
/** The GLSL type of each uniform type: a number is a `float`, a colour or a tuple a vector. */
|
|
38576
|
+
const GLSL_TYPES = {
|
|
38577
|
+
f32: "float",
|
|
38578
|
+
"vec2<f32>": "vec2",
|
|
38579
|
+
"vec3<f32>": "vec3",
|
|
38580
|
+
"vec4<f32>": "vec4"
|
|
38581
|
+
};
|
|
38582
|
+
/**
|
|
38583
|
+
* One `uniform` line per uniform, in declaration order, under the bare names the group keys.
|
|
38584
|
+
* Nothing without uniforms.
|
|
38585
|
+
*
|
|
38586
|
+
* @param uniforms - The uniforms in declaration order.
|
|
38587
|
+
* @returns The GLSL lines, or `""`.
|
|
38588
|
+
* @example
|
|
38589
|
+
* ```ts
|
|
38590
|
+
* glslUniforms([{ name: "color", type: "vec3<f32>", form: "color", size: 3 }]); // "uniform vec3 color;\n\n"
|
|
38591
|
+
* ```
|
|
38592
|
+
*/
|
|
38593
|
+
function glslUniforms(uniforms) {
|
|
38594
|
+
if (uniforms.length === 0) return "";
|
|
38595
|
+
return `${uniforms.map((uniform) => `uniform ${GLSL_TYPES[uniform.type]} ${uniform.name};\n`).join("")}\n`;
|
|
38596
|
+
}
|
|
38597
|
+
/**
|
|
38598
|
+
* The engine's half of each input in GLSL: the texture, and the function that reads it at the body's uv, its scale
|
|
38599
|
+
* and its clamp applied. The two uniforms it reads are declared before it by `glslUniforms`. Nothing without inputs.
|
|
38600
|
+
*
|
|
38601
|
+
* @param inputs - The inputs in declaration order.
|
|
38602
|
+
* @returns The GLSL lines, or `""`.
|
|
38603
|
+
* @example
|
|
38604
|
+
* ```ts
|
|
38605
|
+
* glslInputs([blurInput]).startsWith("uniform sampler2D blurTexture;"); // true
|
|
38606
|
+
* ```
|
|
38607
|
+
*/
|
|
38608
|
+
function glslInputs(inputs) {
|
|
38609
|
+
return inputs.map((input) => `uniform sampler2D ${input.texture};\n\nvec4 ${input.sample}(vec2 uv) {\n return textureLod(${input.texture}, clamp(uv * uInputSize.xy * ${input.scale.name}, ${input.clamp.name}.xy, ${input.clamp.name}.zw), 0.0);\n}
|
|
38610
|
+
|
|
38611
|
+
`).join("");
|
|
38612
|
+
}
|
|
38613
|
+
/** The GLSL twin of `canvasPixel(uv)`: the pixel's canvas position in CSS px (spec 23 P8.3). */
|
|
38614
|
+
const GLSL_CANVAS_PIXEL = `vec2 canvasPixel(vec2 uv) {
|
|
38615
|
+
return uOutputFrame.xy + keptOrigin + uv * uInputSize.xy;
|
|
38616
|
+
}
|
|
38617
|
+
|
|
38618
|
+
`;
|
|
38619
|
+
/** The GLSL twin of `sampleBack(uv)`: what was drawn under the view, at the body's uv, scaled and clamped. */
|
|
38620
|
+
const GLSL_SAMPLE_BACK = `uniform sampler2D uBackTexture;
|
|
38621
|
+
|
|
38622
|
+
vec4 sampleBack(vec2 uv) {
|
|
38623
|
+
return textureLod(uBackTexture, clamp(uv * uInputSize.xy * backScale, backClamp.xy, backClamp.zw), 0.0);
|
|
38624
|
+
}
|
|
38625
|
+
|
|
38626
|
+
`;
|
|
38627
|
+
/**
|
|
38628
|
+
* The whole GLSL of a filter: the header, the uniforms (then the scale and the clamp of each input, then a backdrop's
|
|
38629
|
+
* back scale and clamp, then the engine's `keptOrigin` of a body that calls `canvasPixel`), the inputs, a backdrop's
|
|
38630
|
+
* `sampleBack`, the `canvasPixel` helper, the body.
|
|
38631
|
+
*
|
|
38632
|
+
* @param uniforms - The uniforms in declaration order.
|
|
38633
|
+
* @param inputs - The inputs in declaration order.
|
|
38634
|
+
* @param body - The fragment body the game wrote.
|
|
38635
|
+
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
38636
|
+
* @param backdrop - True for a filter that reads what was drawn under its view.
|
|
38637
|
+
* @returns The GLSL fragment source the `GlProgram` is built from.
|
|
38638
|
+
* @example
|
|
38639
|
+
* ```ts
|
|
38640
|
+
* assembleGlsl([], [], body, undefined, true).includes("uniform sampler2D uBackTexture;"); // true
|
|
38641
|
+
* ```
|
|
38642
|
+
*/
|
|
38643
|
+
function assembleGlsl(uniforms, inputs, body, origin, backdrop = false) {
|
|
38644
|
+
const declared = glslUniforms([
|
|
38645
|
+
...uniforms,
|
|
38646
|
+
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
38647
|
+
...backdrop ? [BACK_SCALE, BACK_CLAMP] : [],
|
|
38648
|
+
...origin === void 0 ? [] : [origin]
|
|
38649
|
+
]);
|
|
38650
|
+
const back = backdrop ? GLSL_SAMPLE_BACK : "";
|
|
38651
|
+
const helper = origin === void 0 ? "" : GLSL_CANVAS_PIXEL;
|
|
38652
|
+
return `${GLSL_HEADER}${declared}${glslInputs(inputs)}${back}${helper}${body}`;
|
|
37838
38653
|
}
|
|
37839
38654
|
//#endregion
|
|
37840
38655
|
//#region src/plugins/effects/filters/define.ts
|
|
@@ -37859,9 +38674,10 @@ const MAIN_GLSL = /\bvoid\s+main\s*\(/;
|
|
|
37859
38674
|
/**
|
|
37860
38675
|
* Names a uniform cannot take: the two fields every filter component carries, and the names the
|
|
37861
38676
|
* shader headers declare, which a game uniform would clash with (on WebGL only, for most). The
|
|
37862
|
-
* GLSL header also declares `finalColor` and `vTextureCoord
|
|
38677
|
+
* GLSL header also declares `finalColor` and `vTextureCoord`; a backdrop's, `uBackTexture`,
|
|
38678
|
+
* `backScale`, `backClamp` and `sampleBack`.
|
|
37863
38679
|
*/
|
|
37864
|
-
const RESERVED_NAME = /^(?:enabled|order|uInputSize|uInputPixel|uInputClamp|uOutputFrame|uGlobalFrame|uOutputTexture|uTexture|uSampler|finalColor|vTextureCoord|keptOrigin|canvasPixel)$/;
|
|
38680
|
+
const RESERVED_NAME = /^(?:enabled|order|uInputSize|uInputPixel|uInputClamp|uOutputFrame|uGlobalFrame|uOutputTexture|uTexture|uSampler|finalColor|vTextureCoord|keptOrigin|canvasPixel|uBackTexture|backScale|backClamp|sampleBack)$/;
|
|
37865
38681
|
/**
|
|
37866
38682
|
* What a body reads of the screen: the root target's frame, the fragment position, the output target's size, the
|
|
37867
38683
|
* origin of the output frame (its size `.zw` is the same in a picture and live).
|
|
@@ -37945,21 +38761,22 @@ function worstOf(a, b) {
|
|
|
37945
38761
|
}
|
|
37946
38762
|
/**
|
|
37947
38763
|
* What the kept-picture cache may do with a filter, from what the game declared and what its bodies read only, never
|
|
37948
|
-
* the engine's header (LEAD-FIXES-4 item 3): `cache: false`
|
|
37949
|
-
* nothing is `"screen"` too (the safe direction); a body that calls `canvasPixel` is `"placed"`,
|
|
37950
|
-
* `cache: "placed"`; everything else is `"local"`.
|
|
38764
|
+
* the engine's header (LEAD-FIXES-4 item 3): a backdrop and `cache: false` are `"screen"`; a body that reads the
|
|
38765
|
+
* screen and declares nothing is `"screen"` too (the safe direction); a body that calls `canvasPixel` is `"placed"`,
|
|
38766
|
+
* as is `cache: "placed"`; everything else is `"local"`.
|
|
37951
38767
|
*
|
|
37952
38768
|
* @param wgsl - The WGSL body.
|
|
37953
38769
|
* @param glsl - The GLSL body.
|
|
37954
38770
|
* @param cache - What the game declared.
|
|
38771
|
+
* @param backdrop - Whether the filter reads what was drawn under its view.
|
|
37955
38772
|
* @returns The category.
|
|
37956
38773
|
* @example
|
|
37957
38774
|
* ```ts
|
|
37958
|
-
* cacheOf("let p = gfu.uOutputFrame.xy + uv * gfu.uInputSize.xy;", "", undefined); // "screen"
|
|
38775
|
+
* cacheOf("let p = gfu.uOutputFrame.xy + uv * gfu.uInputSize.xy;", "", undefined, false); // "screen"
|
|
37959
38776
|
* ```
|
|
37960
38777
|
*/
|
|
37961
|
-
function cacheOf(wgsl, glsl, cache) {
|
|
37962
|
-
if (cache === false) return "screen";
|
|
38778
|
+
function cacheOf(wgsl, glsl, cache, backdrop) {
|
|
38779
|
+
if (cache === false || backdrop) return "screen";
|
|
37963
38780
|
if (cache === void 0 && (readsScreenIn(wgsl) || readsScreenIn(glsl))) return "screen";
|
|
37964
38781
|
if (cache === "placed" || callsCanvasPixel(wgsl, glsl)) return "placed";
|
|
37965
38782
|
return "local";
|
|
@@ -38026,6 +38843,48 @@ function readResolution(id, resolution) {
|
|
|
38026
38843
|
throw new Error(`[game] Filter "${id}": resolution must be from 0 to 4, 0 for the resolution of the canvas.\n Got ${String(resolution)}.`);
|
|
38027
38844
|
}
|
|
38028
38845
|
/**
|
|
38846
|
+
* Reads the declared backdrop switch: `true`, `false` or left out. A JavaScript caller may pass anything.
|
|
38847
|
+
*
|
|
38848
|
+
* @param id - The filter id.
|
|
38849
|
+
* @param backdrop - What the game declared.
|
|
38850
|
+
* @returns True for a backdrop.
|
|
38851
|
+
* @throws {Error} When it is given and is not a boolean.
|
|
38852
|
+
*/
|
|
38853
|
+
function readBackdrop(id, backdrop) {
|
|
38854
|
+
if (backdrop === void 0 || typeof backdrop === "boolean") return backdrop === true;
|
|
38855
|
+
throw new Error(`[game] Filter "${id}": backdrop must be true or false.\n Got ${JSON.stringify(backdrop)}.`);
|
|
38856
|
+
}
|
|
38857
|
+
/**
|
|
38858
|
+
* Refuses what a backdrop cannot have: a padding (Pixi copies the canvas under the padded box, and a copy past the
|
|
38859
|
+
* canvas edge fails on the GPU) and a cache that keeps it (what is under it changes without it).
|
|
38860
|
+
*
|
|
38861
|
+
* @param id - The filter id.
|
|
38862
|
+
* @param padding - The padding as declared, `0` when left out.
|
|
38863
|
+
* @param cache - The cache as declared.
|
|
38864
|
+
* @throws {Error} When the padding is not 0 or the cache keeps it.
|
|
38865
|
+
*/
|
|
38866
|
+
function checkBackdrop(id, padding, cache) {
|
|
38867
|
+
if (padding !== 0) throw new Error(`[game] Filter "${id}" is a backdrop: its padding must be 0.\n Pixi copies the canvas under the padded box, and a copy past the canvas edge fails: leave padding out.`);
|
|
38868
|
+
if (cache === "local" || cache === "placed") throw new Error(`[game] Filter "${id}" is a backdrop: it reads what is drawn under its view, so it is drawn live in every frame.\n Leave cache out, or set it to false.`);
|
|
38869
|
+
}
|
|
38870
|
+
/**
|
|
38871
|
+
* Reads where an input's first step reads: the view's own input (the default) or, for a backdrop, what was drawn under
|
|
38872
|
+
* the view. A JavaScript caller may pass anything.
|
|
38873
|
+
*
|
|
38874
|
+
* @param id - The filter id.
|
|
38875
|
+
* @param name - The input name.
|
|
38876
|
+
* @param from - What the game declared.
|
|
38877
|
+
* @param backdrop - Whether the filter is a backdrop.
|
|
38878
|
+
* @returns `"input"` or `"back"`.
|
|
38879
|
+
* @throws {Error} When it is neither, or `"back"` on a filter that is no backdrop.
|
|
38880
|
+
*/
|
|
38881
|
+
function readFrom(id, name, from, backdrop) {
|
|
38882
|
+
if (from === void 0 || from === "input") return "input";
|
|
38883
|
+
if (from !== "back") throw new Error(`[game] Filter "${id}": input "${name}" starts from ${JSON.stringify(from)}.\n Write from: "input" or from: "back".`);
|
|
38884
|
+
if (!backdrop) throw new Error(`[game] Filter "${id}": input "${name}" starts from "back", which only a backdrop filter can read.\n Give the filter backdrop: true.`);
|
|
38885
|
+
return "back";
|
|
38886
|
+
}
|
|
38887
|
+
/**
|
|
38029
38888
|
* Tells whether a value is a list of 2 to 4 finite numbers.
|
|
38030
38889
|
*
|
|
38031
38890
|
* @param value - A uniform declaration.
|
|
@@ -38206,21 +39065,23 @@ function readStages(id, name, stages) {
|
|
|
38206
39065
|
for (const [index, stage] of stages.entries()) {
|
|
38207
39066
|
if (!isDefinedFilter(stage)) throw new Error(`[game] Filter "${id}": stage ${index} of input "${name}" is not a filter made by defineFilter.\n List what defineFilter returned; a built-in kind cannot be a stage.`);
|
|
38208
39067
|
if (!drawsAlone(stage.filter)) throw new Error(`[game] Filter "${id}": stage "${stage.filter.id}" of input "${name}" has inputs or a fallback of its own.\n A stage draws alone: give it neither.`);
|
|
39068
|
+
if (stage.filter.backdrop) throw new Error(`[game] Filter "${id}": stage "${stage.filter.id}" of input "${name}" is a backdrop.\n A stage reads the step before it: give it no backdrop.`);
|
|
38209
39069
|
if (!read.includes(stage)) read.push(stage);
|
|
38210
39070
|
}
|
|
38211
39071
|
return Object.freeze(read);
|
|
38212
39072
|
}
|
|
38213
39073
|
/**
|
|
38214
|
-
* Reads the inputs of a definition: the names the engine declares for each, its
|
|
38215
|
-
* stages and its chain.
|
|
39074
|
+
* Reads the inputs of a definition: the names the engine declares for each, where its first step reads, its two
|
|
39075
|
+
* uniforms, its stages and its chain.
|
|
38216
39076
|
*
|
|
38217
39077
|
* @param id - The filter id.
|
|
38218
39078
|
* @param declared - What the game declared under `inputs`.
|
|
38219
39079
|
* @param uniforms - The read uniforms.
|
|
39080
|
+
* @param backdrop - Whether the filter is a backdrop: only then may an input start from back.
|
|
38220
39081
|
* @returns The inputs in declaration order, frozen; empty without `inputs`.
|
|
38221
39082
|
* @throws {Error} When `inputs` is not an object, has more than four entries, or an input breaks one of its rules.
|
|
38222
39083
|
*/
|
|
38223
|
-
function readInputs(id, declared, uniforms) {
|
|
39084
|
+
function readInputs(id, declared, uniforms, backdrop) {
|
|
38224
39085
|
if (declared === void 0) return Object.freeze([]);
|
|
38225
39086
|
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) throw new Error(`[game] Filter "${id}": inputs must be an object of named inputs.\n Write inputs: { blur: { stages: [...], chain } }.`);
|
|
38226
39087
|
const entries = Object.entries(declared);
|
|
@@ -38228,10 +39089,12 @@ function readInputs(id, declared, uniforms) {
|
|
|
38228
39089
|
const taken = /* @__PURE__ */ new Set();
|
|
38229
39090
|
return Object.freeze(entries.map(([name, input]) => {
|
|
38230
39091
|
const names = checkInputNames(id, name, uniforms, taken);
|
|
39092
|
+
const from = readFrom(id, name, input.from, backdrop);
|
|
38231
39093
|
const stages = readStages(id, name, input.stages);
|
|
38232
39094
|
if (typeof input.chain !== "function") throw new TypeError(`[game] Filter "${id}": input "${name}" needs a chain.\n Write chain: (value, { resolution }) => [{ stage, scale }].`);
|
|
38233
39095
|
return Object.freeze({
|
|
38234
39096
|
name,
|
|
39097
|
+
from,
|
|
38235
39098
|
texture: names.texture,
|
|
38236
39099
|
sampler: names.sampler,
|
|
38237
39100
|
scale: Object.freeze({
|
|
@@ -38254,21 +39117,23 @@ function readInputs(id, declared, uniforms) {
|
|
|
38254
39117
|
}
|
|
38255
39118
|
/**
|
|
38256
39119
|
* Reads the fallback of a definition: another filter of ours, drawing alone, over a subset of its
|
|
38257
|
-
* uniforms with the same types.
|
|
39120
|
+
* uniforms with the same types; a backdrop only for a backdrop, which it draws in place of on the same view.
|
|
38258
39121
|
*
|
|
38259
39122
|
* @param id - The filter id.
|
|
38260
39123
|
* @param fallback - What the game declared under `fallback`.
|
|
38261
39124
|
* @param uniforms - The read uniforms.
|
|
39125
|
+
* @param backdrop - Whether the filter is a backdrop.
|
|
38262
39126
|
* @returns The fallback's definition, or `undefined` without one.
|
|
38263
|
-
* @throws {Error} When it is no filter of ours, has the same id, has inputs or a fallback,
|
|
38264
|
-
* one of this filter's with the same type.
|
|
39127
|
+
* @throws {Error} When it is no filter of ours, has the same id, has inputs or a fallback, a uniform of it is not
|
|
39128
|
+
* one of this filter's with the same type, or it is a backdrop and the filter is not.
|
|
38265
39129
|
*/
|
|
38266
|
-
function readFallback(id, fallback, uniforms) {
|
|
39130
|
+
function readFallback(id, fallback, uniforms, backdrop) {
|
|
38267
39131
|
if (fallback === void 0) return void 0;
|
|
38268
39132
|
if (!isDefinedFilter(fallback)) throw new Error(`[game] Filter "${id}": fallback is not a filter made by defineFilter.\n Pass what defineFilter returned.`);
|
|
38269
39133
|
const definition = fallback.filter;
|
|
38270
39134
|
if (definition.id === id) throw new Error(`[game] Filter "${id}": its fallback has the same id.\n Give the fallback an id of its own.`);
|
|
38271
39135
|
if (!drawsAlone(definition)) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" has inputs or a fallback of its own.\n A fallback draws alone and simply: give it neither.`);
|
|
39136
|
+
if (definition.backdrop && !backdrop) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" is a backdrop, and "${id}" is not.\n A fallback draws in its place on the same view: give it no backdrop, or make "${id}" a backdrop too.`);
|
|
38272
39137
|
for (const uniform of definition.uniforms) {
|
|
38273
39138
|
const own = uniforms.find((candidate) => candidate.name === uniform.name);
|
|
38274
39139
|
if (!(own?.type === uniform.type && own.form === uniform.form)) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" declares "${uniform.name}", which "${id}" ${own === void 0 ? "does not declare" : "declares as another type"}.\n A fallback draws with the value of ${id}: use a subset of its uniforms.`);
|
|
@@ -38318,18 +39183,20 @@ function cacheWithInputs(cache, inputs) {
|
|
|
38318
39183
|
* An `area` answers, from the value, the rectangle in the entity's own units outside of which the
|
|
38319
39184
|
* filter draws nothing: Pixi then renders and fills only that box, not the view's bounds. A
|
|
38320
39185
|
* `resolution` draws the view and its filters at that many texels per CSS px instead of the
|
|
38321
|
-
* canvas's.
|
|
38322
|
-
*
|
|
38323
|
-
*
|
|
39186
|
+
* canvas's. A `backdrop` reads what was drawn under its view inside that box: the body calls
|
|
39187
|
+
* `sampleBack(uv)`, and an input may start `from: "back"`; it is drawn live in every frame.
|
|
39188
|
+
* Register it through the `filters` key of a feature. The README shows a filter with a blur input,
|
|
39189
|
+
* one with an `area` and a backdrop.
|
|
38324
39190
|
*
|
|
38325
39191
|
* @param id - The filter id, also the component name; unique, and not one of the built-ins.
|
|
38326
39192
|
* @param spec - Both bodies, the uniforms, the passes one apply costs, the padding, the inputs, the fallback, the
|
|
38327
|
-
* cache category, the area and the
|
|
39193
|
+
* cache category, the area, the resolution and the backdrop switch.
|
|
38328
39194
|
* @returns The component type, with the frozen `filter` definition attached.
|
|
38329
39195
|
* @throws {Error} When the id is empty, the WGSL has no `mainFragment`, the GLSL no `void main(`,
|
|
38330
39196
|
* a uniform is reserved, badly named or of no known form, the passes or the padding break
|
|
38331
39197
|
* their rules, an input or the fallback breaks one of its rules, `passes` is given with inputs,
|
|
38332
|
-
* `area` is not a function,
|
|
39198
|
+
* `area` is not a function, `resolution` is not a number from 0 to 4, `backdrop` is not a
|
|
39199
|
+
* boolean, or a backdrop has a padding or a cache that keeps it.
|
|
38333
39200
|
* @example
|
|
38334
39201
|
* ```ts
|
|
38335
39202
|
* // features/board/effects.ts: a gold tint a motion hook tweens on a merge, on WebGPU and WebGL.
|
|
@@ -38343,21 +39210,23 @@ function defineFilter(id, spec) {
|
|
|
38343
39210
|
readSpec(id, spec);
|
|
38344
39211
|
const read = Object.entries(spec.uniforms ?? {}).map(([name, declaration]) => readUniform(id, name, declaration));
|
|
38345
39212
|
const uniforms = Object.freeze(read.map((entry) => entry.spec));
|
|
38346
|
-
const
|
|
39213
|
+
const backdrop = readBackdrop(id, spec.backdrop);
|
|
39214
|
+
const inputs = readInputs(id, spec.inputs, uniforms, backdrop);
|
|
38347
39215
|
if (spec.inputs !== void 0 && spec.passes !== void 0) throw new Error(`[game] Filter "${id}": passes is counted by the engine for a filter with inputs.\n Leave passes out.`);
|
|
38348
|
-
const fallback = readFallback(id, spec.fallback, uniforms);
|
|
39216
|
+
const fallback = readFallback(id, spec.fallback, uniforms, backdrop);
|
|
38349
39217
|
const passes = spec.passes ?? 1;
|
|
38350
39218
|
const padding = spec.padding ?? 0;
|
|
38351
39219
|
checkCost(id, passes, padding, uniforms);
|
|
38352
39220
|
checkCache(id, spec.cache);
|
|
39221
|
+
if (backdrop) checkBackdrop(id, padding, spec.cache);
|
|
38353
39222
|
const area = readArea(id, spec.area);
|
|
38354
39223
|
const resolution = readResolution(id, spec.resolution);
|
|
38355
39224
|
const origin = callsCanvasPixel(spec.wgsl, spec.glsl) ? KEPT_ORIGIN : void 0;
|
|
38356
|
-
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache), inputs);
|
|
39225
|
+
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache, backdrop), inputs);
|
|
38357
39226
|
const filter = Object.freeze({
|
|
38358
39227
|
id,
|
|
38359
|
-
source: assemble(uniforms, inputs, spec.wgsl, origin),
|
|
38360
|
-
glsl: assembleGlsl(uniforms, inputs, spec.glsl, origin),
|
|
39228
|
+
source: assemble(uniforms, inputs, spec.wgsl, origin, backdrop),
|
|
39229
|
+
glsl: assembleGlsl(uniforms, inputs, spec.glsl, origin, backdrop),
|
|
38361
39230
|
uniforms,
|
|
38362
39231
|
passes,
|
|
38363
39232
|
padding,
|
|
@@ -38366,9 +39235,10 @@ function defineFilter(id, spec) {
|
|
|
38366
39235
|
cache,
|
|
38367
39236
|
origin,
|
|
38368
39237
|
resolution,
|
|
39238
|
+
backdrop,
|
|
38369
39239
|
...area === void 0 ? {} : { area }
|
|
38370
39240
|
});
|
|
38371
|
-
if (spec.cache !== false && (readsScreenIn(spec.wgsl) || readsScreenIn(spec.glsl))) screenReaders.add(filter);
|
|
39241
|
+
if (!backdrop && spec.cache !== false && (readsScreenIn(spec.wgsl) || readsScreenIn(spec.glsl))) screenReaders.add(filter);
|
|
38372
39242
|
const defaults = Object.fromEntries([
|
|
38373
39243
|
...read.map((entry) => [entry.spec.name, entry.value]),
|
|
38374
39244
|
["enabled", true],
|
|
@@ -38828,7 +39698,9 @@ function brokenMark() {
|
|
|
38828
39698
|
* asks the renderer for no frame. A destroy frees the uniform buffers first: `Filter.destroy()`
|
|
38829
39699
|
* leaves them to a GC that runs after a minute (P10). A filter with inputs draws each place of its
|
|
38830
39700
|
* chains through Pixi's public `applyFilter` from inside its own `apply`, into pooled textures given
|
|
38831
|
-
* back in a `finally`; a stand-in draws in place of a broken kind or instance.
|
|
39701
|
+
* back in a `finally`; a stand-in draws in place of a broken kind or instance. A backdrop is built
|
|
39702
|
+
* with Pixi's `blendRequired`: Pixi copies the canvas under the view before it draws, and the apply
|
|
39703
|
+
* places `sampleBack` on that copy (`activeBackTexture`) and starts the chains `from: "back"` there.
|
|
38832
39704
|
*/
|
|
38833
39705
|
/**
|
|
38834
39706
|
* The resolution of every filter but `Blur`: the render target's, so a filter draws at the
|
|
@@ -39001,11 +39873,21 @@ function createOurs(pixi, definition, value) {
|
|
|
39001
39873
|
type: input.clamp.type
|
|
39002
39874
|
};
|
|
39003
39875
|
}
|
|
39876
|
+
if (definition.backdrop) {
|
|
39877
|
+
structures[BACK_SCALE.name] = {
|
|
39878
|
+
value: new Float32Array(BACK_SCALE.size),
|
|
39879
|
+
type: BACK_SCALE.type
|
|
39880
|
+
};
|
|
39881
|
+
structures[BACK_CLAMP.name] = {
|
|
39882
|
+
value: new Float32Array(BACK_CLAMP.size),
|
|
39883
|
+
type: BACK_CLAMP.type
|
|
39884
|
+
};
|
|
39885
|
+
}
|
|
39004
39886
|
if (definition.origin !== void 0) structures[definition.origin.name] = {
|
|
39005
39887
|
value: new Float32Array(definition.origin.size),
|
|
39006
39888
|
type: definition.origin.type
|
|
39007
39889
|
};
|
|
39008
|
-
const group = definition.uniforms.length > 0 || definition.inputs.length > 0 || definition.origin !== void 0 ? new pixi.UniformGroup(structures) : void 0;
|
|
39890
|
+
const group = definition.uniforms.length > 0 || definition.inputs.length > 0 || definition.backdrop || definition.origin !== void 0 ? new pixi.UniformGroup(structures) : void 0;
|
|
39009
39891
|
const resources = group === void 0 ? {} : { fu: group };
|
|
39010
39892
|
const empty = pixi.Texture.EMPTY.source;
|
|
39011
39893
|
for (const input of definition.inputs) {
|
|
@@ -39019,7 +39901,8 @@ function createOurs(pixi, definition, value) {
|
|
|
39019
39901
|
glProgram,
|
|
39020
39902
|
resources,
|
|
39021
39903
|
padding: paddingOf$1(definition, value),
|
|
39022
|
-
resolution: resolutionOf(definition.resolution)
|
|
39904
|
+
resolution: resolutionOf(definition.resolution),
|
|
39905
|
+
blendRequired: definition.backdrop
|
|
39023
39906
|
}),
|
|
39024
39907
|
group,
|
|
39025
39908
|
definition,
|
|
@@ -39278,10 +40161,85 @@ function createFilter(ectx, pixi, kind, value, probeMap) {
|
|
|
39278
40161
|
entity: 0
|
|
39279
40162
|
});
|
|
39280
40163
|
if (instance.kind === "wgsl" && instance.definition.inputs.length > 0) wireInputs(ectx, pixi, instance);
|
|
40164
|
+
else if (instance.kind === "wgsl" && instance.definition.backdrop) wireBack(pixi, instance);
|
|
39281
40165
|
guardApply(ectx, instance, kind);
|
|
39282
40166
|
return instance;
|
|
39283
40167
|
}
|
|
39284
40168
|
/**
|
|
40169
|
+
* What was drawn under the view of the running apply: the texture Pixi's filter system copied the canvas under the
|
|
40170
|
+
* view's box into, or its empty texture outside a backdrop's pop.
|
|
40171
|
+
*
|
|
40172
|
+
* @param pixi - The module the renderer loaded.
|
|
40173
|
+
* @param manager - Pixi's filter system, inside an apply.
|
|
40174
|
+
* @returns The back texture.
|
|
40175
|
+
*/
|
|
40176
|
+
function backOf(pixi, manager) {
|
|
40177
|
+
return manager.activeBackTexture ?? pixi.Texture.EMPTY;
|
|
40178
|
+
}
|
|
40179
|
+
/**
|
|
40180
|
+
* Writes the scale and the clamp that place a sample of the body's uv on a texture: one over the size of its backing,
|
|
40181
|
+
* and half a texel inside the part of it its frame covers, rounded to float32. Written only where a value differs, so
|
|
40182
|
+
* Pixi's bind group stays cached; no array is allocated.
|
|
40183
|
+
*
|
|
40184
|
+
* @param uniforms - The values of the uniform group.
|
|
40185
|
+
* @param scaleName - The `vec2` scale uniform.
|
|
40186
|
+
* @param clampName - The `vec4` clamp uniform.
|
|
40187
|
+
* @param texture - The texture the body samples through them.
|
|
40188
|
+
*/
|
|
40189
|
+
function writePlacement(uniforms, scaleName, clampName, texture) {
|
|
40190
|
+
const { source } = texture;
|
|
40191
|
+
const scale = uniforms[scaleName];
|
|
40192
|
+
const clamp = uniforms[clampName];
|
|
40193
|
+
const halfX = .5 / source.pixelWidth;
|
|
40194
|
+
const halfY = .5 / source.pixelHeight;
|
|
40195
|
+
if (scale instanceof Float32Array) {
|
|
40196
|
+
writeAt(scale, 0, 1 / source.width, true);
|
|
40197
|
+
writeAt(scale, 1, 1 / source.height, true);
|
|
40198
|
+
}
|
|
40199
|
+
if (clamp instanceof Float32Array) {
|
|
40200
|
+
writeAt(clamp, 0, halfX, true);
|
|
40201
|
+
writeAt(clamp, 1, halfY, true);
|
|
40202
|
+
writeAt(clamp, 2, texture.frame.width / source.width - halfX, true);
|
|
40203
|
+
writeAt(clamp, 3, texture.frame.height / source.height - halfY, true);
|
|
40204
|
+
}
|
|
40205
|
+
}
|
|
40206
|
+
/**
|
|
40207
|
+
* Places a backdrop's `sampleBack` on the back texture of this apply. Pixi binds the texture itself (group 0
|
|
40208
|
+
* binding 3); the engine fills the scale and the clamp, since the copy's backing is sized at the resolution of the
|
|
40209
|
+
* surface under the view, the input's at the view's.
|
|
40210
|
+
*
|
|
40211
|
+
* @param body - The backdrop body.
|
|
40212
|
+
* @param back - What was drawn under the view.
|
|
40213
|
+
*/
|
|
40214
|
+
function bindBack(body, back) {
|
|
40215
|
+
const uniforms = body.group?.uniforms;
|
|
40216
|
+
if (uniforms !== void 0) writePlacement(uniforms, BACK_SCALE.name, BACK_CLAMP.name, back);
|
|
40217
|
+
}
|
|
40218
|
+
/**
|
|
40219
|
+
* The apply of a backdrop without inputs, as Pixi calls it: `sampleBack` placed on the copy Pixi made, then the body
|
|
40220
|
+
* drawn from the view's input.
|
|
40221
|
+
*
|
|
40222
|
+
* @param pixi - The module the renderer loaded.
|
|
40223
|
+
* @param body - The backdrop body.
|
|
40224
|
+
* @param manager - Pixi's filter system.
|
|
40225
|
+
* @param input - The view's input texture.
|
|
40226
|
+
* @param output - The output.
|
|
40227
|
+
* @param clearMode - Whether the output is cleared.
|
|
40228
|
+
*/
|
|
40229
|
+
function applyBack(pixi, body, manager, input, output, clearMode) {
|
|
40230
|
+
bindBack(body, backOf(pixi, manager));
|
|
40231
|
+
manager.applyFilter(body.filter, input, output, clearMode);
|
|
40232
|
+
}
|
|
40233
|
+
/**
|
|
40234
|
+
* Gives a backdrop body without inputs its own `apply`, which places `sampleBack` before it draws.
|
|
40235
|
+
*
|
|
40236
|
+
* @param pixi - The module the renderer loaded.
|
|
40237
|
+
* @param body - The body, just built.
|
|
40238
|
+
*/
|
|
40239
|
+
function wireBack(pixi, body) {
|
|
40240
|
+
body.filter.apply = (manager, input, output, clearMode) => applyBack(pixi, body, manager, input, output, clearMode);
|
|
40241
|
+
}
|
|
40242
|
+
/**
|
|
39285
40243
|
* The share of the resolution the last place of one input draws at.
|
|
39286
40244
|
*
|
|
39287
40245
|
* @param inputs - The chains of the instance.
|
|
@@ -39307,8 +40265,12 @@ function stagesOf(inputs) {
|
|
|
39307
40265
|
* @param inputs - The chains of the instance.
|
|
39308
40266
|
*/
|
|
39309
40267
|
function giveBack(pixi, inputs) {
|
|
39310
|
-
|
|
39311
|
-
|
|
40268
|
+
const { taken } = inputs;
|
|
40269
|
+
for (let index = 0; index < taken.length; index += 1) {
|
|
40270
|
+
const target = taken[index];
|
|
40271
|
+
if (target !== void 0) pixi.TexturePool.returnTexture(target);
|
|
40272
|
+
}
|
|
40273
|
+
while (taken.length > 0) taken.pop();
|
|
39312
40274
|
}
|
|
39313
40275
|
/**
|
|
39314
40276
|
* Draws the places of one input, each from the one before, the first from `from`, into a pooled
|
|
@@ -39318,7 +40280,7 @@ function giveBack(pixi, inputs) {
|
|
|
39318
40280
|
* @param inputs - The chains of the instance.
|
|
39319
40281
|
* @param index - The input.
|
|
39320
40282
|
* @param manager - Pixi's filter system, inside an active filter pass.
|
|
39321
|
-
* @param from - The view's input, or a kept picture.
|
|
40283
|
+
* @param from - The view's input, the copy of what was drawn under the view, or a kept picture.
|
|
39322
40284
|
* @param resolution - The resolution of `from`.
|
|
39323
40285
|
* @param target - Where the last place draws, instead of a pooled texture.
|
|
39324
40286
|
* @returns The texture the last place drew into; `from` for an empty chain.
|
|
@@ -39360,20 +40322,7 @@ function bindInput(instance, index, last) {
|
|
|
39360
40322
|
const { resources } = instance.filter;
|
|
39361
40323
|
if (resources[input.texture] !== source) resources[input.texture] = source;
|
|
39362
40324
|
if (resources[input.sampler] !== source.style) resources[input.sampler] = source.style;
|
|
39363
|
-
|
|
39364
|
-
const clamp = uniforms[input.clamp.name];
|
|
39365
|
-
const halfX = .5 / source.pixelWidth;
|
|
39366
|
-
const halfY = .5 / source.pixelHeight;
|
|
39367
|
-
if (scale instanceof Float32Array) {
|
|
39368
|
-
writeAt(scale, 0, 1 / source.width, true);
|
|
39369
|
-
writeAt(scale, 1, 1 / source.height, true);
|
|
39370
|
-
}
|
|
39371
|
-
if (clamp instanceof Float32Array) {
|
|
39372
|
-
writeAt(clamp, 0, halfX, true);
|
|
39373
|
-
writeAt(clamp, 1, halfY, true);
|
|
39374
|
-
writeAt(clamp, 2, last.frame.width / source.width - halfX, true);
|
|
39375
|
-
writeAt(clamp, 3, last.frame.height / source.height - halfY, true);
|
|
39376
|
-
}
|
|
40325
|
+
writePlacement(uniforms, input.scale.name, input.clamp.name, last);
|
|
39377
40326
|
}
|
|
39378
40327
|
/**
|
|
39379
40328
|
* Points every input's texture and sampler back at Pixi's empty texture: a filter never holds a
|
|
@@ -39394,11 +40343,24 @@ function unbindInputs(pixi, instance) {
|
|
|
39394
40343
|
}
|
|
39395
40344
|
}
|
|
39396
40345
|
/**
|
|
40346
|
+
* Tells whether the chains of an instance must run again for the resolutions of this apply: the view's input's for a
|
|
40347
|
+
* chain that starts from it, what was drawn under the view's for one that starts from back.
|
|
40348
|
+
*
|
|
40349
|
+
* @param inputs - The chains of the instance.
|
|
40350
|
+
* @param resolution - The resolution of the view's input.
|
|
40351
|
+
* @param backResolution - The resolution of the back; the input's for a filter that reads none.
|
|
40352
|
+
* @returns True when one the chains start from changed.
|
|
40353
|
+
*/
|
|
40354
|
+
function resolutionMoved(inputs, resolution, backResolution) {
|
|
40355
|
+
return inputs.fromInput && resolution !== inputs.resolution || inputs.fromBack && backResolution !== inputs.backResolution;
|
|
40356
|
+
}
|
|
40357
|
+
/**
|
|
39397
40358
|
* The apply of a filter with inputs, as Pixi calls it: a resolution change runs the chains once
|
|
39398
|
-
* at the new value, then every place draws,
|
|
39399
|
-
*
|
|
39400
|
-
*
|
|
39401
|
-
*
|
|
40359
|
+
* at the new value, then every place draws, each chain from the view's input or, `from: "back"`,
|
|
40360
|
+
* from the copy Pixi made of the canvas under the view, the last texture of each input is bound, a
|
|
40361
|
+
* backdrop's `sampleBack` is placed, and the main body draws from the view's input. Held results are
|
|
40362
|
+
* bound instead and no stage draws. The pooled textures go back and the inputs are unbound in a
|
|
40363
|
+
* `finally`, also when a place throws; a throw then reaches the guard, which breaks the instance.
|
|
39402
40364
|
*
|
|
39403
40365
|
* @param ectx - Domain context of the effects plugin.
|
|
39404
40366
|
* @param pixi - The module the renderer loaded.
|
|
@@ -39411,16 +40373,23 @@ function unbindInputs(pixi, instance) {
|
|
|
39411
40373
|
*/
|
|
39412
40374
|
function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode) {
|
|
39413
40375
|
const { resolution } = input.source;
|
|
39414
|
-
|
|
40376
|
+
const { definition } = instance;
|
|
40377
|
+
const back = definition.backdrop ? backOf(pixi, manager) : input;
|
|
40378
|
+
const backResolution = inputs.fromBack ? back.source.resolution : resolution;
|
|
40379
|
+
if (inputs.held === void 0 && resolutionMoved(inputs, resolution, backResolution)) evaluateInRender(ectx, pixi, instance, inputs, resolution, backResolution);
|
|
39415
40380
|
if (instance.broken) {
|
|
39416
40381
|
drawBroken(instance, manager, input, output, clearMode);
|
|
39417
40382
|
return;
|
|
39418
40383
|
}
|
|
39419
40384
|
try {
|
|
39420
40385
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
39421
|
-
const
|
|
40386
|
+
const fromBack = definition.inputs[index]?.from === "back";
|
|
40387
|
+
const start = fromBack ? back : input;
|
|
40388
|
+
const startResolution = fromBack ? backResolution : resolution;
|
|
40389
|
+
const last = inputs.held === void 0 ? drawChain(pixi, inputs, index, manager, start, startResolution) : inputs.held[index] ?? input;
|
|
39422
40390
|
bindInput(instance, index, last);
|
|
39423
40391
|
}
|
|
40392
|
+
if (definition.backdrop) bindBack(instance, back);
|
|
39424
40393
|
manager.applyFilter(instance.filter, input, output, clearMode);
|
|
39425
40394
|
} finally {
|
|
39426
40395
|
giveBack(pixi, inputs);
|
|
@@ -39442,7 +40411,7 @@ function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clear
|
|
|
39442
40411
|
*/
|
|
39443
40412
|
function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
39444
40413
|
const { resolution } = picture.source;
|
|
39445
|
-
if (!instance.broken && resolution !== inputs.resolution) evaluateInRender(ectx, pixi, instance, inputs, resolution);
|
|
40414
|
+
if (!instance.broken && resolution !== inputs.resolution) evaluateInRender(ectx, pixi, instance, inputs, resolution, resolution);
|
|
39446
40415
|
if (instance.broken) return;
|
|
39447
40416
|
try {
|
|
39448
40417
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
@@ -39462,10 +40431,14 @@ function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
|
39462
40431
|
* @param instance - The instance, just built.
|
|
39463
40432
|
*/
|
|
39464
40433
|
function wireInputs(ectx, pixi, instance) {
|
|
39465
|
-
const
|
|
40434
|
+
const declared = instance.definition.inputs;
|
|
40435
|
+
const count = declared.length;
|
|
39466
40436
|
const inputs = {
|
|
39467
40437
|
stale: true,
|
|
39468
40438
|
resolution: 0,
|
|
40439
|
+
backResolution: 0,
|
|
40440
|
+
fromInput: declared.some((input) => input.from === "input"),
|
|
40441
|
+
fromBack: declared.some((input) => input.from === "back"),
|
|
39469
40442
|
version: 0,
|
|
39470
40443
|
context: { resolution: 0 },
|
|
39471
40444
|
places: Array.from({ length: count }, () => []),
|
|
@@ -39491,7 +40464,9 @@ function wireInputs(ectx, pixi, instance) {
|
|
|
39491
40464
|
instance.inputs = inputs;
|
|
39492
40465
|
instance.filter.apply = (manager, input, output, clearMode) => applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode);
|
|
39493
40466
|
const { resolution } = instance.definition;
|
|
39494
|
-
|
|
40467
|
+
const canvas = ectx.deps.renderer.host.resolution();
|
|
40468
|
+
const own = resolution > 0 ? resolution : canvas;
|
|
40469
|
+
evaluate(ectx, pixi, instance, inputs, own, inputs.fromBack ? canvas : own);
|
|
39495
40470
|
}
|
|
39496
40471
|
/**
|
|
39497
40472
|
* Reads one property of a value a chain returned, whatever its type.
|
|
@@ -39685,9 +40660,10 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39685
40660
|
return changed || rebuilt;
|
|
39686
40661
|
}
|
|
39687
40662
|
/**
|
|
39688
|
-
* Runs the chain of every input for the instance's value at a resolution and places its steps
|
|
40663
|
+
* Runs the chain of every input for the instance's value at a resolution and places its steps: a chain that starts
|
|
40664
|
+
* from the view's input at the input's resolution, one that starts from back at the back's.
|
|
39689
40665
|
* A chain that throws or returns a step it may not draw breaks the instance (reason `"chain"`).
|
|
39690
|
-
* The version moves when a place, a scale, a stage uniform or
|
|
40666
|
+
* The version moves when a place, a scale, a stage uniform or a resolution changed; the passes
|
|
39691
40667
|
* become the main body plus every place.
|
|
39692
40668
|
*
|
|
39693
40669
|
* @param ectx - Domain context of the effects plugin.
|
|
@@ -39695,14 +40671,15 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39695
40671
|
* @param instance - The instance.
|
|
39696
40672
|
* @param inputs - Its chains.
|
|
39697
40673
|
* @param resolution - The resolution of the texture the view is drawn into.
|
|
40674
|
+
* @param backResolution - The resolution of what was drawn under the view; the input's for a filter that reads none.
|
|
39698
40675
|
*/
|
|
39699
|
-
function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
40676
|
+
function evaluate(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
39700
40677
|
const kind = ectx.state.kinds.get(instance.kindId);
|
|
39701
|
-
let changed = resolution !== inputs.resolution;
|
|
40678
|
+
let changed = resolution !== inputs.resolution || backResolution !== inputs.backResolution;
|
|
39702
40679
|
let passes = 1;
|
|
39703
|
-
inputs.context.resolution = resolution;
|
|
39704
40680
|
for (const [index, input] of instance.definition.inputs.entries()) {
|
|
39705
40681
|
let steps;
|
|
40682
|
+
inputs.context.resolution = input.from === "back" ? backResolution : resolution;
|
|
39706
40683
|
try {
|
|
39707
40684
|
steps = input.chain(instance.value, inputs.context);
|
|
39708
40685
|
} catch (error) {
|
|
@@ -39718,6 +40695,7 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39718
40695
|
passes += steps.length;
|
|
39719
40696
|
}
|
|
39720
40697
|
inputs.resolution = resolution;
|
|
40698
|
+
inputs.backResolution = backResolution;
|
|
39721
40699
|
inputs.stale = false;
|
|
39722
40700
|
instance.passes = passes;
|
|
39723
40701
|
if (changed) inputs.version += 1;
|
|
@@ -39732,9 +40710,10 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39732
40710
|
* @param instance - The instance.
|
|
39733
40711
|
* @param inputs - Its chains.
|
|
39734
40712
|
* @param resolution - The resolution of the texture the view is drawn into.
|
|
40713
|
+
* @param backResolution - The resolution of what was drawn under the view.
|
|
39735
40714
|
*/
|
|
39736
|
-
function evaluateInRender(ectx, pixi, instance, inputs, resolution) {
|
|
39737
|
-
evaluate(ectx, pixi, instance, inputs, resolution);
|
|
40715
|
+
function evaluateInRender(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
40716
|
+
evaluate(ectx, pixi, instance, inputs, resolution, backResolution);
|
|
39738
40717
|
ectx.state.filterSync.all = true;
|
|
39739
40718
|
}
|
|
39740
40719
|
/**
|
|
@@ -39756,12 +40735,13 @@ function refreshInputs(ectx, pixi, instance, off) {
|
|
|
39756
40735
|
inputs.retired.length = 0;
|
|
39757
40736
|
}
|
|
39758
40737
|
if (instance.broken || !instance.filter.enabled || off || !inputs.stale) return;
|
|
39759
|
-
evaluate(ectx, pixi, instance, inputs, inputs.resolution);
|
|
40738
|
+
evaluate(ectx, pixi, instance, inputs, inputs.resolution, inputs.backResolution);
|
|
39760
40739
|
}
|
|
39761
40740
|
/**
|
|
39762
40741
|
* The body a stand-in of a dev build draws: the fallback with the striped mark over it, or the
|
|
39763
40742
|
* mark alone. The pair draws the fallback into one pooled texture of the input's frame and
|
|
39764
|
-
* resolution, then the mark from it into the output, and gives the texture back in a `finally`.
|
|
40743
|
+
* resolution, then the mark from it into the output, and gives the texture back in a `finally`. A
|
|
40744
|
+
* backdrop fallback has its `sampleBack` placed first.
|
|
39765
40745
|
*
|
|
39766
40746
|
* @param pixi - The module the renderer loaded.
|
|
39767
40747
|
* @param fallback - The checked fallback, or `undefined`.
|
|
@@ -39786,6 +40766,7 @@ function markedBody(pixi, fallback, value) {
|
|
|
39786
40766
|
request.height = input.frame.height;
|
|
39787
40767
|
request.resolution = input.source.resolution;
|
|
39788
40768
|
const pooled = pixi.TexturePool.getOptimalTexture(request);
|
|
40769
|
+
if (body.definition.backdrop) bindBack(body, backOf(pixi, manager));
|
|
39789
40770
|
try {
|
|
39790
40771
|
manager.applyFilter(body.filter, input, pooled, true);
|
|
39791
40772
|
manager.applyFilter(mark.filter, pooled, output, clearMode);
|
|
@@ -39815,7 +40796,10 @@ function createStandIn(ectx, pixi, kind, value) {
|
|
|
39815
40796
|
const usable = status === "ok" ? fallback : void 0;
|
|
39816
40797
|
let body;
|
|
39817
40798
|
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ === true) body = markedBody(pixi, usable, value);
|
|
39818
|
-
else if (usable !== void 0)
|
|
40799
|
+
else if (usable !== void 0) {
|
|
40800
|
+
body = createOurs(pixi, usable, value);
|
|
40801
|
+
if (usable.backdrop) wireBack(pixi, body);
|
|
40802
|
+
}
|
|
39819
40803
|
if (body === void 0) return void 0;
|
|
39820
40804
|
body.filter.enabled = value.enabled;
|
|
39821
40805
|
body.areaStale = body.definition.area !== void 0;
|
|
@@ -41001,14 +41985,27 @@ function cacheOfInstance(instance) {
|
|
|
41001
41985
|
return instance.kind === "wgsl" ? instance.definition.cache : CORE_CACHE[instance.kind];
|
|
41002
41986
|
}
|
|
41003
41987
|
/**
|
|
41988
|
+
* Tells whether one of the inputs of a definition starts from the view's own input: its chain draws a copy of the
|
|
41989
|
+
* view's subtree. A chain from back draws what was drawn under the view instead.
|
|
41990
|
+
*
|
|
41991
|
+
* @param definition - The definition.
|
|
41992
|
+
* @returns True when one does.
|
|
41993
|
+
*/
|
|
41994
|
+
function readsOwnInput(definition) {
|
|
41995
|
+
const { inputs } = definition;
|
|
41996
|
+
for (let index = 0; index < inputs.length; index += 1) if (inputs[index]?.from === "input") return true;
|
|
41997
|
+
return false;
|
|
41998
|
+
}
|
|
41999
|
+
/**
|
|
41004
42000
|
* Whether an instance shows a blurred copy of what is under it (spec 23 P8.5): the built-in `Blur`, or one of ours
|
|
41005
|
-
* with
|
|
42001
|
+
* with an input drawn from its own subtree. A backdrop whose chains start from back blurs the scene under the view,
|
|
42002
|
+
* not the view: an overlay holds no loops for it. The predicate an overlay's held loops read (spec 22).
|
|
41006
42003
|
*
|
|
41007
42004
|
* @param instance - The instance.
|
|
41008
42005
|
* @returns True, or `undefined` for every other filter (the slot leaves the key out).
|
|
41009
42006
|
*/
|
|
41010
42007
|
function blursOf(instance) {
|
|
41011
|
-
return instance.kind === "blur" || instance.kind === "wgsl" && instance.definition
|
|
42008
|
+
return instance.kind === "blur" || instance.kind === "wgsl" && readsOwnInput(instance.definition) ? true : void 0;
|
|
41012
42009
|
}
|
|
41013
42010
|
/**
|
|
41014
42011
|
* Tells whether a debug switch turned a kind off (`effects.diagnostics.disable`). The guard is
|
|
@@ -59151,6 +60148,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
59151
60148
|
* | renderer | preference | "webgpu", WebGL is Pixi's fallback |
|
|
59152
60149
|
* | renderer | aspect | { min: 4/3, max: 21/9 }; shallow merge: a game that sets `aspect` gives both fields |
|
|
59153
60150
|
* | renderer | poolLimit | 256 |
|
|
60151
|
+
* | renderer | adaptiveScale | { floor: 0.75, devices: "phone" }, the render scale of the adaptive frame rate on a GPU-bound phone; false keeps the resolution; shallow merge: a game that sets it gives both fields |
|
|
59154
60152
|
* | renderer | unsupportedMessage | "This device cannot run the game." |
|
|
59155
60153
|
* | renderer | loadPixi | () => import("pixi.js") |
|
|
59156
60154
|
* | 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 |
|