@moku-labs/game 0.15.5 → 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-e_I5i8dK.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-Bq_93oZv.d.mts → index-CU4Oc2Qv.d.mts} +19 -10
- 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-DnclBdeU.mjs → src-nJYt0LMr.mjs} +1353 -247
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-C2g2KYCN.d.mts → types-BkcgFOoB.d.mts} +55 -13
- 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-6inPYTzE.d.mts → types-MocH_igX.d.mts} +4 -4
- package/dist/{visual-DalSHy9z.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,
|
|
@@ -12317,7 +12432,7 @@ function withinRatio(differing, bytes) {
|
|
|
12317
12432
|
* @param host - The host module (`resolution()`).
|
|
12318
12433
|
* @returns Device pixels per CSS pixel; `undefined` for the half resolution of `host.pixels`.
|
|
12319
12434
|
*/
|
|
12320
|
-
function readResolution(ctx, host) {
|
|
12435
|
+
function readResolution$1(ctx, host) {
|
|
12321
12436
|
return hasPictures(ctx.state.kept) ? host.resolution() : void 0;
|
|
12322
12437
|
}
|
|
12323
12438
|
/**
|
|
@@ -12341,7 +12456,7 @@ function hasPictures(kept) {
|
|
|
12341
12456
|
* @param decided - The decision of this frame, changed in place when a missed frame is drawn.
|
|
12342
12457
|
*/
|
|
12343
12458
|
function verifyFrame(ctx, state, host, decided) {
|
|
12344
|
-
const resolution = readResolution(ctx, host);
|
|
12459
|
+
const resolution = readResolution$1(ctx, host);
|
|
12345
12460
|
if (decided.reason !== "none") {
|
|
12346
12461
|
state.verify.reference = host.pixels(resolution);
|
|
12347
12462
|
return;
|
|
@@ -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
|
}
|
|
@@ -15357,7 +16095,7 @@ const BEFORE = {
|
|
|
15357
16095
|
* @param resolution - The target's resolution.
|
|
15358
16096
|
* @returns Texels per CSS px.
|
|
15359
16097
|
*/
|
|
15360
|
-
function resolutionOf(filter, resolution) {
|
|
16098
|
+
function resolutionOf$1(filter, resolution) {
|
|
15361
16099
|
const own = filter.resolution;
|
|
15362
16100
|
return own === "inherit" ? resolution : own;
|
|
15363
16101
|
}
|
|
@@ -15377,7 +16115,7 @@ function readStack(slots, resolution, into) {
|
|
|
15377
16115
|
const filter = slots[index]?.filter;
|
|
15378
16116
|
if (filter === void 0 || !filter.enabled) continue;
|
|
15379
16117
|
enabled += 1;
|
|
15380
|
-
rs = Math.min(rs, resolutionOf(filter, resolution));
|
|
16118
|
+
rs = Math.min(rs, resolutionOf$1(filter, resolution));
|
|
15381
16119
|
padding += filter.padding;
|
|
15382
16120
|
}
|
|
15383
16121
|
into.enabled = enabled;
|
|
@@ -21697,7 +22435,7 @@ const NO_DETAIL = Object.freeze({});
|
|
|
21697
22435
|
/** The fewest device pixels per CSS pixel `setResolution` draws at. */
|
|
21698
22436
|
const MIN_RESOLUTION = .5;
|
|
21699
22437
|
/** The most device pixels per CSS pixel `setResolution` draws at. */
|
|
21700
|
-
const MAX_RESOLUTION = 4;
|
|
22438
|
+
const MAX_RESOLUTION$1 = 4;
|
|
21701
22439
|
/**
|
|
21702
22440
|
* Message of a resolution `setResolution` refuses.
|
|
21703
22441
|
*
|
|
@@ -21705,7 +22443,7 @@ const MAX_RESOLUTION = 4;
|
|
|
21705
22443
|
* @returns The message.
|
|
21706
22444
|
*/
|
|
21707
22445
|
function resolutionMessage(value) {
|
|
21708
|
-
return `[game] renderer.monitor.setResolution(${String(value)}) needs a number from ${MIN_RESOLUTION} to ${MAX_RESOLUTION}.\n Pass undefined to go back to maxResolution.`;
|
|
22446
|
+
return `[game] renderer.monitor.setResolution(${String(value)}) needs a number from ${MIN_RESOLUTION} to ${MAX_RESOLUTION$1}.\n Pass undefined to go back to maxResolution.`;
|
|
21709
22447
|
}
|
|
21710
22448
|
/**
|
|
21711
22449
|
* Tells whether `setResolution` takes a value: a number from `MIN_RESOLUTION` to `MAX_RESOLUTION`, or `undefined` to
|
|
@@ -21719,7 +22457,7 @@ function resolutionMessage(value) {
|
|
|
21719
22457
|
* ```
|
|
21720
22458
|
*/
|
|
21721
22459
|
function takesResolution(value) {
|
|
21722
|
-
return value === void 0 || typeof value === "number" && value >= MIN_RESOLUTION && value <= MAX_RESOLUTION;
|
|
22460
|
+
return value === void 0 || typeof value === "number" && value >= MIN_RESOLUTION && value <= MAX_RESOLUTION$1;
|
|
21723
22461
|
}
|
|
21724
22462
|
/**
|
|
21725
22463
|
* Creates the monitor module: the counters of the renderer, the capture of a frame, and the
|
|
@@ -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";
|
|
@@ -38013,6 +38830,61 @@ function readArea(id, area) {
|
|
|
38013
38830
|
return area;
|
|
38014
38831
|
}
|
|
38015
38832
|
/**
|
|
38833
|
+
* Reads the declared resolution: texels per CSS px, or 0 for the canvas's. A JavaScript caller may pass anything.
|
|
38834
|
+
*
|
|
38835
|
+
* @param id - The filter id.
|
|
38836
|
+
* @param resolution - What the game declared.
|
|
38837
|
+
* @returns The resolution; 0 when it was left out.
|
|
38838
|
+
* @throws {Error} When it is given and is not a number from 0 to `MAX_RESOLUTION`.
|
|
38839
|
+
*/
|
|
38840
|
+
function readResolution(id, resolution) {
|
|
38841
|
+
if (resolution === void 0) return 0;
|
|
38842
|
+
if (typeof resolution === "number" && resolution >= 0 && resolution <= 4) return resolution;
|
|
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)}.`);
|
|
38844
|
+
}
|
|
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
|
+
/**
|
|
38016
38888
|
* Tells whether a value is a list of 2 to 4 finite numbers.
|
|
38017
38889
|
*
|
|
38018
38890
|
* @param value - A uniform declaration.
|
|
@@ -38193,21 +39065,23 @@ function readStages(id, name, stages) {
|
|
|
38193
39065
|
for (const [index, stage] of stages.entries()) {
|
|
38194
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.`);
|
|
38195
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.`);
|
|
38196
39069
|
if (!read.includes(stage)) read.push(stage);
|
|
38197
39070
|
}
|
|
38198
39071
|
return Object.freeze(read);
|
|
38199
39072
|
}
|
|
38200
39073
|
/**
|
|
38201
|
-
* Reads the inputs of a definition: the names the engine declares for each, its
|
|
38202
|
-
* 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.
|
|
38203
39076
|
*
|
|
38204
39077
|
* @param id - The filter id.
|
|
38205
39078
|
* @param declared - What the game declared under `inputs`.
|
|
38206
39079
|
* @param uniforms - The read uniforms.
|
|
39080
|
+
* @param backdrop - Whether the filter is a backdrop: only then may an input start from back.
|
|
38207
39081
|
* @returns The inputs in declaration order, frozen; empty without `inputs`.
|
|
38208
39082
|
* @throws {Error} When `inputs` is not an object, has more than four entries, or an input breaks one of its rules.
|
|
38209
39083
|
*/
|
|
38210
|
-
function readInputs(id, declared, uniforms) {
|
|
39084
|
+
function readInputs(id, declared, uniforms, backdrop) {
|
|
38211
39085
|
if (declared === void 0) return Object.freeze([]);
|
|
38212
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 } }.`);
|
|
38213
39087
|
const entries = Object.entries(declared);
|
|
@@ -38215,10 +39089,12 @@ function readInputs(id, declared, uniforms) {
|
|
|
38215
39089
|
const taken = /* @__PURE__ */ new Set();
|
|
38216
39090
|
return Object.freeze(entries.map(([name, input]) => {
|
|
38217
39091
|
const names = checkInputNames(id, name, uniforms, taken);
|
|
39092
|
+
const from = readFrom(id, name, input.from, backdrop);
|
|
38218
39093
|
const stages = readStages(id, name, input.stages);
|
|
38219
39094
|
if (typeof input.chain !== "function") throw new TypeError(`[game] Filter "${id}": input "${name}" needs a chain.\n Write chain: (value, { resolution }) => [{ stage, scale }].`);
|
|
38220
39095
|
return Object.freeze({
|
|
38221
39096
|
name,
|
|
39097
|
+
from,
|
|
38222
39098
|
texture: names.texture,
|
|
38223
39099
|
sampler: names.sampler,
|
|
38224
39100
|
scale: Object.freeze({
|
|
@@ -38241,21 +39117,23 @@ function readInputs(id, declared, uniforms) {
|
|
|
38241
39117
|
}
|
|
38242
39118
|
/**
|
|
38243
39119
|
* Reads the fallback of a definition: another filter of ours, drawing alone, over a subset of its
|
|
38244
|
-
* 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.
|
|
38245
39121
|
*
|
|
38246
39122
|
* @param id - The filter id.
|
|
38247
39123
|
* @param fallback - What the game declared under `fallback`.
|
|
38248
39124
|
* @param uniforms - The read uniforms.
|
|
39125
|
+
* @param backdrop - Whether the filter is a backdrop.
|
|
38249
39126
|
* @returns The fallback's definition, or `undefined` without one.
|
|
38250
|
-
* @throws {Error} When it is no filter of ours, has the same id, has inputs or a fallback,
|
|
38251
|
-
* 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.
|
|
38252
39129
|
*/
|
|
38253
|
-
function readFallback(id, fallback, uniforms) {
|
|
39130
|
+
function readFallback(id, fallback, uniforms, backdrop) {
|
|
38254
39131
|
if (fallback === void 0) return void 0;
|
|
38255
39132
|
if (!isDefinedFilter(fallback)) throw new Error(`[game] Filter "${id}": fallback is not a filter made by defineFilter.\n Pass what defineFilter returned.`);
|
|
38256
39133
|
const definition = fallback.filter;
|
|
38257
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.`);
|
|
38258
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.`);
|
|
38259
39137
|
for (const uniform of definition.uniforms) {
|
|
38260
39138
|
const own = uniforms.find((candidate) => candidate.name === uniform.name);
|
|
38261
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.`);
|
|
@@ -38303,18 +39181,22 @@ function cacheWithInputs(cache, inputs) {
|
|
|
38303
39181
|
* `enabled: true` and `order: 0`. An input `blur` gives the body `sampleBlur(uv)`: the last texture
|
|
38304
39182
|
* of its chain, read at the body's `uv`. A `fallback` draws instead when the filter is broken.
|
|
38305
39183
|
* An `area` answers, from the value, the rectangle in the entity's own units outside of which the
|
|
38306
|
-
* filter draws nothing: Pixi then renders and fills only that box, not the view's bounds.
|
|
38307
|
-
*
|
|
38308
|
-
*
|
|
39184
|
+
* filter draws nothing: Pixi then renders and fills only that box, not the view's bounds. A
|
|
39185
|
+
* `resolution` draws the view and its filters at that many texels per CSS px instead of the
|
|
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.
|
|
38309
39190
|
*
|
|
38310
39191
|
* @param id - The filter id, also the component name; unique, and not one of the built-ins.
|
|
38311
39192
|
* @param spec - Both bodies, the uniforms, the passes one apply costs, the padding, the inputs, the fallback, the
|
|
38312
|
-
* cache category and the
|
|
39193
|
+
* cache category, the area, the resolution and the backdrop switch.
|
|
38313
39194
|
* @returns The component type, with the frozen `filter` definition attached.
|
|
38314
39195
|
* @throws {Error} When the id is empty, the WGSL has no `mainFragment`, the GLSL no `void main(`,
|
|
38315
39196
|
* a uniform is reserved, badly named or of no known form, the passes or the padding break
|
|
38316
39197
|
* their rules, an input or the fallback breaks one of its rules, `passes` is given with inputs,
|
|
38317
|
-
*
|
|
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.
|
|
38318
39200
|
* @example
|
|
38319
39201
|
* ```ts
|
|
38320
39202
|
* // features/board/effects.ts: a gold tint a motion hook tweens on a merge, on WebGPU and WebGL.
|
|
@@ -38328,20 +39210,23 @@ function defineFilter(id, spec) {
|
|
|
38328
39210
|
readSpec(id, spec);
|
|
38329
39211
|
const read = Object.entries(spec.uniforms ?? {}).map(([name, declaration]) => readUniform(id, name, declaration));
|
|
38330
39212
|
const uniforms = Object.freeze(read.map((entry) => entry.spec));
|
|
38331
|
-
const
|
|
39213
|
+
const backdrop = readBackdrop(id, spec.backdrop);
|
|
39214
|
+
const inputs = readInputs(id, spec.inputs, uniforms, backdrop);
|
|
38332
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.`);
|
|
38333
|
-
const fallback = readFallback(id, spec.fallback, uniforms);
|
|
39216
|
+
const fallback = readFallback(id, spec.fallback, uniforms, backdrop);
|
|
38334
39217
|
const passes = spec.passes ?? 1;
|
|
38335
39218
|
const padding = spec.padding ?? 0;
|
|
38336
39219
|
checkCost(id, passes, padding, uniforms);
|
|
38337
39220
|
checkCache(id, spec.cache);
|
|
39221
|
+
if (backdrop) checkBackdrop(id, padding, spec.cache);
|
|
38338
39222
|
const area = readArea(id, spec.area);
|
|
39223
|
+
const resolution = readResolution(id, spec.resolution);
|
|
38339
39224
|
const origin = callsCanvasPixel(spec.wgsl, spec.glsl) ? KEPT_ORIGIN : void 0;
|
|
38340
|
-
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache), inputs);
|
|
39225
|
+
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache, backdrop), inputs);
|
|
38341
39226
|
const filter = Object.freeze({
|
|
38342
39227
|
id,
|
|
38343
|
-
source: assemble(uniforms, inputs, spec.wgsl, origin),
|
|
38344
|
-
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),
|
|
38345
39230
|
uniforms,
|
|
38346
39231
|
passes,
|
|
38347
39232
|
padding,
|
|
@@ -38349,9 +39234,11 @@ function defineFilter(id, spec) {
|
|
|
38349
39234
|
fallback,
|
|
38350
39235
|
cache,
|
|
38351
39236
|
origin,
|
|
39237
|
+
resolution,
|
|
39238
|
+
backdrop,
|
|
38352
39239
|
...area === void 0 ? {} : { area }
|
|
38353
39240
|
});
|
|
38354
|
-
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);
|
|
38355
39242
|
const defaults = Object.fromEntries([
|
|
38356
39243
|
...read.map((entry) => [entry.spec.name, entry.value]),
|
|
38357
39244
|
["enabled", true],
|
|
@@ -38569,6 +39456,7 @@ const displacementDefaults = {
|
|
|
38569
39456
|
};
|
|
38570
39457
|
const alphaDefaults = {
|
|
38571
39458
|
alpha: 1,
|
|
39459
|
+
resolution: 0,
|
|
38572
39460
|
enabled: true,
|
|
38573
39461
|
order: 0
|
|
38574
39462
|
};
|
|
@@ -38665,11 +39553,16 @@ const Noise = /*#__PURE__*/ component("effects.noise", noiseDefaults);
|
|
|
38665
39553
|
const Displacement = /*#__PURE__*/ component("effects.displacement", displacementDefaults);
|
|
38666
39554
|
/**
|
|
38667
39555
|
* Pixi's alpha over a view and its subtree as one layer, so overlapping children do not show
|
|
38668
|
-
* through each other.
|
|
39556
|
+
* through each other. `resolution: 0` draws at the resolution of the canvas; a number draws the
|
|
39557
|
+
* subtree at that many texels per CSS px, at most 4, and every other filter of the view with it
|
|
39558
|
+
* (Pixi's smallest resolution wins). At `alpha: 1` and `resolution: 0` the filter is switched off
|
|
39559
|
+
* after 8 frames there: it would copy the subtree unchanged through a pass of its own, and a
|
|
39560
|
+
* keyframe that only touches 1 switches nothing. One render pass per apply.
|
|
38669
39561
|
*
|
|
38670
39562
|
* @example
|
|
38671
39563
|
* ```ts
|
|
38672
|
-
*
|
|
39564
|
+
* // The fog of a memory screen, drawn at half a texel per CSS px through one pass.
|
|
39565
|
+
* Alpha({ resolution: 0.5 }).value; // { alpha: 1, resolution: 0.5, enabled: true, order: 0 }
|
|
38673
39566
|
* ```
|
|
38674
39567
|
*/
|
|
38675
39568
|
const Alpha = /*#__PURE__*/ component("effects.alpha", alphaDefaults);
|
|
@@ -38794,6 +39687,22 @@ function brokenMark() {
|
|
|
38794
39687
|
//#endregion
|
|
38795
39688
|
//#region src/plugins/effects/filters/instance.ts
|
|
38796
39689
|
/**
|
|
39690
|
+
* @file effects/filters — the only file that builds, writes and destroys Pixi filters. Ours: a
|
|
39691
|
+
* `Filter` over a `GpuProgram` of the assembled WGSL and a `GlProgram` of the assembled GLSL, with
|
|
39692
|
+
* one `UniformGroup` both read. The Pixi-core kinds: their own class, written through their setters
|
|
39693
|
+
* when a field differs from what they were last given. Every kind but `Blur` renders at
|
|
39694
|
+
* `resolution: "inherit"`, the resolution of the canvas, so a filtered view stays sharp on DPR 2-3
|
|
39695
|
+
* (decision 32), unless its value (`Alpha`) or its definition (`defineFilter`) gives a resolution of
|
|
39696
|
+
* its own. An `Alpha` at 1 drawn at the canvas's is switched off once it rested there: it would copy
|
|
39697
|
+
* the view unchanged through a pass. Every write compares first: a value the filter holds already writes nothing and
|
|
39698
|
+
* asks the renderer for no frame. A destroy frees the uniform buffers first: `Filter.destroy()`
|
|
39699
|
+
* leaves them to a GC that runs after a minute (P10). A filter with inputs draws each place of its
|
|
39700
|
+
* chains through Pixi's public `applyFilter` from inside its own `apply`, into pooled textures given
|
|
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.
|
|
39704
|
+
*/
|
|
39705
|
+
/**
|
|
38797
39706
|
* The resolution of every filter but `Blur`: the render target's, so a filter draws at the
|
|
38798
39707
|
* resolution of the canvas. Pixi's default of 1 would draw it at half the pixels on DPR 2.
|
|
38799
39708
|
*/
|
|
@@ -38808,6 +39717,20 @@ const PROBE_VALUE = Object.freeze({
|
|
|
38808
39717
|
/** The numbers of a vector field that holds none. */
|
|
38809
39718
|
const NO_NUMBERS = Object.freeze([]);
|
|
38810
39719
|
/**
|
|
39720
|
+
* The resolution Pixi is given for a resolution of a value or a definition: the number, at most `MAX_RESOLUTION`, or
|
|
39721
|
+
* the canvas's for 0 (or less, or not a number).
|
|
39722
|
+
*
|
|
39723
|
+
* @param resolution - Texels per CSS px; 0 for the canvas's.
|
|
39724
|
+
* @returns What a filter's `resolution` takes.
|
|
39725
|
+
* @example
|
|
39726
|
+
* ```ts
|
|
39727
|
+
* resolutionOf(50); // 4
|
|
39728
|
+
* ```
|
|
39729
|
+
*/
|
|
39730
|
+
function resolutionOf(resolution) {
|
|
39731
|
+
return resolution > 0 ? Math.min(resolution, 4) : CANVAS_RESOLUTION;
|
|
39732
|
+
}
|
|
39733
|
+
/**
|
|
38811
39734
|
* Tells whether two numbers are the same value for the GPU: equal, or both NaN.
|
|
38812
39735
|
*
|
|
38813
39736
|
* @param a - One number.
|
|
@@ -38950,11 +39873,21 @@ function createOurs(pixi, definition, value) {
|
|
|
38950
39873
|
type: input.clamp.type
|
|
38951
39874
|
};
|
|
38952
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
|
+
}
|
|
38953
39886
|
if (definition.origin !== void 0) structures[definition.origin.name] = {
|
|
38954
39887
|
value: new Float32Array(definition.origin.size),
|
|
38955
39888
|
type: definition.origin.type
|
|
38956
39889
|
};
|
|
38957
|
-
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;
|
|
38958
39891
|
const resources = group === void 0 ? {} : { fu: group };
|
|
38959
39892
|
const empty = pixi.Texture.EMPTY.source;
|
|
38960
39893
|
for (const input of definition.inputs) {
|
|
@@ -38968,7 +39901,8 @@ function createOurs(pixi, definition, value) {
|
|
|
38968
39901
|
glProgram,
|
|
38969
39902
|
resources,
|
|
38970
39903
|
padding: paddingOf$1(definition, value),
|
|
38971
|
-
resolution:
|
|
39904
|
+
resolution: resolutionOf(definition.resolution),
|
|
39905
|
+
blendRequired: definition.backdrop
|
|
38972
39906
|
}),
|
|
38973
39907
|
group,
|
|
38974
39908
|
definition,
|
|
@@ -39010,6 +39944,43 @@ function blurResolution(ectx, value) {
|
|
|
39010
39944
|
return ectx.state.phone ? ectx.config.blur.phoneResolution : 1;
|
|
39011
39945
|
}
|
|
39012
39946
|
/**
|
|
39947
|
+
* Tells whether an `Alpha` value draws its view unchanged: switched on, at alpha 1, at the canvas's resolution. Its
|
|
39948
|
+
* filter would copy the view through a pass of its own. The fields are read by name: a fade reads them every frame.
|
|
39949
|
+
*
|
|
39950
|
+
* @param value - The stored `Alpha` value.
|
|
39951
|
+
* @returns True when the filter may be switched off.
|
|
39952
|
+
* @example
|
|
39953
|
+
* ```ts
|
|
39954
|
+
* drawsUnchanged({ enabled: true, order: 0, alpha: 1, resolution: 0 }); // true
|
|
39955
|
+
* ```
|
|
39956
|
+
*/
|
|
39957
|
+
function drawsUnchanged(value) {
|
|
39958
|
+
const { alpha, resolution } = value;
|
|
39959
|
+
return value.enabled && alpha === 1 && !(typeof resolution === "number" && resolution > 0);
|
|
39960
|
+
}
|
|
39961
|
+
/**
|
|
39962
|
+
* The `enabled` switch of an instance for the stored value of this sync: the value's own, at once, but an `Alpha` that
|
|
39963
|
+
* draws its view unchanged is switched off only at its `CALM_AFTER`-th sync in a row so while its filter is on, counted
|
|
39964
|
+
* in `calm`: keyframes that touch 1 keep the pass and switch nothing, a fade that ends at 1 lets it go. The filter
|
|
39965
|
+
* sync syncs the view again while `calm` counts.
|
|
39966
|
+
*
|
|
39967
|
+
* @param instance - The instance.
|
|
39968
|
+
* @param value - The stored filter value.
|
|
39969
|
+
* @returns True when Pixi is to draw the filter.
|
|
39970
|
+
*/
|
|
39971
|
+
function switchOf(instance, value) {
|
|
39972
|
+
if (instance.kind !== "alpha") return value.enabled;
|
|
39973
|
+
if (!drawsUnchanged(value)) {
|
|
39974
|
+
instance.calm = 0;
|
|
39975
|
+
return value.enabled;
|
|
39976
|
+
}
|
|
39977
|
+
if (!instance.filter.enabled) return false;
|
|
39978
|
+
instance.calm += 1;
|
|
39979
|
+
if (instance.calm < 8) return true;
|
|
39980
|
+
instance.calm = 0;
|
|
39981
|
+
return false;
|
|
39982
|
+
}
|
|
39983
|
+
/**
|
|
39013
39984
|
* Composes a colour matrix from the identity: brightness, saturation, contrast, hue, and the
|
|
39014
39985
|
* grayscale as a negative saturation. `ColorMatrixFilter` multiplies, so it starts from `reset()`.
|
|
39015
39986
|
*
|
|
@@ -39151,10 +40122,11 @@ function createCore(ectx, pixi, core, value, probeMap) {
|
|
|
39151
40122
|
kind: "alpha",
|
|
39152
40123
|
filter: new pixi.AlphaFilter({
|
|
39153
40124
|
alpha: numberOf$1(value, "alpha"),
|
|
39154
|
-
resolution:
|
|
40125
|
+
resolution: resolutionOf(numberOf$1(value, "resolution"))
|
|
39155
40126
|
}),
|
|
39156
40127
|
...appliedOf(value),
|
|
39157
|
-
passes: 1
|
|
40128
|
+
passes: 1,
|
|
40129
|
+
calm: 0
|
|
39158
40130
|
};
|
|
39159
40131
|
}
|
|
39160
40132
|
}
|
|
@@ -39176,7 +40148,7 @@ function createCore(ectx, pixi, core, value, probeMap) {
|
|
|
39176
40148
|
function createFilter(ectx, pixi, kind, value, probeMap) {
|
|
39177
40149
|
const body = kind.source === "wgsl" ? createOurs(pixi, kind.definition, value) : createCore(ectx, pixi, kind.core, value, probeMap);
|
|
39178
40150
|
if (body === void 0) return void 0;
|
|
39179
|
-
body.filter.enabled = value.enabled;
|
|
40151
|
+
body.filter.enabled = value.enabled && !(body.kind === "alpha" && drawsUnchanged(value));
|
|
39180
40152
|
if (body.kind === "wgsl") body.areaStale = body.definition.area !== void 0;
|
|
39181
40153
|
const instance = Object.assign(body, {
|
|
39182
40154
|
order: value.order,
|
|
@@ -39189,10 +40161,85 @@ function createFilter(ectx, pixi, kind, value, probeMap) {
|
|
|
39189
40161
|
entity: 0
|
|
39190
40162
|
});
|
|
39191
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);
|
|
39192
40165
|
guardApply(ectx, instance, kind);
|
|
39193
40166
|
return instance;
|
|
39194
40167
|
}
|
|
39195
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
|
+
/**
|
|
39196
40243
|
* The share of the resolution the last place of one input draws at.
|
|
39197
40244
|
*
|
|
39198
40245
|
* @param inputs - The chains of the instance.
|
|
@@ -39218,8 +40265,12 @@ function stagesOf(inputs) {
|
|
|
39218
40265
|
* @param inputs - The chains of the instance.
|
|
39219
40266
|
*/
|
|
39220
40267
|
function giveBack(pixi, inputs) {
|
|
39221
|
-
|
|
39222
|
-
|
|
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();
|
|
39223
40274
|
}
|
|
39224
40275
|
/**
|
|
39225
40276
|
* Draws the places of one input, each from the one before, the first from `from`, into a pooled
|
|
@@ -39229,7 +40280,7 @@ function giveBack(pixi, inputs) {
|
|
|
39229
40280
|
* @param inputs - The chains of the instance.
|
|
39230
40281
|
* @param index - The input.
|
|
39231
40282
|
* @param manager - Pixi's filter system, inside an active filter pass.
|
|
39232
|
-
* @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.
|
|
39233
40284
|
* @param resolution - The resolution of `from`.
|
|
39234
40285
|
* @param target - Where the last place draws, instead of a pooled texture.
|
|
39235
40286
|
* @returns The texture the last place drew into; `from` for an empty chain.
|
|
@@ -39271,20 +40322,7 @@ function bindInput(instance, index, last) {
|
|
|
39271
40322
|
const { resources } = instance.filter;
|
|
39272
40323
|
if (resources[input.texture] !== source) resources[input.texture] = source;
|
|
39273
40324
|
if (resources[input.sampler] !== source.style) resources[input.sampler] = source.style;
|
|
39274
|
-
|
|
39275
|
-
const clamp = uniforms[input.clamp.name];
|
|
39276
|
-
const halfX = .5 / source.pixelWidth;
|
|
39277
|
-
const halfY = .5 / source.pixelHeight;
|
|
39278
|
-
if (scale instanceof Float32Array) {
|
|
39279
|
-
writeAt(scale, 0, 1 / source.width, true);
|
|
39280
|
-
writeAt(scale, 1, 1 / source.height, true);
|
|
39281
|
-
}
|
|
39282
|
-
if (clamp instanceof Float32Array) {
|
|
39283
|
-
writeAt(clamp, 0, halfX, true);
|
|
39284
|
-
writeAt(clamp, 1, halfY, true);
|
|
39285
|
-
writeAt(clamp, 2, last.frame.width / source.width - halfX, true);
|
|
39286
|
-
writeAt(clamp, 3, last.frame.height / source.height - halfY, true);
|
|
39287
|
-
}
|
|
40325
|
+
writePlacement(uniforms, input.scale.name, input.clamp.name, last);
|
|
39288
40326
|
}
|
|
39289
40327
|
/**
|
|
39290
40328
|
* Points every input's texture and sampler back at Pixi's empty texture: a filter never holds a
|
|
@@ -39305,11 +40343,24 @@ function unbindInputs(pixi, instance) {
|
|
|
39305
40343
|
}
|
|
39306
40344
|
}
|
|
39307
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
|
+
/**
|
|
39308
40358
|
* The apply of a filter with inputs, as Pixi calls it: a resolution change runs the chains once
|
|
39309
|
-
* at the new value, then every place draws,
|
|
39310
|
-
*
|
|
39311
|
-
*
|
|
39312
|
-
*
|
|
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.
|
|
39313
40364
|
*
|
|
39314
40365
|
* @param ectx - Domain context of the effects plugin.
|
|
39315
40366
|
* @param pixi - The module the renderer loaded.
|
|
@@ -39322,16 +40373,23 @@ function unbindInputs(pixi, instance) {
|
|
|
39322
40373
|
*/
|
|
39323
40374
|
function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode) {
|
|
39324
40375
|
const { resolution } = input.source;
|
|
39325
|
-
|
|
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);
|
|
39326
40380
|
if (instance.broken) {
|
|
39327
40381
|
drawBroken(instance, manager, input, output, clearMode);
|
|
39328
40382
|
return;
|
|
39329
40383
|
}
|
|
39330
40384
|
try {
|
|
39331
40385
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
39332
|
-
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;
|
|
39333
40390
|
bindInput(instance, index, last);
|
|
39334
40391
|
}
|
|
40392
|
+
if (definition.backdrop) bindBack(instance, back);
|
|
39335
40393
|
manager.applyFilter(instance.filter, input, output, clearMode);
|
|
39336
40394
|
} finally {
|
|
39337
40395
|
giveBack(pixi, inputs);
|
|
@@ -39353,7 +40411,7 @@ function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clear
|
|
|
39353
40411
|
*/
|
|
39354
40412
|
function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
39355
40413
|
const { resolution } = picture.source;
|
|
39356
|
-
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);
|
|
39357
40415
|
if (instance.broken) return;
|
|
39358
40416
|
try {
|
|
39359
40417
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
@@ -39373,10 +40431,14 @@ function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
|
39373
40431
|
* @param instance - The instance, just built.
|
|
39374
40432
|
*/
|
|
39375
40433
|
function wireInputs(ectx, pixi, instance) {
|
|
39376
|
-
const
|
|
40434
|
+
const declared = instance.definition.inputs;
|
|
40435
|
+
const count = declared.length;
|
|
39377
40436
|
const inputs = {
|
|
39378
40437
|
stale: true,
|
|
39379
40438
|
resolution: 0,
|
|
40439
|
+
backResolution: 0,
|
|
40440
|
+
fromInput: declared.some((input) => input.from === "input"),
|
|
40441
|
+
fromBack: declared.some((input) => input.from === "back"),
|
|
39380
40442
|
version: 0,
|
|
39381
40443
|
context: { resolution: 0 },
|
|
39382
40444
|
places: Array.from({ length: count }, () => []),
|
|
@@ -39401,7 +40463,10 @@ function wireInputs(ectx, pixi, instance) {
|
|
|
39401
40463
|
};
|
|
39402
40464
|
instance.inputs = inputs;
|
|
39403
40465
|
instance.filter.apply = (manager, input, output, clearMode) => applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode);
|
|
39404
|
-
|
|
40466
|
+
const { resolution } = instance.definition;
|
|
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);
|
|
39405
40470
|
}
|
|
39406
40471
|
/**
|
|
39407
40472
|
* Reads one property of a value a chain returned, whatever its type.
|
|
@@ -39595,9 +40660,10 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39595
40660
|
return changed || rebuilt;
|
|
39596
40661
|
}
|
|
39597
40662
|
/**
|
|
39598
|
-
* 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.
|
|
39599
40665
|
* A chain that throws or returns a step it may not draw breaks the instance (reason `"chain"`).
|
|
39600
|
-
* 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
|
|
39601
40667
|
* become the main body plus every place.
|
|
39602
40668
|
*
|
|
39603
40669
|
* @param ectx - Domain context of the effects plugin.
|
|
@@ -39605,14 +40671,15 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39605
40671
|
* @param instance - The instance.
|
|
39606
40672
|
* @param inputs - Its chains.
|
|
39607
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.
|
|
39608
40675
|
*/
|
|
39609
|
-
function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
40676
|
+
function evaluate(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
39610
40677
|
const kind = ectx.state.kinds.get(instance.kindId);
|
|
39611
|
-
let changed = resolution !== inputs.resolution;
|
|
40678
|
+
let changed = resolution !== inputs.resolution || backResolution !== inputs.backResolution;
|
|
39612
40679
|
let passes = 1;
|
|
39613
|
-
inputs.context.resolution = resolution;
|
|
39614
40680
|
for (const [index, input] of instance.definition.inputs.entries()) {
|
|
39615
40681
|
let steps;
|
|
40682
|
+
inputs.context.resolution = input.from === "back" ? backResolution : resolution;
|
|
39616
40683
|
try {
|
|
39617
40684
|
steps = input.chain(instance.value, inputs.context);
|
|
39618
40685
|
} catch (error) {
|
|
@@ -39628,6 +40695,7 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39628
40695
|
passes += steps.length;
|
|
39629
40696
|
}
|
|
39630
40697
|
inputs.resolution = resolution;
|
|
40698
|
+
inputs.backResolution = backResolution;
|
|
39631
40699
|
inputs.stale = false;
|
|
39632
40700
|
instance.passes = passes;
|
|
39633
40701
|
if (changed) inputs.version += 1;
|
|
@@ -39642,9 +40710,10 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39642
40710
|
* @param instance - The instance.
|
|
39643
40711
|
* @param inputs - Its chains.
|
|
39644
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.
|
|
39645
40714
|
*/
|
|
39646
|
-
function evaluateInRender(ectx, pixi, instance, inputs, resolution) {
|
|
39647
|
-
evaluate(ectx, pixi, instance, inputs, resolution);
|
|
40715
|
+
function evaluateInRender(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
40716
|
+
evaluate(ectx, pixi, instance, inputs, resolution, backResolution);
|
|
39648
40717
|
ectx.state.filterSync.all = true;
|
|
39649
40718
|
}
|
|
39650
40719
|
/**
|
|
@@ -39666,12 +40735,13 @@ function refreshInputs(ectx, pixi, instance, off) {
|
|
|
39666
40735
|
inputs.retired.length = 0;
|
|
39667
40736
|
}
|
|
39668
40737
|
if (instance.broken || !instance.filter.enabled || off || !inputs.stale) return;
|
|
39669
|
-
evaluate(ectx, pixi, instance, inputs, inputs.resolution);
|
|
40738
|
+
evaluate(ectx, pixi, instance, inputs, inputs.resolution, inputs.backResolution);
|
|
39670
40739
|
}
|
|
39671
40740
|
/**
|
|
39672
40741
|
* The body a stand-in of a dev build draws: the fallback with the striped mark over it, or the
|
|
39673
40742
|
* mark alone. The pair draws the fallback into one pooled texture of the input's frame and
|
|
39674
|
-
* 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.
|
|
39675
40745
|
*
|
|
39676
40746
|
* @param pixi - The module the renderer loaded.
|
|
39677
40747
|
* @param fallback - The checked fallback, or `undefined`.
|
|
@@ -39696,6 +40766,7 @@ function markedBody(pixi, fallback, value) {
|
|
|
39696
40766
|
request.height = input.frame.height;
|
|
39697
40767
|
request.resolution = input.source.resolution;
|
|
39698
40768
|
const pooled = pixi.TexturePool.getOptimalTexture(request);
|
|
40769
|
+
if (body.definition.backdrop) bindBack(body, backOf(pixi, manager));
|
|
39699
40770
|
try {
|
|
39700
40771
|
manager.applyFilter(body.filter, input, pooled, true);
|
|
39701
40772
|
manager.applyFilter(mark.filter, pooled, output, clearMode);
|
|
@@ -39725,7 +40796,10 @@ function createStandIn(ectx, pixi, kind, value) {
|
|
|
39725
40796
|
const usable = status === "ok" ? fallback : void 0;
|
|
39726
40797
|
let body;
|
|
39727
40798
|
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ === true) body = markedBody(pixi, usable, value);
|
|
39728
|
-
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
|
+
}
|
|
39729
40803
|
if (body === void 0) return void 0;
|
|
39730
40804
|
body.filter.enabled = value.enabled;
|
|
39731
40805
|
body.areaStale = body.definition.area !== void 0;
|
|
@@ -39876,6 +40950,7 @@ function sameField(a, b) {
|
|
|
39876
40950
|
*/
|
|
39877
40951
|
function coreDiffers(instance, value) {
|
|
39878
40952
|
const { fields, applied } = instance;
|
|
40953
|
+
if (instance.kind === "alpha") return !sameField(applied.alpha, value.alpha) || !sameField(applied.resolution, value.resolution);
|
|
39879
40954
|
for (const field of fields) if (instance.kind === "displacement" && field === "map") {
|
|
39880
40955
|
if (mapOf(value) !== instance.map) return true;
|
|
39881
40956
|
} else if (!sameField(applied[field], value[field])) return true;
|
|
@@ -39905,6 +40980,12 @@ function mapOf(value) {
|
|
|
39905
40980
|
function writeCore(ectx, instance, value) {
|
|
39906
40981
|
setCore(ectx, instance, value);
|
|
39907
40982
|
const { fields, applied } = instance;
|
|
40983
|
+
if (instance.kind === "alpha") {
|
|
40984
|
+
const { alpha, resolution } = value;
|
|
40985
|
+
if (alpha !== void 0) applied.alpha = alpha;
|
|
40986
|
+
if (resolution !== void 0 && resolution !== applied.resolution) applied.resolution = resolution;
|
|
40987
|
+
return;
|
|
40988
|
+
}
|
|
39908
40989
|
for (const field of fields) {
|
|
39909
40990
|
const next = value[field];
|
|
39910
40991
|
if (next !== void 0) applied[field] = next;
|
|
@@ -39946,7 +41027,11 @@ function setCore(ectx, instance, value) {
|
|
|
39946
41027
|
instance.map = map;
|
|
39947
41028
|
return;
|
|
39948
41029
|
}
|
|
39949
|
-
default:
|
|
41030
|
+
default: {
|
|
41031
|
+
const { alpha, resolution } = value;
|
|
41032
|
+
instance.filter.alpha = typeof alpha === "number" ? alpha : 0;
|
|
41033
|
+
if (resolution !== instance.applied.resolution) instance.filter.resolution = resolutionOf(typeof resolution === "number" ? resolution : 0);
|
|
41034
|
+
}
|
|
39950
41035
|
}
|
|
39951
41036
|
}
|
|
39952
41037
|
/**
|
|
@@ -39972,8 +41057,9 @@ function switchOffBroken(instance, before) {
|
|
|
39972
41057
|
}
|
|
39973
41058
|
/**
|
|
39974
41059
|
* Brings one instance up to the stored value for a frame, comparing before anything is written.
|
|
39975
|
-
* A broken instance stays off whatever its component says
|
|
39976
|
-
*
|
|
41060
|
+
* A broken instance stays off whatever its component says, and an `Alpha` at 1 drawn at the
|
|
41061
|
+
* canvas's resolution is switched off at its `CALM_AFTER`-th sync there. Switching on or off is a
|
|
41062
|
+
* visible change; so is a new value of a filter that is on. A filter that is off and stays off still gets
|
|
39977
41063
|
* its new values written, so it is current when it comes back on, but asks for nothing: an off
|
|
39978
41064
|
* filter draws no pixel. Ours compare on every sync of their view (a view syncs when something of it changed); a core
|
|
39979
41065
|
* kind only when its component changed.
|
|
@@ -39990,13 +41076,14 @@ function writeFilter(ectx, instance, value, changed, before) {
|
|
|
39990
41076
|
switchOffBroken(instance, before);
|
|
39991
41077
|
return false;
|
|
39992
41078
|
}
|
|
39993
|
-
const
|
|
41079
|
+
const on = switchOf(instance, value);
|
|
41080
|
+
const toggled = instance.filter.enabled !== on;
|
|
39994
41081
|
const differs = instance.kind === "wgsl" ? oursDiffer(instance, instance.definition, value) : changed && coreDiffers(instance, value);
|
|
39995
41082
|
if (!toggled && !differs) return false;
|
|
39996
41083
|
staleArea(instance);
|
|
39997
|
-
const visible = toggled ||
|
|
41084
|
+
const visible = toggled || on;
|
|
39998
41085
|
if (visible) before();
|
|
39999
|
-
if (toggled) instance.filter.enabled =
|
|
41086
|
+
if (toggled) instance.filter.enabled = on;
|
|
40000
41087
|
if (differs) {
|
|
40001
41088
|
if (instance.kind === "wgsl") {
|
|
40002
41089
|
writeOurs(instance, instance.definition, value);
|
|
@@ -40741,8 +41828,10 @@ function judgeArea(ectx, instance) {
|
|
|
40741
41828
|
}
|
|
40742
41829
|
/**
|
|
40743
41830
|
* The union of the areas of a view's enabled filters, into the scratch box: none while no filter is enabled, and none
|
|
40744
|
-
* when an enabled filter has no area (every built-in kind, or a definition that answered `undefined`) — it draws
|
|
40745
|
-
* the view's bounds.
|
|
41831
|
+
* when an enabled filter has no area (every other built-in kind, or a definition that answered `undefined`) — it draws
|
|
41832
|
+
* over the view's bounds. An `Alpha` does not count: it only scales the pixels the others draw, and makes none where
|
|
41833
|
+
* they draw nothing; alone it leaves the view without an area. A disabled filter, and a kind a debug switch turned off
|
|
41834
|
+
* (it is not placed), do not count either.
|
|
40746
41835
|
*
|
|
40747
41836
|
* @param view - The view's record, its instances placed.
|
|
40748
41837
|
*/
|
|
@@ -40753,7 +41842,7 @@ function unionOf(view) {
|
|
|
40753
41842
|
let bottom = Number.NEGATIVE_INFINITY;
|
|
40754
41843
|
union.some = false;
|
|
40755
41844
|
for (const instance of view.placed) {
|
|
40756
|
-
if (!instance.filter.enabled) continue;
|
|
41845
|
+
if (!instance.filter.enabled || instance.kind === "alpha") continue;
|
|
40757
41846
|
if (instance.kind !== "wgsl" || !instance.area.some) {
|
|
40758
41847
|
union.some = false;
|
|
40759
41848
|
return;
|
|
@@ -40765,6 +41854,7 @@ function unionOf(view) {
|
|
|
40765
41854
|
right = Math.max(right, area.x + area.width);
|
|
40766
41855
|
bottom = Math.max(bottom, area.y + area.height);
|
|
40767
41856
|
}
|
|
41857
|
+
if (!union.some) return;
|
|
40768
41858
|
union.x = left;
|
|
40769
41859
|
union.y = top;
|
|
40770
41860
|
union.width = right - left;
|
|
@@ -40895,14 +41985,27 @@ function cacheOfInstance(instance) {
|
|
|
40895
41985
|
return instance.kind === "wgsl" ? instance.definition.cache : CORE_CACHE[instance.kind];
|
|
40896
41986
|
}
|
|
40897
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
|
+
/**
|
|
40898
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
|
|
40899
|
-
* 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).
|
|
40900
42003
|
*
|
|
40901
42004
|
* @param instance - The instance.
|
|
40902
42005
|
* @returns True, or `undefined` for every other filter (the slot leaves the key out).
|
|
40903
42006
|
*/
|
|
40904
42007
|
function blursOf(instance) {
|
|
40905
|
-
return instance.kind === "blur" || instance.kind === "wgsl" && instance.definition
|
|
42008
|
+
return instance.kind === "blur" || instance.kind === "wgsl" && readsOwnInput(instance.definition) ? true : void 0;
|
|
40906
42009
|
}
|
|
40907
42010
|
/**
|
|
40908
42011
|
* Tells whether a debug switch turned a kind off (`effects.diagnostics.disable`). The guard is
|
|
@@ -40980,7 +42083,7 @@ function syncKind(ectx, pixi, entity, view, kind, changed, off) {
|
|
|
40980
42083
|
writeFilter(ectx, instance, value, changed, off ? NO_SIGNAL : view.signal);
|
|
40981
42084
|
instance.order = value.order;
|
|
40982
42085
|
refreshInputs(ectx, pixi, instance, off);
|
|
40983
|
-
if (instance.kind === "wgsl" && (instance.inputs?.retired.length ?? 0) > 0) markDirty$1(ectx, view);
|
|
42086
|
+
if (instance.kind === "wgsl" && (instance.inputs?.retired.length ?? 0) > 0 || instance.kind === "alpha" && instance.calm > 0) markDirty$1(ectx, view);
|
|
40984
42087
|
return installReplacement(view, kind, instance);
|
|
40985
42088
|
}
|
|
40986
42089
|
instance = firstInstance(ectx, pixi, kind, value);
|
|
@@ -41247,7 +42350,8 @@ function dropProbe(ectx, probe) {
|
|
|
41247
42350
|
}
|
|
41248
42351
|
/**
|
|
41249
42352
|
* A probe of one Pixi-core kind at the frozen defaults of its component; a `Displacement` draws over Pixi's white
|
|
41250
|
-
* texture, since its program does not depend on the map.
|
|
42353
|
+
* texture, since its program does not depend on the map. A probe is switched on whatever its defaults: an `Alpha` at 1
|
|
42354
|
+
* would be off, and Pixi would warm nothing. Its `apply` is guarded like any instance's.
|
|
41251
42355
|
*
|
|
41252
42356
|
* @param ectx - Domain context of the effects plugin.
|
|
41253
42357
|
* @param pixi - The module the renderer loaded.
|
|
@@ -41258,6 +42362,7 @@ function coreProbe(ectx, pixi, id) {
|
|
|
41258
42362
|
const kind = ectx.state.kinds.get(id);
|
|
41259
42363
|
const instance = kind === void 0 ? void 0 : createFilter(ectx, pixi, kind, kind.component.defaults, pixi.Texture.WHITE);
|
|
41260
42364
|
if (instance === void 0) return void 0;
|
|
42365
|
+
instance.filter.enabled = true;
|
|
41261
42366
|
return {
|
|
41262
42367
|
filter: instance.filter,
|
|
41263
42368
|
destroy: () => destroyFilter(instance)
|
|
@@ -59043,6 +60148,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
59043
60148
|
* | renderer | preference | "webgpu", WebGL is Pixi's fallback |
|
|
59044
60149
|
* | renderer | aspect | { min: 4/3, max: 21/9 }; shallow merge: a game that sets `aspect` gives both fields |
|
|
59045
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 |
|
|
59046
60152
|
* | renderer | unsupportedMessage | "This device cannot run the game." |
|
|
59047
60153
|
* | renderer | loadPixi | () => import("pixi.js") |
|
|
59048
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 |
|