melonjs 20.9.0 → 20.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/application/settings.d.ts +12 -8
- package/build/application/settings.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1981 -237
- package/build/index.js.map +4 -4
- package/build/loader/loader.d.ts +62 -17
- package/build/loader/loader.d.ts.map +1 -1
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +6 -0
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/state/state.d.ts.map +1 -1
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/squish.d.ts +74 -0
- package/build/video/effects/squish.d.ts.map +1 -0
- package/build/video/renderer.d.ts +16 -3
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/rendertarget/render_target_pool.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts +5 -0
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts +21 -0
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/buffer/deletable.d.ts +15 -0
- package/build/video/webgl/buffer/deletable.d.ts.map +1 -0
- package/build/video/webgl/buffer/instance_buffer.d.ts.map +1 -1
- package/build/video/webgl/buffer/retained_geometry.d.ts.map +1 -1
- package/build/video/webgl/buffer/uniformblock.d.ts.map +1 -1
- package/build/video/webgl/buffer/vertexstate.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/texture/compressed.d.ts.map +1 -1
- package/build/video/webgpu/texture/store.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs-effects-and-shaders/SKILL.md +50 -6
- package/skills/melonjs-loading-assets/SKILL.md +122 -13
- package/skills/melonjs-performance/SKILL.md +11 -3
- package/skills/melonjs-scenes-and-state/SKILL.md +7 -0
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* melonJS Game Engine - 20.
|
|
2
|
+
* melonJS Game Engine - 20.10.1
|
|
3
3
|
* http://www.melonjs.org
|
|
4
4
|
* melonjs is licensed under the MIT License.
|
|
5
5
|
* http://www.opensource.org/licenses/mit-license
|
|
@@ -10130,6 +10130,9 @@ var Tween = class {
|
|
|
10130
10130
|
* @internal
|
|
10131
10131
|
*/
|
|
10132
10132
|
_onTick(timestamp) {
|
|
10133
|
+
if (!this._isRunning) {
|
|
10134
|
+
return;
|
|
10135
|
+
}
|
|
10133
10136
|
if (!this._isPaused || this.updateWhenPaused) {
|
|
10134
10137
|
const dt = timestamp - this._lastTick;
|
|
10135
10138
|
this._lastTick = timestamp;
|
|
@@ -13370,10 +13373,17 @@ var Renderable = class _Renderable extends Rect {
|
|
|
13370
13373
|
this.postEffects.length = 0;
|
|
13371
13374
|
} else {
|
|
13372
13375
|
this.postEffects = [value];
|
|
13376
|
+
value._renderer?._queueWarm?.(this);
|
|
13373
13377
|
}
|
|
13374
13378
|
}
|
|
13375
13379
|
/**
|
|
13376
13380
|
* Add a post-processing shader effect to this renderable.
|
|
13381
|
+
*
|
|
13382
|
+
* With the `prewarm` application setting on (the default), what the
|
|
13383
|
+
* effect will need to draw here is compiled from this call, off the
|
|
13384
|
+
* frame, rather than on the frame the renderable first draws with it.
|
|
13385
|
+
* That takes a moment to finish, so attach effects when the renderable
|
|
13386
|
+
* is built (a pool, a stage starting) rather than when it is shown.
|
|
13377
13387
|
* @param {GLShader|ShaderEffect} effect - the effect to add
|
|
13378
13388
|
* @returns {GLShader|ShaderEffect} the added effect
|
|
13379
13389
|
* @example
|
|
@@ -13381,6 +13391,7 @@ var Renderable = class _Renderable extends Rect {
|
|
|
13381
13391
|
*/
|
|
13382
13392
|
addPostEffect(effect) {
|
|
13383
13393
|
this.postEffects.push(effect);
|
|
13394
|
+
effect?._renderer?._queueWarm?.(this);
|
|
13384
13395
|
return effect;
|
|
13385
13396
|
}
|
|
13386
13397
|
/**
|
|
@@ -15409,6 +15420,7 @@ var ShaderEffect = class _ShaderEffect {
|
|
|
15409
15420
|
this._renderer = renderer;
|
|
15410
15421
|
this._fragmentBody = body;
|
|
15411
15422
|
this._precision = precision;
|
|
15423
|
+
this._vertexScale = { x: 1, y: 1 };
|
|
15412
15424
|
const bodies = typeof body === "string" ? { glsl: body } : body ?? {};
|
|
15413
15425
|
const language2 = renderer.shaderLanguage;
|
|
15414
15426
|
const source = language2 !== null ? bodies[language2] : void 0;
|
|
@@ -16928,6 +16940,29 @@ var Camera2d = class extends Renderable {
|
|
|
16928
16940
|
fx.draw(renderer, this.width, this.height);
|
|
16929
16941
|
}
|
|
16930
16942
|
}
|
|
16943
|
+
/**
|
|
16944
|
+
* The built-in {@link Camera2d#colorMatrix} as the effect that applies it,
|
|
16945
|
+
* made on first need and kept, and set to the current matrix.
|
|
16946
|
+
*
|
|
16947
|
+
* `draw` appends it last in the camera's chain for the frame. The
|
|
16948
|
+
* renderer's warm-up calls this too, once a stage has been built, so the
|
|
16949
|
+
* effect exists, and is warmed where `draw` will run it, before the frame
|
|
16950
|
+
* that first needs it rather than inside that frame.
|
|
16951
|
+
* @param renderer - the renderer the camera draws with
|
|
16952
|
+
* @returns the effect, or null while the matrix is identity
|
|
16953
|
+
* @ignore
|
|
16954
|
+
* @internal
|
|
16955
|
+
*/
|
|
16956
|
+
_syncColorMatrixEffect(renderer) {
|
|
16957
|
+
if (this.colorMatrix.isIdentity()) {
|
|
16958
|
+
return null;
|
|
16959
|
+
}
|
|
16960
|
+
if (!this._colorMatrixEffect) {
|
|
16961
|
+
this._colorMatrixEffect = new ColorMatrixEffect(renderer);
|
|
16962
|
+
}
|
|
16963
|
+
this._colorMatrixEffect.reset().multiply(this.colorMatrix);
|
|
16964
|
+
return this._colorMatrixEffect;
|
|
16965
|
+
}
|
|
16931
16966
|
/**
|
|
16932
16967
|
* draw all objects visible in this viewport
|
|
16933
16968
|
* @ignore
|
|
@@ -16943,12 +16978,9 @@ var Camera2d = class extends Renderable {
|
|
|
16943
16978
|
const containerOffsetY = isNonDefault ? container.pos.y : 0;
|
|
16944
16979
|
const translateX = this.pos.x + this.offset.x + containerOffsetX;
|
|
16945
16980
|
const translateY = this.pos.y + this.offset.y + containerOffsetY;
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
}
|
|
16950
|
-
this._colorMatrixEffect.reset().multiply(this.colorMatrix);
|
|
16951
|
-
this.postEffects.push(this._colorMatrixEffect);
|
|
16981
|
+
const colorMatrixEffect = this._syncColorMatrixEffect(renderer);
|
|
16982
|
+
if (colorMatrixEffect !== null) {
|
|
16983
|
+
this.postEffects.push(colorMatrixEffect);
|
|
16952
16984
|
}
|
|
16953
16985
|
const usePostEffect = r.beginPostEffect(this);
|
|
16954
16986
|
this._applyContainerViewTransform(container, translateX, translateY);
|
|
@@ -17562,6 +17594,7 @@ var FrameAnimation = class {
|
|
|
17562
17594
|
|
|
17563
17595
|
// src/renderable/sprite.js
|
|
17564
17596
|
var FLICKER_INTERVAL_MS = 33;
|
|
17597
|
+
var VIDEO_GESTURES = ["touchstart", "touchend", "click", "keydown"];
|
|
17565
17598
|
var Sprite = class extends Renderable {
|
|
17566
17599
|
/**
|
|
17567
17600
|
* @param {number} x - the x coordinates of the sprite object
|
|
@@ -17668,8 +17701,12 @@ var Sprite = class extends Renderable {
|
|
|
17668
17701
|
}
|
|
17669
17702
|
this.removeStatePauseListener = on(STATE_PAUSE, () => {
|
|
17670
17703
|
this.image.pause();
|
|
17704
|
+
this._cancelVideoPlay();
|
|
17671
17705
|
});
|
|
17672
17706
|
this._lastVideoFrameVersion = -1;
|
|
17707
|
+
this._videoPlayPending = false;
|
|
17708
|
+
this._videoPlayRequest = 0;
|
|
17709
|
+
this._videoGestureListener = void 0;
|
|
17673
17710
|
if (typeof this.image.requestVideoFrameCallback === "function") {
|
|
17674
17711
|
this.image.version ??= 0;
|
|
17675
17712
|
const tick2 = () => {
|
|
@@ -18144,8 +18181,8 @@ var Sprite = class extends Renderable {
|
|
|
18144
18181
|
if (this.isVideo) {
|
|
18145
18182
|
if (this.animationpause) {
|
|
18146
18183
|
this.image.pause();
|
|
18147
|
-
} else if (this.image.paused) {
|
|
18148
|
-
this.
|
|
18184
|
+
} else if (this.image.paused && this._videoPlayPending !== true) {
|
|
18185
|
+
this._playVideo();
|
|
18149
18186
|
}
|
|
18150
18187
|
if (this.image.version === void 0) {
|
|
18151
18188
|
if (!this.image.paused) {
|
|
@@ -18241,6 +18278,86 @@ var Sprite = class extends Renderable {
|
|
|
18241
18278
|
// dw,dh
|
|
18242
18279
|
);
|
|
18243
18280
|
}
|
|
18281
|
+
/**
|
|
18282
|
+
* Start the video and handle the browser's answer. Before the user has
|
|
18283
|
+
* interacted with the page, browsers refuse playback: calling `play()`
|
|
18284
|
+
* again on every frame only logged an uncaught error per frame, so a
|
|
18285
|
+
* refusal now waits for the next gesture instead. Any other failure
|
|
18286
|
+
* (e.g. a `pause()` interrupting the request) retries on the next frame.
|
|
18287
|
+
* @ignore
|
|
18288
|
+
* @internal
|
|
18289
|
+
*/
|
|
18290
|
+
_playVideo() {
|
|
18291
|
+
const request = ++this._videoPlayRequest;
|
|
18292
|
+
this._videoPlayPending = true;
|
|
18293
|
+
Promise.resolve(this.image.play()).then(
|
|
18294
|
+
() => {
|
|
18295
|
+
this._videoPlayPending = false;
|
|
18296
|
+
},
|
|
18297
|
+
(error) => {
|
|
18298
|
+
if (request !== this._videoPlayRequest) {
|
|
18299
|
+
return;
|
|
18300
|
+
}
|
|
18301
|
+
if (error?.name === "NotAllowedError") {
|
|
18302
|
+
this._awaitVideoGesture();
|
|
18303
|
+
} else {
|
|
18304
|
+
this._videoPlayPending = false;
|
|
18305
|
+
}
|
|
18306
|
+
}
|
|
18307
|
+
);
|
|
18308
|
+
}
|
|
18309
|
+
/**
|
|
18310
|
+
* Wait for the next user gesture and start the video from inside its
|
|
18311
|
+
* handler, the moment a browser that refused playback allows it.
|
|
18312
|
+
* @ignore
|
|
18313
|
+
* @internal
|
|
18314
|
+
*/
|
|
18315
|
+
_awaitVideoGesture() {
|
|
18316
|
+
if (this._videoGestureListener !== void 0) {
|
|
18317
|
+
return;
|
|
18318
|
+
}
|
|
18319
|
+
this._videoGestureListener = () => {
|
|
18320
|
+
this._removeVideoGestureListener();
|
|
18321
|
+
this._videoPlayPending = false;
|
|
18322
|
+
if (this.animationpause !== true && this.image.paused) {
|
|
18323
|
+
this._playVideo();
|
|
18324
|
+
}
|
|
18325
|
+
};
|
|
18326
|
+
for (const type of VIDEO_GESTURES) {
|
|
18327
|
+
globalThis.document.addEventListener(
|
|
18328
|
+
type,
|
|
18329
|
+
this._videoGestureListener,
|
|
18330
|
+
true
|
|
18331
|
+
);
|
|
18332
|
+
}
|
|
18333
|
+
}
|
|
18334
|
+
/**
|
|
18335
|
+
* Drop a pending `play()` request and stop waiting for a gesture.
|
|
18336
|
+
* @ignore
|
|
18337
|
+
* @internal
|
|
18338
|
+
*/
|
|
18339
|
+
_cancelVideoPlay() {
|
|
18340
|
+
this._videoPlayRequest++;
|
|
18341
|
+
this._videoPlayPending = false;
|
|
18342
|
+
this._removeVideoGestureListener();
|
|
18343
|
+
}
|
|
18344
|
+
/**
|
|
18345
|
+
* @ignore
|
|
18346
|
+
* @internal
|
|
18347
|
+
*/
|
|
18348
|
+
_removeVideoGestureListener() {
|
|
18349
|
+
if (this._videoGestureListener === void 0) {
|
|
18350
|
+
return;
|
|
18351
|
+
}
|
|
18352
|
+
for (const type of VIDEO_GESTURES) {
|
|
18353
|
+
globalThis.document.removeEventListener(
|
|
18354
|
+
type,
|
|
18355
|
+
this._videoGestureListener,
|
|
18356
|
+
true
|
|
18357
|
+
);
|
|
18358
|
+
}
|
|
18359
|
+
this._videoGestureListener = void 0;
|
|
18360
|
+
}
|
|
18244
18361
|
/**
|
|
18245
18362
|
* Destroy function<br>
|
|
18246
18363
|
* @ignore
|
|
@@ -18252,6 +18369,7 @@ var Sprite = class extends Renderable {
|
|
|
18252
18369
|
this.offset = void 0;
|
|
18253
18370
|
if (this.isVideo) {
|
|
18254
18371
|
this.removeStatePauseListener();
|
|
18372
|
+
this._cancelVideoPlay();
|
|
18255
18373
|
if (typeof this._videoFrameHandle !== "undefined") {
|
|
18256
18374
|
this.image.cancelVideoFrameCallback(this._videoFrameHandle);
|
|
18257
18375
|
delete this.image.version;
|
|
@@ -21693,6 +21811,85 @@ var Path2D = class {
|
|
|
21693
21811
|
};
|
|
21694
21812
|
var path2d_default = Path2D;
|
|
21695
21813
|
|
|
21814
|
+
// src/video/blendmodes.js
|
|
21815
|
+
var ADVANCED_BLEND_MODES = [
|
|
21816
|
+
"difference",
|
|
21817
|
+
"overlay",
|
|
21818
|
+
"hard-light",
|
|
21819
|
+
"color-dodge",
|
|
21820
|
+
"color-burn",
|
|
21821
|
+
"soft-light",
|
|
21822
|
+
// `darken` and `lighten` joined the shader path in 20.2: fixed-function
|
|
21823
|
+
// MIN/MAX compute `min(src, dst)` and nothing else, so there is nowhere to
|
|
21824
|
+
// put the `(1 - srcAlpha) * dst` term source-over contributes after the
|
|
21825
|
+
// blend. A translucent source lost the backdrop's share entirely — a white
|
|
21826
|
+
// `lighten` glow over a light backdrop rendered invisible. APPENDED, never
|
|
21827
|
+
// inserted: the index is the shader's branch value.
|
|
21828
|
+
"darken",
|
|
21829
|
+
"lighten"
|
|
21830
|
+
];
|
|
21831
|
+
function isAdvancedBlendMode(mode) {
|
|
21832
|
+
return ADVANCED_BLEND_MODES.indexOf(mode) !== -1;
|
|
21833
|
+
}
|
|
21834
|
+
function advancedBlendModeIndex(mode) {
|
|
21835
|
+
return ADVANCED_BLEND_MODES.indexOf(mode);
|
|
21836
|
+
}
|
|
21837
|
+
function normalizeBlendMode(mode) {
|
|
21838
|
+
switch (mode) {
|
|
21839
|
+
case "none":
|
|
21840
|
+
case "multiply":
|
|
21841
|
+
case "screen":
|
|
21842
|
+
case "exclusion":
|
|
21843
|
+
case "normal":
|
|
21844
|
+
return mode;
|
|
21845
|
+
case "additive":
|
|
21846
|
+
case "add":
|
|
21847
|
+
case "lighter":
|
|
21848
|
+
return "additive";
|
|
21849
|
+
default:
|
|
21850
|
+
return "normal";
|
|
21851
|
+
}
|
|
21852
|
+
}
|
|
21853
|
+
function isSupportedBlendMode(mode) {
|
|
21854
|
+
return isAdvancedBlendMode(mode) || mode === "normal" || normalizeBlendMode(mode) !== "normal";
|
|
21855
|
+
}
|
|
21856
|
+
function reportedBlendMode(mode) {
|
|
21857
|
+
return isSupportedBlendMode(mode) ? mode : "normal";
|
|
21858
|
+
}
|
|
21859
|
+
function blendStateFor(mode, premultipliedAlpha) {
|
|
21860
|
+
const src = premultipliedAlpha ? "one" : "src-alpha";
|
|
21861
|
+
switch (mode) {
|
|
21862
|
+
case "none":
|
|
21863
|
+
return void 0;
|
|
21864
|
+
case "additive":
|
|
21865
|
+
return { operation: "add", srcFactor: src, dstFactor: "one" };
|
|
21866
|
+
case "multiply":
|
|
21867
|
+
return {
|
|
21868
|
+
operation: "add",
|
|
21869
|
+
srcFactor: "dst",
|
|
21870
|
+
dstFactor: "one-minus-src-alpha"
|
|
21871
|
+
};
|
|
21872
|
+
case "exclusion":
|
|
21873
|
+
return {
|
|
21874
|
+
operation: "add",
|
|
21875
|
+
srcFactor: "one-minus-dst",
|
|
21876
|
+
dstFactor: "one-minus-src"
|
|
21877
|
+
};
|
|
21878
|
+
case "screen":
|
|
21879
|
+
return {
|
|
21880
|
+
operation: "add",
|
|
21881
|
+
srcFactor: "one",
|
|
21882
|
+
dstFactor: "one-minus-src"
|
|
21883
|
+
};
|
|
21884
|
+
default:
|
|
21885
|
+
return {
|
|
21886
|
+
operation: "add",
|
|
21887
|
+
srcFactor: src,
|
|
21888
|
+
dstFactor: "one-minus-src-alpha"
|
|
21889
|
+
};
|
|
21890
|
+
}
|
|
21891
|
+
}
|
|
21892
|
+
|
|
21696
21893
|
// src/video/effects/tonemap.js
|
|
21697
21894
|
var CURVES = {
|
|
21698
21895
|
/**
|
|
@@ -22171,12 +22368,25 @@ var Renderer = class _Renderer {
|
|
|
22171
22368
|
*
|
|
22172
22369
|
* Two consequences worth knowing:
|
|
22173
22370
|
*
|
|
22174
|
-
* - **A game that never calls `loader.preload()`
|
|
22175
|
-
*
|
|
22176
|
-
* and still want the warm-up.
|
|
22371
|
+
* - **A game that never calls `loader.preload()` never gets this part.**
|
|
22372
|
+
* Call it yourself if you load assets some other way and still want it.
|
|
22177
22373
|
* - It is deliberately NOT called from `Application.init()`. That runs
|
|
22178
22374
|
* before anything is on screen, so warming there would only lengthen a
|
|
22179
22375
|
* blank page instead of hiding behind a progress bar.
|
|
22376
|
+
*
|
|
22377
|
+
* This is the half that needs no renderable. The other half cannot run
|
|
22378
|
+
* here, because what it compiles depends on the renderable drawing: under
|
|
22379
|
+
* WebGPU a pipeline is keyed by blend mode, culling, transparency and the
|
|
22380
|
+
* format of the target as well as by shader. So, behind the same
|
|
22381
|
+
* `prewarm` setting, the renderer also warms a renderable when it gains
|
|
22382
|
+
* a post effect ({@link Renderable#addPostEffect}) and when it is added
|
|
22383
|
+
* to the world ({@link Container#addChild}): its effect pipelines, its
|
|
22384
|
+
* own draw, the render targets its effect chain composites through, and
|
|
22385
|
+
* the advanced blend machinery if its blend mode needs it. That work is
|
|
22386
|
+
* started after the caller's code has run and finishes off the frame, so
|
|
22387
|
+
* it pays off when a renderable is set up at least a frame before it is
|
|
22388
|
+
* first drawn: build pools and attach their effects when a stage starts,
|
|
22389
|
+
* not when the object is first shown.
|
|
22180
22390
|
* @returns {Promise<void>} settles when the built-in programs are ready
|
|
22181
22391
|
* @example
|
|
22182
22392
|
* // the usual case: nothing to write. `preload()` warms on the way through
|
|
@@ -23096,6 +23306,290 @@ var Renderer = class _Renderer {
|
|
|
23096
23306
|
}
|
|
23097
23307
|
return effects;
|
|
23098
23308
|
}
|
|
23309
|
+
/**
|
|
23310
|
+
* How many effects a renderable's chain has to have render targets for:
|
|
23311
|
+
* every one attached, not just the ones enabled right now.
|
|
23312
|
+
*
|
|
23313
|
+
* Sizing the pool by the enabled count meant the ping-pong target was
|
|
23314
|
+
* only created on the frame a second effect was switched on, which is a
|
|
23315
|
+
* frame in the middle of play by construction (a hit flash, a power-up),
|
|
23316
|
+
* and creating a target there is a stall in that frame. A disabled effect
|
|
23317
|
+
* is one the game intends to turn on, so its target is made up front.
|
|
23318
|
+
* @param {Renderable} renderable - the renderable whose chain this is
|
|
23319
|
+
* @param {ShaderEffect[]} effects - its enabled chain, from `_effectChainFor`
|
|
23320
|
+
* @returns {number} the number of chain slots to provision
|
|
23321
|
+
* @ignore
|
|
23322
|
+
* @internal
|
|
23323
|
+
*/
|
|
23324
|
+
_postEffectCapacity(renderable, effects) {
|
|
23325
|
+
return Math.max(effects.length, renderable.postEffects.length);
|
|
23326
|
+
}
|
|
23327
|
+
/**
|
|
23328
|
+
* Warm, ahead of its first draw, what a renderable will need: the
|
|
23329
|
+
* pipelines, programs and render targets that would otherwise be built
|
|
23330
|
+
* on the frame it first shows up.
|
|
23331
|
+
*
|
|
23332
|
+
* Called when a renderable gains a post effect
|
|
23333
|
+
* ({@link Renderable#addPostEffect}) and when it enters the world
|
|
23334
|
+
* ({@link Container#addChild}), the two moments its draw becomes
|
|
23335
|
+
* knowable. Deferred to a microtask so it runs once the caller's own code
|
|
23336
|
+
* has finished, which is when the rest of the picture exists: a
|
|
23337
|
+
* renderable usually gets its effect before it is added to the world,
|
|
23338
|
+
* sized, or given its blend mode, and those all decide which pipeline its
|
|
23339
|
+
* draw will ask for. Several requests in one task collapse into one
|
|
23340
|
+
* warm-up per renderable.
|
|
23341
|
+
*
|
|
23342
|
+
* Honours the `prewarm` setting, the same switch as the loading-screen
|
|
23343
|
+
* warm-up.
|
|
23344
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23345
|
+
* @ignore
|
|
23346
|
+
* @internal
|
|
23347
|
+
*/
|
|
23348
|
+
_queueWarm(renderable) {
|
|
23349
|
+
if (this.settings?.prewarm !== true || this.shaderLanguage == null) {
|
|
23350
|
+
return;
|
|
23351
|
+
}
|
|
23352
|
+
if (this._warmHosts === void 0) {
|
|
23353
|
+
this._warmHosts = /* @__PURE__ */ new Set();
|
|
23354
|
+
}
|
|
23355
|
+
if (this._warmHosts.size === 0) {
|
|
23356
|
+
queueMicrotask(() => {
|
|
23357
|
+
this._flushWarm();
|
|
23358
|
+
});
|
|
23359
|
+
}
|
|
23360
|
+
this._warmHosts.add(renderable);
|
|
23361
|
+
}
|
|
23362
|
+
/**
|
|
23363
|
+
* Run the warm-ups queued by {@link Renderer#_queueWarm}.
|
|
23364
|
+
* @param {boolean} [stage=false] - true when a stage is being entered,
|
|
23365
|
+
* which warms its cameras' grading as well
|
|
23366
|
+
* @returns {Promise<void>[]} what they started that is still being built
|
|
23367
|
+
* @ignore
|
|
23368
|
+
* @internal
|
|
23369
|
+
*/
|
|
23370
|
+
_flushWarm(stage = false) {
|
|
23371
|
+
const hosts = this._warmHosts === void 0 ? [] : [...this._warmHosts];
|
|
23372
|
+
if (hosts.length === 0 && !stage) {
|
|
23373
|
+
return [];
|
|
23374
|
+
}
|
|
23375
|
+
this._warmHosts?.clear();
|
|
23376
|
+
const cameras = stage || hosts.some((host) => {
|
|
23377
|
+
return host?._postEffectManaged === true;
|
|
23378
|
+
});
|
|
23379
|
+
this._warmFog = this._warmFogStates();
|
|
23380
|
+
try {
|
|
23381
|
+
return this._collectWarms(() => {
|
|
23382
|
+
for (const host of hosts) {
|
|
23383
|
+
try {
|
|
23384
|
+
this._warmRenderable(host);
|
|
23385
|
+
} catch {
|
|
23386
|
+
}
|
|
23387
|
+
}
|
|
23388
|
+
if (cameras) {
|
|
23389
|
+
try {
|
|
23390
|
+
this._warmCameraColorMatrices();
|
|
23391
|
+
} catch {
|
|
23392
|
+
}
|
|
23393
|
+
}
|
|
23394
|
+
});
|
|
23395
|
+
} finally {
|
|
23396
|
+
this._warmFog = void 0;
|
|
23397
|
+
}
|
|
23398
|
+
}
|
|
23399
|
+
/**
|
|
23400
|
+
* Run `warm` and hand back what it started that is still being built,
|
|
23401
|
+
* as one promise each. Every warm-up on this base is done by the time it
|
|
23402
|
+
* returns, so nothing.
|
|
23403
|
+
* @param {Function} warm - issues the warm-ups
|
|
23404
|
+
* @returns {Promise<void>[]} the warm-ups still on their way
|
|
23405
|
+
* @ignore
|
|
23406
|
+
* @internal
|
|
23407
|
+
*/
|
|
23408
|
+
_collectWarms(warm) {
|
|
23409
|
+
warm();
|
|
23410
|
+
return [];
|
|
23411
|
+
}
|
|
23412
|
+
/**
|
|
23413
|
+
* Run the queued warm-ups now, and say when they are built.
|
|
23414
|
+
*
|
|
23415
|
+
* What a state change waits on before drawing the stage it has just built
|
|
23416
|
+
* (see `state.change`). A warm-up on its own does not keep the first
|
|
23417
|
+
* frame from compiling: the frame comes some milliseconds after the stage
|
|
23418
|
+
* is built, and a pipeline still compiling then is compiled again, on the
|
|
23419
|
+
* spot, by the draw that needs it. Holding the frame until they land is
|
|
23420
|
+
* what lets them do their job.
|
|
23421
|
+
*
|
|
23422
|
+
* Only what this flush asked for is waited on, not every warm-up in
|
|
23423
|
+
* flight: one a game started for later (an effect attached ahead of
|
|
23424
|
+
* time, a level's own preload) is not this stage's to wait for.
|
|
23425
|
+
* @returns {Promise<void>|undefined} settles once they are built, or
|
|
23426
|
+
* `undefined` when nothing is being built and the frame can draw now
|
|
23427
|
+
* @ignore
|
|
23428
|
+
* @internal
|
|
23429
|
+
*/
|
|
23430
|
+
_whenWarmed() {
|
|
23431
|
+
let waits;
|
|
23432
|
+
try {
|
|
23433
|
+
waits = this._flushWarm(true);
|
|
23434
|
+
} catch {
|
|
23435
|
+
return void 0;
|
|
23436
|
+
}
|
|
23437
|
+
if (waits.length === 0) {
|
|
23438
|
+
return void 0;
|
|
23439
|
+
}
|
|
23440
|
+
return Promise.all(waits).then(() => {
|
|
23441
|
+
});
|
|
23442
|
+
}
|
|
23443
|
+
/**
|
|
23444
|
+
* Warm each of the current stage's cameras for its built-in colour
|
|
23445
|
+
* matrix.
|
|
23446
|
+
*
|
|
23447
|
+
* A camera applies `colorMatrix` as one more effect at the end of its
|
|
23448
|
+
* chain, but it built that effect inside its own `draw`, on the first
|
|
23449
|
+
* frame the matrix was not identity, and appends it for that frame only.
|
|
23450
|
+
* Nothing ever attached it, so nothing warmed it: its program (WebGL) or
|
|
23451
|
+
* its pipelines (WebGPU) were built in the first frame of a stage that
|
|
23452
|
+
* grades its camera. Made here instead, once the stage has been built,
|
|
23453
|
+
* and warmed where `draw` runs it, last in the camera's chain.
|
|
23454
|
+
* @ignore
|
|
23455
|
+
* @internal
|
|
23456
|
+
*/
|
|
23457
|
+
_warmCameraColorMatrices() {
|
|
23458
|
+
const cameras = state_default.current()?.cameras;
|
|
23459
|
+
if (cameras === void 0) {
|
|
23460
|
+
return;
|
|
23461
|
+
}
|
|
23462
|
+
for (const camera of cameras.values()) {
|
|
23463
|
+
const effect = camera._syncColorMatrixEffect?.(this);
|
|
23464
|
+
if (effect == null) {
|
|
23465
|
+
continue;
|
|
23466
|
+
}
|
|
23467
|
+
const effects = camera.postEffects;
|
|
23468
|
+
effects.push(effect);
|
|
23469
|
+
try {
|
|
23470
|
+
this._warmPostEffects(camera);
|
|
23471
|
+
} finally {
|
|
23472
|
+
effects.splice(effects.indexOf(effect), 1);
|
|
23473
|
+
}
|
|
23474
|
+
}
|
|
23475
|
+
}
|
|
23476
|
+
/**
|
|
23477
|
+
* Whether the meshes being warmed will draw with fog, without it, or both.
|
|
23478
|
+
*
|
|
23479
|
+
* Fog is a pipeline variant, and which one a mesh draws with is decided by
|
|
23480
|
+
* the camera drawing it: each camera installs its own fog on the renderer
|
|
23481
|
+
* as it starts drawing ({@link Renderer#setFog}), and the frame clears it.
|
|
23482
|
+
* So the renderer's own value is no answer here. A warm-up runs between
|
|
23483
|
+
* frames, while the renderer still holds the fog of whichever camera drew
|
|
23484
|
+
* LAST, which after a state change is the previous screen's: a loading
|
|
23485
|
+
* screen has none, so every mesh of a fogged scene was warmed without it
|
|
23486
|
+
* and then compiled with it, synchronously, on its first frame.
|
|
23487
|
+
*
|
|
23488
|
+
* Asked of the current stage's cameras instead, through the same
|
|
23489
|
+
* `_fog3dState` the draw installs, so the warm-up and the draw cannot
|
|
23490
|
+
* disagree. A stage with a fogged camera and a clear one (a minimap, say)
|
|
23491
|
+
* warms both variants. Fog switched on in a later frame than the scene was
|
|
23492
|
+
* built in is not seen here, and is built on that frame as before.
|
|
23493
|
+
* @returns {boolean[]} the fog states to warm, each `true` for fogged
|
|
23494
|
+
* @ignore
|
|
23495
|
+
* @internal
|
|
23496
|
+
*/
|
|
23497
|
+
_warmFogStates() {
|
|
23498
|
+
let fogged = false;
|
|
23499
|
+
let clear = false;
|
|
23500
|
+
const cameras = state_default.current()?.cameras;
|
|
23501
|
+
if (cameras !== void 0) {
|
|
23502
|
+
for (const camera of cameras.values()) {
|
|
23503
|
+
if (camera._fog3dState?.(this) != null) {
|
|
23504
|
+
fogged = true;
|
|
23505
|
+
} else {
|
|
23506
|
+
clear = true;
|
|
23507
|
+
}
|
|
23508
|
+
}
|
|
23509
|
+
}
|
|
23510
|
+
if (!fogged && !clear) {
|
|
23511
|
+
return [this._fog3d != null];
|
|
23512
|
+
}
|
|
23513
|
+
return clear && fogged ? [false, true] : [fogged];
|
|
23514
|
+
}
|
|
23515
|
+
/**
|
|
23516
|
+
* Warm one renderable, and everything under it when it is a container:
|
|
23517
|
+
* adding a container to the world brings its whole subtree in with it,
|
|
23518
|
+
* without an `addChild` per descendant.
|
|
23519
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23520
|
+
* @ignore
|
|
23521
|
+
* @internal
|
|
23522
|
+
*/
|
|
23523
|
+
_warmRenderable(renderable) {
|
|
23524
|
+
if (renderable == null) {
|
|
23525
|
+
return;
|
|
23526
|
+
}
|
|
23527
|
+
if (renderable.postEffects?.length > 0) {
|
|
23528
|
+
this._warmPostEffects(renderable);
|
|
23529
|
+
}
|
|
23530
|
+
this._warmDraw(renderable);
|
|
23531
|
+
if (isAdvancedBlendMode(renderable.blendMode)) {
|
|
23532
|
+
this._warmAdvancedBlend();
|
|
23533
|
+
}
|
|
23534
|
+
const children = renderable.children;
|
|
23535
|
+
if (Array.isArray(children)) {
|
|
23536
|
+
for (const child of children) {
|
|
23537
|
+
this._warmRenderable(child);
|
|
23538
|
+
}
|
|
23539
|
+
}
|
|
23540
|
+
}
|
|
23541
|
+
/**
|
|
23542
|
+
* Warm everything one renderable's post effects will need. Each backend
|
|
23543
|
+
* knows what that is; this base has no shaders, so nothing.
|
|
23544
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23545
|
+
* @ignore
|
|
23546
|
+
* @internal
|
|
23547
|
+
*/
|
|
23548
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
23549
|
+
_warmPostEffects(renderable) {
|
|
23550
|
+
}
|
|
23551
|
+
/**
|
|
23552
|
+
* Warm the renderable's own draw, the one it makes without any effect.
|
|
23553
|
+
* Only meaningful where that is a pipeline keyed by draw state; nothing
|
|
23554
|
+
* to do on this base.
|
|
23555
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23556
|
+
* @ignore
|
|
23557
|
+
* @internal
|
|
23558
|
+
*/
|
|
23559
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
23560
|
+
_warmDraw(renderable) {
|
|
23561
|
+
}
|
|
23562
|
+
/**
|
|
23563
|
+
* Warm the advanced blend modes' machinery: the blend shader, built on
|
|
23564
|
+
* the first draw that uses one of those modes, and its offscreen target.
|
|
23565
|
+
* Nothing to do on this base.
|
|
23566
|
+
* @ignore
|
|
23567
|
+
* @internal
|
|
23568
|
+
*/
|
|
23569
|
+
_warmAdvancedBlend() {
|
|
23570
|
+
}
|
|
23571
|
+
/**
|
|
23572
|
+
* Whether a renderable draws through `drawMesh`, where a lone effect is
|
|
23573
|
+
* spliced into the mesh shader instead of being drawn as a quad.
|
|
23574
|
+
* Duck-typed on fields only a `Mesh` carries, so the renderer does not
|
|
23575
|
+
* have to import the renderable classes. An instanced mesh never hosts
|
|
23576
|
+
* an effect: its shading is a separate family that knows nothing of one.
|
|
23577
|
+
* @param {Renderable} renderable - the renderable to test
|
|
23578
|
+
* @returns {boolean} true for a non-instanced mesh
|
|
23579
|
+
* @ignore
|
|
23580
|
+
* @internal
|
|
23581
|
+
*/
|
|
23582
|
+
_hostsMeshEffect(renderable) {
|
|
23583
|
+
return typeof renderable.cullBackFaces === "boolean" && renderable.indices !== void 0 && renderable.instanceLayout === void 0;
|
|
23584
|
+
}
|
|
23585
|
+
/**
|
|
23586
|
+
* Warm the tone curve's pipeline once it exists. Nothing to do on a
|
|
23587
|
+
* backend that compiles it in its constructor.
|
|
23588
|
+
* @ignore
|
|
23589
|
+
* @internal
|
|
23590
|
+
*/
|
|
23591
|
+
_warmToneMap() {
|
|
23592
|
+
}
|
|
23099
23593
|
/**
|
|
23100
23594
|
* Set the tone curve applied at the end of the camera's chain.
|
|
23101
23595
|
*
|
|
@@ -23172,6 +23666,7 @@ var Renderer = class _Renderer {
|
|
|
23172
23666
|
this._toneMapEffect?.destroy?.();
|
|
23173
23667
|
this._toneMapEffect = new ToneMappingEffect(this, { mode });
|
|
23174
23668
|
this._toneMapEffect.shared = true;
|
|
23669
|
+
this._warmToneMap();
|
|
23175
23670
|
}
|
|
23176
23671
|
this._toneMapEffect.setExposure(this.settings.toneMappingExposure ?? 1);
|
|
23177
23672
|
this._toneMapEffect.setWhite(this.settings.toneMappingWhite ?? 0);
|
|
@@ -28585,6 +29080,7 @@ var Container = class _Container extends Renderable {
|
|
|
28585
29080
|
registerChildBody(cchild, worldContainer);
|
|
28586
29081
|
});
|
|
28587
29082
|
}
|
|
29083
|
+
worldContainer?.app?.renderer?._queueWarm?.(child);
|
|
28588
29084
|
}
|
|
28589
29085
|
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
|
|
28590
29086
|
child.onActivateEvent();
|
|
@@ -28627,6 +29123,7 @@ var Container = class _Container extends Renderable {
|
|
|
28627
29123
|
registerChildBody(cchild, worldContainer);
|
|
28628
29124
|
});
|
|
28629
29125
|
}
|
|
29126
|
+
worldContainer?.app?.renderer?._queueWarm?.(child);
|
|
28630
29127
|
}
|
|
28631
29128
|
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
|
|
28632
29129
|
child.onActivateEvent();
|
|
@@ -39865,6 +40362,8 @@ var _transitionConfig = {
|
|
|
39865
40362
|
duration: 0
|
|
39866
40363
|
};
|
|
39867
40364
|
var _onSwitchComplete = null;
|
|
40365
|
+
var MAX_WARM_HOLD = 1500;
|
|
40366
|
+
var _heldSwitch = null;
|
|
39868
40367
|
var _extraArgs = null;
|
|
39869
40368
|
var _pauseTime = 0;
|
|
39870
40369
|
var _freezeTimer = null;
|
|
@@ -39918,9 +40417,17 @@ function _renderFrame(time) {
|
|
|
39918
40417
|
}
|
|
39919
40418
|
}
|
|
39920
40419
|
function _stopRunLoop() {
|
|
40420
|
+
_endHeldSwitches();
|
|
39921
40421
|
globalThis.cancelAnimationFrame(_animFrameId);
|
|
39922
40422
|
_animFrameId = -1;
|
|
39923
40423
|
}
|
|
40424
|
+
function _endHeldSwitches() {
|
|
40425
|
+
while (_heldSwitch !== null) {
|
|
40426
|
+
const finish = _heldSwitch;
|
|
40427
|
+
_heldSwitch = null;
|
|
40428
|
+
finish();
|
|
40429
|
+
}
|
|
40430
|
+
}
|
|
39924
40431
|
function _switchState(stateId) {
|
|
39925
40432
|
_stopRunLoop();
|
|
39926
40433
|
if (_stages[_state]) {
|
|
@@ -39928,17 +40435,45 @@ function _switchState(stateId) {
|
|
|
39928
40435
|
}
|
|
39929
40436
|
if (_stages[stateId]) {
|
|
39930
40437
|
_state = stateId;
|
|
40438
|
+
const onSwitchComplete = _onSwitchComplete;
|
|
40439
|
+
_onSwitchComplete = null;
|
|
40440
|
+
const finish = () => {
|
|
40441
|
+
try {
|
|
40442
|
+
emit(STATE_CHANGE);
|
|
40443
|
+
} finally {
|
|
40444
|
+
onSwitchComplete?.();
|
|
40445
|
+
}
|
|
40446
|
+
};
|
|
39931
40447
|
if (_extraArgs) {
|
|
39932
40448
|
_stages[_state].stage.reset(_app2, ..._extraArgs);
|
|
39933
40449
|
} else {
|
|
39934
40450
|
_stages[_state].stage.reset(_app2);
|
|
39935
40451
|
}
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
|
|
40452
|
+
const renderer = _app2?.renderer;
|
|
40453
|
+
const warmed = renderer?._whenWarmed?.();
|
|
40454
|
+
_endHeldSwitches();
|
|
40455
|
+
if (warmed === void 0) {
|
|
40456
|
+
_startRunLoop();
|
|
40457
|
+
finish();
|
|
40458
|
+
return;
|
|
39941
40459
|
}
|
|
40460
|
+
_heldSwitch = finish;
|
|
40461
|
+
let cap;
|
|
40462
|
+
const start = () => {
|
|
40463
|
+
clearTimeout(cap);
|
|
40464
|
+
if (_heldSwitch !== finish) {
|
|
40465
|
+
return;
|
|
40466
|
+
}
|
|
40467
|
+
_heldSwitch = null;
|
|
40468
|
+
_startRunLoop();
|
|
40469
|
+
finish();
|
|
40470
|
+
};
|
|
40471
|
+
void Promise.race([
|
|
40472
|
+
warmed,
|
|
40473
|
+
new Promise((resolve) => {
|
|
40474
|
+
cap = setTimeout(resolve, MAX_WARM_HOLD);
|
|
40475
|
+
})
|
|
40476
|
+
]).then(start, start);
|
|
39942
40477
|
}
|
|
39943
40478
|
}
|
|
39944
40479
|
var _app2;
|
|
@@ -40122,7 +40657,7 @@ var state3 = {
|
|
|
40122
40657
|
* @returns true if a "process is running"
|
|
40123
40658
|
*/
|
|
40124
40659
|
isRunning() {
|
|
40125
|
-
return _animFrameId !== -1;
|
|
40660
|
+
return _animFrameId !== -1 || _heldSwitch !== null;
|
|
40126
40661
|
},
|
|
40127
40662
|
/**
|
|
40128
40663
|
* Return the pause state of the state manager
|
|
@@ -41909,6 +42444,23 @@ var WebGLIndexBuffer = class extends IndexBuffer {
|
|
|
41909
42444
|
}
|
|
41910
42445
|
};
|
|
41911
42446
|
|
|
42447
|
+
// src/video/webgl/buffer/deletable.js
|
|
42448
|
+
var lostOnce = /* @__PURE__ */ new WeakSet();
|
|
42449
|
+
function markContextLost(gl) {
|
|
42450
|
+
if (gl) {
|
|
42451
|
+
lostOnce.add(gl);
|
|
42452
|
+
}
|
|
42453
|
+
}
|
|
42454
|
+
function deletable(gl, probe, handle) {
|
|
42455
|
+
if (gl.isContextLost() === true) {
|
|
42456
|
+
return false;
|
|
42457
|
+
}
|
|
42458
|
+
if (lostOnce.has(gl) === false) {
|
|
42459
|
+
return true;
|
|
42460
|
+
}
|
|
42461
|
+
return probe.call(gl, handle) === true;
|
|
42462
|
+
}
|
|
42463
|
+
|
|
41912
42464
|
// src/video/webgl/buffer/vertexstate.js
|
|
41913
42465
|
function bufferGroups(descriptor) {
|
|
41914
42466
|
if (Array.isArray(descriptor.buffers)) {
|
|
@@ -42057,12 +42609,15 @@ var WebGLVertexState = class {
|
|
|
42057
42609
|
/**
|
|
42058
42610
|
* Delete the underlying vertex array object, if any. Idempotent, and
|
|
42059
42611
|
* safe on a lost context: deleting an object belonging to a lost
|
|
42060
|
-
* context would queue `INVALID_OPERATION
|
|
42061
|
-
* it
|
|
42612
|
+
* context would queue `INVALID_OPERATION`, because deletion counts as
|
|
42613
|
+
* *using* it.
|
|
42614
|
+
*
|
|
42615
|
+
* The probe behind `deletable` is synchronous, so it is skipped unless
|
|
42616
|
+
* this context has actually lost one — see `buffer/deletable.js`.
|
|
42062
42617
|
*/
|
|
42063
42618
|
release() {
|
|
42064
42619
|
if (this.handle !== null) {
|
|
42065
|
-
if (this.gl.isVertexArray
|
|
42620
|
+
if (deletable(this.gl, this.gl.isVertexArray, this.handle)) {
|
|
42066
42621
|
this.gl.deleteVertexArray(this.handle);
|
|
42067
42622
|
}
|
|
42068
42623
|
this.handle = null;
|
|
@@ -43054,6 +43609,14 @@ function transformQuadCorners(m, x, y, w, h, z) {
|
|
|
43054
43609
|
}
|
|
43055
43610
|
return corners;
|
|
43056
43611
|
}
|
|
43612
|
+
var scaled = new Float64Array(4);
|
|
43613
|
+
function scaleQuad(x, y, w, h, sx, sy) {
|
|
43614
|
+
scaled[0] = x + w * (1 - sx) / 2;
|
|
43615
|
+
scaled[1] = y + h * (1 - sy) / 2;
|
|
43616
|
+
scaled[2] = w * sx;
|
|
43617
|
+
scaled[3] = h * sy;
|
|
43618
|
+
return scaled;
|
|
43619
|
+
}
|
|
43057
43620
|
|
|
43058
43621
|
// src/video/webgl/shaders/multitexture.js
|
|
43059
43622
|
function buildMultiTextureFragment(maxTextures) {
|
|
@@ -43092,6 +43655,8 @@ var immutableStorage = /* @__PURE__ */ new WeakMap();
|
|
|
43092
43655
|
function mipLevels(w, h) {
|
|
43093
43656
|
return Math.floor(Math.log2(Math.max(w, h))) + 1;
|
|
43094
43657
|
}
|
|
43658
|
+
var COMPRESSED_RGB_ETC1_WEBGL3 = 36196;
|
|
43659
|
+
var COMPRESSED_RGB8_ETC22 = 37492;
|
|
43095
43660
|
var MaterialBatcher = class extends WebGLBatcher {
|
|
43096
43661
|
/**
|
|
43097
43662
|
* Initialize the textured batcher
|
|
@@ -43201,11 +43766,20 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43201
43766
|
this.currentTextureUnit = unit;
|
|
43202
43767
|
const resourceOwned = pixels !== null && typeof pixels.upload === "function";
|
|
43203
43768
|
const compressed = pixels !== null && pixels.compressed === true;
|
|
43769
|
+
let compressedFormat = compressed ? pixels.format : void 0;
|
|
43770
|
+
let mutableETC1 = false;
|
|
43771
|
+
if (compressedFormat === COMPRESSED_RGB_ETC1_WEBGL3) {
|
|
43772
|
+
if (this.renderer.getSupportedCompressedTextureFormats().etc2) {
|
|
43773
|
+
compressedFormat = COMPRESSED_RGB8_ETC22;
|
|
43774
|
+
} else {
|
|
43775
|
+
mutableETC1 = true;
|
|
43776
|
+
}
|
|
43777
|
+
}
|
|
43204
43778
|
if (!resourceOwned) {
|
|
43205
43779
|
const storageW = Math.max(1, w | 0);
|
|
43206
43780
|
const storageH = Math.max(1, h | 0);
|
|
43207
43781
|
const levels2 = compressed ? pixels.mipmaps.length : mipmap === true ? mipLevels(storageW, storageH) : 1;
|
|
43208
|
-
const format = compressed ?
|
|
43782
|
+
const format = compressed ? compressedFormat : gl.RGBA8;
|
|
43209
43783
|
let storage = immutableStorage.get(currentTexture);
|
|
43210
43784
|
if (storage && (storage.width !== storageW || storage.height !== storageH || storage.levels !== levels2 || storage.format !== format)) {
|
|
43211
43785
|
gl.deleteTexture(currentTexture);
|
|
@@ -43214,7 +43788,7 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43214
43788
|
this.boundTextures[unit] = currentTexture;
|
|
43215
43789
|
storage = void 0;
|
|
43216
43790
|
}
|
|
43217
|
-
if (!storage) {
|
|
43791
|
+
if (!storage && !mutableETC1) {
|
|
43218
43792
|
gl.texStorage2D(gl.TEXTURE_2D, levels2, format, storageW, storageH);
|
|
43219
43793
|
immutableStorage.set(currentTexture, {
|
|
43220
43794
|
width: storageW,
|
|
@@ -43233,18 +43807,37 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43233
43807
|
pixels.upload(gl, gl.TEXTURE_2D);
|
|
43234
43808
|
} else if (compressed) {
|
|
43235
43809
|
const mipmaps = pixels.mipmaps;
|
|
43236
|
-
|
|
43237
|
-
gl.
|
|
43810
|
+
if (mutableETC1) {
|
|
43811
|
+
gl.texParameteri(
|
|
43238
43812
|
gl.TEXTURE_2D,
|
|
43239
|
-
|
|
43240
|
-
|
|
43241
|
-
0,
|
|
43242
|
-
mipmaps[i].width,
|
|
43243
|
-
mipmaps[i].height,
|
|
43244
|
-
pixels.format,
|
|
43245
|
-
mipmaps[i].data
|
|
43813
|
+
gl.TEXTURE_MAX_LEVEL,
|
|
43814
|
+
mipmaps.length - 1
|
|
43246
43815
|
);
|
|
43247
43816
|
}
|
|
43817
|
+
for (let i = 0; i < mipmaps.length; i++) {
|
|
43818
|
+
if (mutableETC1) {
|
|
43819
|
+
gl.compressedTexImage2D(
|
|
43820
|
+
gl.TEXTURE_2D,
|
|
43821
|
+
i,
|
|
43822
|
+
compressedFormat,
|
|
43823
|
+
mipmaps[i].width,
|
|
43824
|
+
mipmaps[i].height,
|
|
43825
|
+
0,
|
|
43826
|
+
mipmaps[i].data
|
|
43827
|
+
);
|
|
43828
|
+
} else {
|
|
43829
|
+
gl.compressedTexSubImage2D(
|
|
43830
|
+
gl.TEXTURE_2D,
|
|
43831
|
+
i,
|
|
43832
|
+
0,
|
|
43833
|
+
0,
|
|
43834
|
+
mipmaps[i].width,
|
|
43835
|
+
mipmaps[i].height,
|
|
43836
|
+
compressedFormat,
|
|
43837
|
+
mipmaps[i].data
|
|
43838
|
+
);
|
|
43839
|
+
}
|
|
43840
|
+
}
|
|
43248
43841
|
} else if (pixels === null) {
|
|
43249
43842
|
} else if (typeof pixels.byteLength !== "undefined") {
|
|
43250
43843
|
gl.texSubImage2D(
|
|
@@ -43653,6 +44246,14 @@ var QuadBatcher = class extends MaterialBatcher {
|
|
|
43653
44246
|
);
|
|
43654
44247
|
}
|
|
43655
44248
|
this.currentShader._setUVYDir?.(1);
|
|
44249
|
+
const scale2 = this.currentShader._vertexScale;
|
|
44250
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
44251
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
44252
|
+
x = q[0];
|
|
44253
|
+
y = q[1];
|
|
44254
|
+
w = q[2];
|
|
44255
|
+
h = q[3];
|
|
44256
|
+
}
|
|
43656
44257
|
}
|
|
43657
44258
|
const [vec0, vec1, vec2, vec3] = transformQuadCorners(
|
|
43658
44259
|
this.viewMatrix,
|
|
@@ -43670,85 +44271,6 @@ var QuadBatcher = class extends MaterialBatcher {
|
|
|
43670
44271
|
}
|
|
43671
44272
|
};
|
|
43672
44273
|
|
|
43673
|
-
// src/video/blendmodes.js
|
|
43674
|
-
var ADVANCED_BLEND_MODES = [
|
|
43675
|
-
"difference",
|
|
43676
|
-
"overlay",
|
|
43677
|
-
"hard-light",
|
|
43678
|
-
"color-dodge",
|
|
43679
|
-
"color-burn",
|
|
43680
|
-
"soft-light",
|
|
43681
|
-
// `darken` and `lighten` joined the shader path in 20.2: fixed-function
|
|
43682
|
-
// MIN/MAX compute `min(src, dst)` and nothing else, so there is nowhere to
|
|
43683
|
-
// put the `(1 - srcAlpha) * dst` term source-over contributes after the
|
|
43684
|
-
// blend. A translucent source lost the backdrop's share entirely — a white
|
|
43685
|
-
// `lighten` glow over a light backdrop rendered invisible. APPENDED, never
|
|
43686
|
-
// inserted: the index is the shader's branch value.
|
|
43687
|
-
"darken",
|
|
43688
|
-
"lighten"
|
|
43689
|
-
];
|
|
43690
|
-
function isAdvancedBlendMode(mode) {
|
|
43691
|
-
return ADVANCED_BLEND_MODES.indexOf(mode) !== -1;
|
|
43692
|
-
}
|
|
43693
|
-
function advancedBlendModeIndex(mode) {
|
|
43694
|
-
return ADVANCED_BLEND_MODES.indexOf(mode);
|
|
43695
|
-
}
|
|
43696
|
-
function normalizeBlendMode(mode) {
|
|
43697
|
-
switch (mode) {
|
|
43698
|
-
case "none":
|
|
43699
|
-
case "multiply":
|
|
43700
|
-
case "screen":
|
|
43701
|
-
case "exclusion":
|
|
43702
|
-
case "normal":
|
|
43703
|
-
return mode;
|
|
43704
|
-
case "additive":
|
|
43705
|
-
case "add":
|
|
43706
|
-
case "lighter":
|
|
43707
|
-
return "additive";
|
|
43708
|
-
default:
|
|
43709
|
-
return "normal";
|
|
43710
|
-
}
|
|
43711
|
-
}
|
|
43712
|
-
function isSupportedBlendMode(mode) {
|
|
43713
|
-
return isAdvancedBlendMode(mode) || mode === "normal" || normalizeBlendMode(mode) !== "normal";
|
|
43714
|
-
}
|
|
43715
|
-
function reportedBlendMode(mode) {
|
|
43716
|
-
return isSupportedBlendMode(mode) ? mode : "normal";
|
|
43717
|
-
}
|
|
43718
|
-
function blendStateFor(mode, premultipliedAlpha) {
|
|
43719
|
-
const src = premultipliedAlpha ? "one" : "src-alpha";
|
|
43720
|
-
switch (mode) {
|
|
43721
|
-
case "none":
|
|
43722
|
-
return void 0;
|
|
43723
|
-
case "additive":
|
|
43724
|
-
return { operation: "add", srcFactor: src, dstFactor: "one" };
|
|
43725
|
-
case "multiply":
|
|
43726
|
-
return {
|
|
43727
|
-
operation: "add",
|
|
43728
|
-
srcFactor: "dst",
|
|
43729
|
-
dstFactor: "one-minus-src-alpha"
|
|
43730
|
-
};
|
|
43731
|
-
case "exclusion":
|
|
43732
|
-
return {
|
|
43733
|
-
operation: "add",
|
|
43734
|
-
srcFactor: "one-minus-dst",
|
|
43735
|
-
dstFactor: "one-minus-src"
|
|
43736
|
-
};
|
|
43737
|
-
case "screen":
|
|
43738
|
-
return {
|
|
43739
|
-
operation: "add",
|
|
43740
|
-
srcFactor: "one",
|
|
43741
|
-
dstFactor: "one-minus-src"
|
|
43742
|
-
};
|
|
43743
|
-
default:
|
|
43744
|
-
return {
|
|
43745
|
-
operation: "add",
|
|
43746
|
-
srcFactor: src,
|
|
43747
|
-
dstFactor: "one-minus-src-alpha"
|
|
43748
|
-
};
|
|
43749
|
-
}
|
|
43750
|
-
}
|
|
43751
|
-
|
|
43752
44274
|
// src/video/effects/blendEffect.js
|
|
43753
44275
|
var wgslFragment2 = `
|
|
43754
44276
|
struct BlendUniforms {
|
|
@@ -44032,6 +44554,26 @@ var RenderTargetPool = class {
|
|
|
44032
44554
|
}
|
|
44033
44555
|
return rt;
|
|
44034
44556
|
}
|
|
44557
|
+
/**
|
|
44558
|
+
* Create, ahead of the pass that will use them, the targets a top-level
|
|
44559
|
+
* chain needs: the camera pair, or the first sprite pair. Targets that
|
|
44560
|
+
* already exist are left as they are (the pass resizes them when it
|
|
44561
|
+
* begins), and the active-pass stack is not touched, so this is safe
|
|
44562
|
+
* outside any pass.
|
|
44563
|
+
* @param {boolean} isCamera - true for the camera pair (0/1), false for the first sprite pair (2/3)
|
|
44564
|
+
* @param {number} effectCount - the number of effects in the chain
|
|
44565
|
+
* @param {number} width - target width in pixels
|
|
44566
|
+
* @param {number} height - target height in pixels
|
|
44567
|
+
*/
|
|
44568
|
+
reserve(isCamera, effectCount, width, height) {
|
|
44569
|
+
const base = isCamera ? 0 : 2;
|
|
44570
|
+
if (!this._pool[base]) {
|
|
44571
|
+
this.get(base, width, height);
|
|
44572
|
+
}
|
|
44573
|
+
if (effectCount > 1 && !this._pool[base + 1]) {
|
|
44574
|
+
this.get(base + 1, width, height);
|
|
44575
|
+
}
|
|
44576
|
+
}
|
|
44035
44577
|
/**
|
|
44036
44578
|
* Get the capture render target for the current active pass.
|
|
44037
44579
|
* @returns {RenderTarget|undefined} the capture target, or undefined if no active pass
|
|
@@ -44751,7 +45293,7 @@ var UniformBlock = class {
|
|
|
44751
45293
|
destroy() {
|
|
44752
45294
|
const gl = this.gl;
|
|
44753
45295
|
if (this.buffer !== null) {
|
|
44754
|
-
if (gl.isBuffer
|
|
45296
|
+
if (deletable(gl, gl.isBuffer, this.buffer)) {
|
|
44755
45297
|
gl.deleteBuffer(this.buffer);
|
|
44756
45298
|
}
|
|
44757
45299
|
this.buffer = null;
|
|
@@ -45004,14 +45546,13 @@ var WebGLInstanceBuffer = class {
|
|
|
45004
45546
|
);
|
|
45005
45547
|
}
|
|
45006
45548
|
/**
|
|
45007
|
-
* Release the GL buffer. Safe to call twice, and on a lost context
|
|
45008
|
-
*
|
|
45009
|
-
* context died raises INVALID_OPERATION.
|
|
45549
|
+
* Release the GL buffer. Safe to call twice, and on a lost context:
|
|
45550
|
+
* deleting a handle whose context died raises INVALID_OPERATION.
|
|
45010
45551
|
*/
|
|
45011
45552
|
destroy() {
|
|
45012
45553
|
const gl = this.gl;
|
|
45013
45554
|
if (this.buffer !== null) {
|
|
45014
|
-
if (gl.isBuffer
|
|
45555
|
+
if (deletable(gl, gl.isBuffer, this.buffer)) {
|
|
45015
45556
|
gl.deleteBuffer(this.buffer);
|
|
45016
45557
|
}
|
|
45017
45558
|
this.buffer = null;
|
|
@@ -45097,13 +45638,13 @@ var RetainedGeometry = class {
|
|
|
45097
45638
|
this.vertexState = null;
|
|
45098
45639
|
}
|
|
45099
45640
|
if (this.vertexBuffer !== null) {
|
|
45100
|
-
if (gl.isBuffer
|
|
45641
|
+
if (deletable(gl, gl.isBuffer, this.vertexBuffer)) {
|
|
45101
45642
|
gl.deleteBuffer(this.vertexBuffer);
|
|
45102
45643
|
}
|
|
45103
45644
|
this.vertexBuffer = null;
|
|
45104
45645
|
}
|
|
45105
45646
|
if (this.glIndexBuffer !== null) {
|
|
45106
|
-
if (gl.isBuffer
|
|
45647
|
+
if (deletable(gl, gl.isBuffer, this.glIndexBuffer)) {
|
|
45107
45648
|
gl.deleteBuffer(this.glIndexBuffer);
|
|
45108
45649
|
}
|
|
45109
45650
|
this.glIndexBuffer = null;
|
|
@@ -45142,6 +45683,14 @@ var _EYE_POSITION = new Float32Array(3);
|
|
|
45142
45683
|
var _FOG_COLOR = new Float32Array(3);
|
|
45143
45684
|
var _FOG_PARAMS = new Float32Array(4);
|
|
45144
45685
|
var _FOG_HEIGHT = new Float32Array(4);
|
|
45686
|
+
function prewarmYield() {
|
|
45687
|
+
if (globalThis.document?.hidden === true) {
|
|
45688
|
+
return Promise.resolve();
|
|
45689
|
+
}
|
|
45690
|
+
return new Promise((resolve) => {
|
|
45691
|
+
setTimeout(resolve, 0);
|
|
45692
|
+
});
|
|
45693
|
+
}
|
|
45145
45694
|
var MeshBatcher = class extends MaterialBatcher {
|
|
45146
45695
|
/**
|
|
45147
45696
|
* Initialize the mesh batcher
|
|
@@ -45725,7 +46274,21 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45725
46274
|
* @internal
|
|
45726
46275
|
*/
|
|
45727
46276
|
effectShaderFor(effect) {
|
|
45728
|
-
|
|
46277
|
+
return this._effectShader(effect, this._fogDefine());
|
|
46278
|
+
}
|
|
46279
|
+
/**
|
|
46280
|
+
* The spliced program for one effect under one fog state.
|
|
46281
|
+
*
|
|
46282
|
+
* Split out of {@link MeshBatcher#effectShaderFor} so `prewarm` can build
|
|
46283
|
+
* BOTH fog variants at load, rather than only the one the current scene
|
|
46284
|
+
* happens to be using.
|
|
46285
|
+
* @param {object} effect - the hosted ShaderEffect
|
|
46286
|
+
* @param {string} fog - the fog define, or ""
|
|
46287
|
+
* @returns {GLShader|null} the spliced program, or null if it will not build
|
|
46288
|
+
* @ignore
|
|
46289
|
+
* @internal
|
|
46290
|
+
*/
|
|
46291
|
+
_effectShader(effect, fog) {
|
|
45729
46292
|
const sources = this._shaderSources();
|
|
45730
46293
|
const fragment2 = spliceEffect(sources.fragment, effect._meshBody);
|
|
45731
46294
|
if (fragment2 === null) {
|
|
@@ -45858,17 +46421,14 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45858
46421
|
* @ignore
|
|
45859
46422
|
* @internal
|
|
45860
46423
|
*/
|
|
45861
|
-
|
|
45862
|
-
|
|
45863
|
-
|
|
45864
|
-
|
|
45865
|
-
|
|
45866
|
-
|
|
45867
|
-
setTimeout(resolve, 0);
|
|
45868
|
-
});
|
|
45869
|
-
};
|
|
46424
|
+
/**
|
|
46425
|
+
* @param {object[]} [effects] - hosted ShaderEffects to warm the spliced
|
|
46426
|
+
* mesh variants for, normally every preloaded `{type: "shader"}` asset
|
|
46427
|
+
*/
|
|
46428
|
+
async prewarm(effects = []) {
|
|
46429
|
+
const yieldToFrame = prewarmYield;
|
|
45870
46430
|
const alive = () => {
|
|
45871
|
-
return this.
|
|
46431
|
+
return this._prewarmAlive();
|
|
45872
46432
|
};
|
|
45873
46433
|
const sources = this._shaderSources();
|
|
45874
46434
|
const instanced = this._instancedShaderSources();
|
|
@@ -45906,6 +46466,54 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45906
46466
|
return;
|
|
45907
46467
|
}
|
|
45908
46468
|
}
|
|
46469
|
+
await this.prewarmEffects(effects);
|
|
46470
|
+
}
|
|
46471
|
+
/**
|
|
46472
|
+
* Build the spliced mesh variant of each effect, in both fog states.
|
|
46473
|
+
*
|
|
46474
|
+
* A ShaderEffect hosted on a mesh does NOT draw with the program its
|
|
46475
|
+
* asset compiled to: the body is spliced into the mesh shader, which is a
|
|
46476
|
+
* different program again, and it was built on the frame the effect first
|
|
46477
|
+
* drew. Preloading the shader looked like it covered this and did not, so
|
|
46478
|
+
* a game that declared every effect in its manifest still stuttered the
|
|
46479
|
+
* first time each one appeared. Warming them here is what makes the
|
|
46480
|
+
* preload mean what it looks like it means.
|
|
46481
|
+
*
|
|
46482
|
+
* Separate from {@link MeshBatcher#prewarm} because the two halves have
|
|
46483
|
+
* different lifetimes: the batcher's own variants are built once, while
|
|
46484
|
+
* the effects grow with every preload.
|
|
46485
|
+
* @param {object[]} [effects] - hosted ShaderEffects, normally the
|
|
46486
|
+
* preloaded `{type: "shader"}` assets
|
|
46487
|
+
* @returns {Promise<void>} settles once every variant is linked
|
|
46488
|
+
* @ignore
|
|
46489
|
+
* @internal
|
|
46490
|
+
*/
|
|
46491
|
+
async prewarmEffects(effects = []) {
|
|
46492
|
+
for (const fogDefine of ["", "#define FOG\n"]) {
|
|
46493
|
+
for (const effect of effects) {
|
|
46494
|
+
if (effect?._meshBody === void 0) {
|
|
46495
|
+
continue;
|
|
46496
|
+
}
|
|
46497
|
+
this._effectShader(effect, fogDefine);
|
|
46498
|
+
await prewarmYield();
|
|
46499
|
+
if (!this._prewarmAlive()) {
|
|
46500
|
+
return;
|
|
46501
|
+
}
|
|
46502
|
+
}
|
|
46503
|
+
}
|
|
46504
|
+
}
|
|
46505
|
+
/**
|
|
46506
|
+
* Whether a warm-up may keep building into this batcher. A yield is a
|
|
46507
|
+
* window in which the application can be destroyed or the context lost,
|
|
46508
|
+
* and building into a batcher that has been torn down leaks a `GLShader`
|
|
46509
|
+
* per remaining variant, each subscribed to the global context-loss
|
|
46510
|
+
* events with nothing left to destroy it.
|
|
46511
|
+
* @returns {boolean} true while the context is live
|
|
46512
|
+
* @ignore
|
|
46513
|
+
* @internal
|
|
46514
|
+
*/
|
|
46515
|
+
_prewarmAlive() {
|
|
46516
|
+
return this.gl !== null && this.gl?.isContextLost?.() !== true;
|
|
45909
46517
|
}
|
|
45910
46518
|
/**
|
|
45911
46519
|
* Whether the bound program is one this batcher would pick for a plain
|
|
@@ -47418,6 +48026,14 @@ var LitQuadBatcher = class extends QuadBatcher {
|
|
|
47418
48026
|
);
|
|
47419
48027
|
}
|
|
47420
48028
|
this.currentShader._setUVYDir?.(1);
|
|
48029
|
+
const scale2 = this.currentShader._vertexScale;
|
|
48030
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
48031
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
48032
|
+
x = q[0];
|
|
48033
|
+
y = q[1];
|
|
48034
|
+
w = q[2];
|
|
48035
|
+
h = q[3];
|
|
48036
|
+
}
|
|
47421
48037
|
}
|
|
47422
48038
|
let normalTextureId = -1;
|
|
47423
48039
|
let shininess = 0;
|
|
@@ -48123,6 +48739,7 @@ function initBlendEnums(gl) {
|
|
|
48123
48739
|
"one-minus-dst-alpha": gl.ONE_MINUS_DST_ALPHA
|
|
48124
48740
|
};
|
|
48125
48741
|
}
|
|
48742
|
+
var MESH_EFFECT_LINK_BUDGET = 4;
|
|
48126
48743
|
var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
48127
48744
|
/**
|
|
48128
48745
|
* @param {ApplicationSettings} [options] - optional parameters for the renderer
|
|
@@ -48214,6 +48831,7 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48214
48831
|
(e) => {
|
|
48215
48832
|
e.preventDefault();
|
|
48216
48833
|
this.isContextValid = false;
|
|
48834
|
+
markContextLost(this.gl);
|
|
48217
48835
|
this.vertexBuffer = null;
|
|
48218
48836
|
emit(ONCONTEXT_LOST, this);
|
|
48219
48837
|
},
|
|
@@ -48252,6 +48870,11 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48252
48870
|
if (renderer === this) {
|
|
48253
48871
|
this.currentProgram = void 0;
|
|
48254
48872
|
this._prewarmed = void 0;
|
|
48873
|
+
this._prewarmedEffects = /* @__PURE__ */ new WeakSet();
|
|
48874
|
+
if (this.settings.prewarm === true) {
|
|
48875
|
+
this.prewarm().catch(() => {
|
|
48876
|
+
});
|
|
48877
|
+
}
|
|
48255
48878
|
}
|
|
48256
48879
|
};
|
|
48257
48880
|
this.onCanvasResize = (width, height) => {
|
|
@@ -48407,18 +49030,213 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48407
49030
|
* @see {@link Renderer#prewarm}
|
|
48408
49031
|
*/
|
|
48409
49032
|
prewarm() {
|
|
49033
|
+
this._prewarmedEffects ??= /* @__PURE__ */ new WeakSet();
|
|
49034
|
+
const effects = Object.values(shaderList).filter((effect) => {
|
|
49035
|
+
return effect != null && !this._prewarmedEffects.has(effect);
|
|
49036
|
+
});
|
|
49037
|
+
for (const effect of effects) {
|
|
49038
|
+
this._prewarmedEffects.add(effect);
|
|
49039
|
+
}
|
|
49040
|
+
const batchers = [...this.batchers.values()];
|
|
48410
49041
|
if (this._prewarmed === void 0) {
|
|
48411
49042
|
this._prewarmed = Promise.all(
|
|
48412
|
-
|
|
48413
|
-
return batcher.prewarm();
|
|
49043
|
+
batchers.map((batcher) => {
|
|
49044
|
+
return batcher.prewarm(effects);
|
|
48414
49045
|
})
|
|
48415
49046
|
).then(() => {
|
|
48416
49047
|
this.setBatcher("quad");
|
|
48417
49048
|
});
|
|
49049
|
+
} else if (effects.length > 0) {
|
|
49050
|
+
this._prewarmed = this._prewarmed.then(() => {
|
|
49051
|
+
return Promise.all(
|
|
49052
|
+
batchers.map((batcher) => {
|
|
49053
|
+
return batcher.prewarmEffects?.(effects);
|
|
49054
|
+
})
|
|
49055
|
+
).then(() => {
|
|
49056
|
+
this.setBatcher("quad");
|
|
49057
|
+
});
|
|
49058
|
+
});
|
|
48418
49059
|
}
|
|
48419
49060
|
return this._prewarmed;
|
|
48420
49061
|
}
|
|
49062
|
+
/**
|
|
49063
|
+
* Warm what one renderable's post effects will need on this backend.
|
|
49064
|
+
*
|
|
49065
|
+
* An effect's own program is linked when the effect is constructed, so
|
|
49066
|
+
* what is left is what depends on where it is drawn: a mesh does not
|
|
49067
|
+
* draw an effect with that program but with the effect spliced into the
|
|
49068
|
+
* mesh shader, built on first draw; and a chain composites through
|
|
49069
|
+
* render targets, built on the frame the chain first runs or grows a
|
|
49070
|
+
* second effect.
|
|
49071
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
49072
|
+
* @override
|
|
49073
|
+
* @ignore
|
|
49074
|
+
* @internal
|
|
49075
|
+
*/
|
|
49076
|
+
_warmPostEffects(renderable) {
|
|
49077
|
+
const gl = this.gl;
|
|
49078
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
49079
|
+
return;
|
|
49080
|
+
}
|
|
49081
|
+
const effects = renderable.postEffects.filter((fx) => {
|
|
49082
|
+
return fx != null;
|
|
49083
|
+
});
|
|
49084
|
+
if (effects.length === 0) {
|
|
49085
|
+
return;
|
|
49086
|
+
}
|
|
49087
|
+
const isCamera = renderable._postEffectManaged === true;
|
|
49088
|
+
if (this._effectPassDepth === 0 && (isCamera || effects.length > 1 || renderable.postEffectNeedsCapture === true)) {
|
|
49089
|
+
const { width, height } = this.getCanvas();
|
|
49090
|
+
this.flush();
|
|
49091
|
+
this._renderTargetPool.reserve(
|
|
49092
|
+
isCamera,
|
|
49093
|
+
this._postEffectCapacity(renderable, effects),
|
|
49094
|
+
width,
|
|
49095
|
+
height
|
|
49096
|
+
);
|
|
49097
|
+
this.invalidateTextureUnit(0);
|
|
49098
|
+
}
|
|
49099
|
+
if (!this._hostsMeshEffect(renderable)) {
|
|
49100
|
+
return;
|
|
49101
|
+
}
|
|
49102
|
+
const batcher = this.batchers.get(
|
|
49103
|
+
renderable.lit === true ? "litMesh" : "mesh"
|
|
49104
|
+
);
|
|
49105
|
+
if (batcher === void 0) {
|
|
49106
|
+
return;
|
|
49107
|
+
}
|
|
49108
|
+
this._meshEffectWarmQueue ??= [];
|
|
49109
|
+
for (const fog of this._warmFog ?? [this._fog3d != null]) {
|
|
49110
|
+
const fogDefine = fog ? "#define FOG\n" : "";
|
|
49111
|
+
for (const fx of effects) {
|
|
49112
|
+
this._meshEffectWarmQueue.push(batcher, fx, fogDefine);
|
|
49113
|
+
}
|
|
49114
|
+
}
|
|
49115
|
+
if (this._meshEffectWarmScheduled !== true) {
|
|
49116
|
+
this._meshEffectWarmScheduled = true;
|
|
49117
|
+
this._meshEffectWarmDone = new Promise((resolve) => {
|
|
49118
|
+
this._meshEffectWarmSettle = resolve;
|
|
49119
|
+
});
|
|
49120
|
+
queueMicrotask(() => {
|
|
49121
|
+
this._linkQueuedMeshEffects();
|
|
49122
|
+
});
|
|
49123
|
+
}
|
|
49124
|
+
}
|
|
49125
|
+
/**
|
|
49126
|
+
* Run `warm` and hand back what is still to be built once it returns:
|
|
49127
|
+
* the spliced mesh programs it queued, which are linked over the next
|
|
49128
|
+
* tasks rather than all at once.
|
|
49129
|
+
* @param {Function} warm - issues the warm-ups
|
|
49130
|
+
* @returns {Promise<void>[]} the queued links, as one promise, or nothing
|
|
49131
|
+
* @override
|
|
49132
|
+
* @ignore
|
|
49133
|
+
* @internal
|
|
49134
|
+
*/
|
|
49135
|
+
_collectWarms(warm) {
|
|
49136
|
+
warm();
|
|
49137
|
+
return this._meshEffectWarmScheduled === true ? [this._meshEffectWarmDone] : [];
|
|
49138
|
+
}
|
|
49139
|
+
/**
|
|
49140
|
+
* Link the spliced mesh programs queued by `_warmPostEffects`, a few
|
|
49141
|
+
* milliseconds' worth per task, so a pool's worth spreads over the next
|
|
49142
|
+
* frames instead of landing in one. A draw that comes first simply links
|
|
49143
|
+
* its own program, as it would have without the warm-up.
|
|
49144
|
+
* @ignore
|
|
49145
|
+
* @internal
|
|
49146
|
+
*/
|
|
49147
|
+
_linkQueuedMeshEffects() {
|
|
49148
|
+
const queue = this._meshEffectWarmQueue;
|
|
49149
|
+
const gl = this.gl;
|
|
49150
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
49151
|
+
queue.length = 0;
|
|
49152
|
+
this._settleMeshEffectWarm();
|
|
49153
|
+
return;
|
|
49154
|
+
}
|
|
49155
|
+
const start = globalThis.performance.now();
|
|
49156
|
+
const previous = this.currentProgram;
|
|
49157
|
+
try {
|
|
49158
|
+
while (queue.length > 0 && globalThis.performance.now() - start < MESH_EFFECT_LINK_BUDGET) {
|
|
49159
|
+
const batcher = queue.shift();
|
|
49160
|
+
const fx = queue.shift();
|
|
49161
|
+
const fogDefine = queue.shift();
|
|
49162
|
+
if (fx.destroyed !== true && batcher.canHostEffect?.(fx) === true) {
|
|
49163
|
+
batcher._effectShader(fx, fogDefine);
|
|
49164
|
+
}
|
|
49165
|
+
}
|
|
49166
|
+
} finally {
|
|
49167
|
+
this._restoreProgram(previous);
|
|
49168
|
+
}
|
|
49169
|
+
if (queue.length === 0) {
|
|
49170
|
+
this._settleMeshEffectWarm();
|
|
49171
|
+
} else if (globalThis.document?.hidden === true) {
|
|
49172
|
+
this._linkQueuedMeshEffects();
|
|
49173
|
+
} else {
|
|
49174
|
+
setTimeout(() => {
|
|
49175
|
+
this._linkQueuedMeshEffects();
|
|
49176
|
+
}, 0);
|
|
49177
|
+
}
|
|
49178
|
+
}
|
|
49179
|
+
/**
|
|
49180
|
+
* The link queue is empty: let the next warm-up schedule a new one, and
|
|
49181
|
+
* release whatever is waiting on this one.
|
|
49182
|
+
* @ignore
|
|
49183
|
+
* @internal
|
|
49184
|
+
*/
|
|
49185
|
+
_settleMeshEffectWarm() {
|
|
49186
|
+
this._meshEffectWarmScheduled = false;
|
|
49187
|
+
this._meshEffectWarmSettle?.();
|
|
49188
|
+
this._meshEffectWarmSettle = void 0;
|
|
49189
|
+
}
|
|
49190
|
+
/**
|
|
49191
|
+
* Build the advanced blend shader and its offscreen target before the
|
|
49192
|
+
* first draw in one of those modes, which otherwise compiles the shader
|
|
49193
|
+
* and creates the target in the middle of that frame.
|
|
49194
|
+
* @override
|
|
49195
|
+
* @ignore
|
|
49196
|
+
* @internal
|
|
49197
|
+
*/
|
|
49198
|
+
_warmAdvancedBlend() {
|
|
49199
|
+
const gl = this.gl;
|
|
49200
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
49201
|
+
return;
|
|
49202
|
+
}
|
|
49203
|
+
if (typeof this._advancedBlendEffect === "undefined") {
|
|
49204
|
+
const previous = this.currentProgram;
|
|
49205
|
+
try {
|
|
49206
|
+
this._advancedBlendEffect = new BlendEffect(this);
|
|
49207
|
+
} finally {
|
|
49208
|
+
this._restoreProgram(previous);
|
|
49209
|
+
}
|
|
49210
|
+
}
|
|
49211
|
+
if (typeof this._advancedBlendTarget === "undefined" && this._effectPassDepth === 0 && this._advancedBlendOpen !== true) {
|
|
49212
|
+
const { width, height } = this.getCanvas();
|
|
49213
|
+
this.flush();
|
|
49214
|
+
this._advancedBlendTarget = new WebGLRenderTarget(gl, width, height, {
|
|
49215
|
+
samples: 0,
|
|
49216
|
+
format: gl.RGBA8
|
|
49217
|
+
});
|
|
49218
|
+
this.invalidateTextureUnit(0);
|
|
49219
|
+
}
|
|
49220
|
+
}
|
|
49221
|
+
/**
|
|
49222
|
+
* Put back the program a warm-up found bound. Linking leaves the new
|
|
49223
|
+
* program bound, and the batchers trust what they last bound, so a
|
|
49224
|
+
* warm-up between two draws would otherwise hand the next one a
|
|
49225
|
+
* stranger's program.
|
|
49226
|
+
* @param {WebGLProgram|undefined} previous - what was bound before
|
|
49227
|
+
* @ignore
|
|
49228
|
+
* @internal
|
|
49229
|
+
*/
|
|
49230
|
+
_restoreProgram(previous) {
|
|
49231
|
+
if (previous != null && this.currentProgram !== previous) {
|
|
49232
|
+
this.gl.useProgram(previous);
|
|
49233
|
+
this.currentProgram = previous;
|
|
49234
|
+
}
|
|
49235
|
+
}
|
|
48421
49236
|
destroy() {
|
|
49237
|
+
if (this._meshEffectWarmQueue !== void 0) {
|
|
49238
|
+
this._meshEffectWarmQueue.length = 0;
|
|
49239
|
+
}
|
|
48422
49240
|
this._toneMapEffect?.destroy?.();
|
|
48423
49241
|
this._toneMapEffect = void 0;
|
|
48424
49242
|
off(GAME_RESET, this.onGameReset);
|
|
@@ -48956,7 +49774,12 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48956
49774
|
}
|
|
48957
49775
|
savedProjection.copy(this.projectionMatrix);
|
|
48958
49776
|
this._effectPassDepth++;
|
|
48959
|
-
const rt = this._renderTargetPool.begin(
|
|
49777
|
+
const rt = this._renderTargetPool.begin(
|
|
49778
|
+
isCamera,
|
|
49779
|
+
this._postEffectCapacity(renderable, effects),
|
|
49780
|
+
w,
|
|
49781
|
+
h
|
|
49782
|
+
);
|
|
48960
49783
|
this.invalidateTextureUnit(0);
|
|
48961
49784
|
rt.bind();
|
|
48962
49785
|
this.setViewport(0, 0, w, h);
|
|
@@ -49447,8 +50270,16 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
49447
50270
|
* @see WebGLRenderer#createPattern
|
|
49448
50271
|
*/
|
|
49449
50272
|
drawPattern(pattern, x, y, width, height) {
|
|
50273
|
+
if (this.customShader?._screenTextureUniforms?.length > 0) {
|
|
50274
|
+
this.toFrameTexture();
|
|
50275
|
+
}
|
|
49450
50276
|
const uvs = pattern.getUVs("0,0," + width + "," + height);
|
|
49451
50277
|
this.setBatcher("quad");
|
|
50278
|
+
const shader = this.customShader;
|
|
50279
|
+
if (shader != null) {
|
|
50280
|
+
this.currentBatcher.useShader(shader);
|
|
50281
|
+
shader._prepareTextures?.(this.currentBatcher);
|
|
50282
|
+
}
|
|
49452
50283
|
this.currentBatcher.addQuad(
|
|
49453
50284
|
pattern,
|
|
49454
50285
|
x,
|
|
@@ -56056,7 +56887,7 @@ var Trigger = class extends Renderable {
|
|
|
56056
56887
|
};
|
|
56057
56888
|
|
|
56058
56889
|
// src/version.ts
|
|
56059
|
-
var version = "20.
|
|
56890
|
+
var version = "20.10.1";
|
|
56060
56891
|
|
|
56061
56892
|
// src/system/bootstrap.ts
|
|
56062
56893
|
var initialized = false;
|
|
@@ -58499,6 +59330,8 @@ function gpuBlendState(mode, premultipliedAlpha) {
|
|
|
58499
59330
|
return typeof component === "undefined" ? void 0 : { color: component, alpha: component };
|
|
58500
59331
|
}
|
|
58501
59332
|
var MAX_QUAD_TEXTURES = 8;
|
|
59333
|
+
var MAX_WARM_IN_FLIGHT = 16;
|
|
59334
|
+
var MISS_KEYS_KEPT = 64;
|
|
58502
59335
|
var DEPTH_STENCIL_FORMAT = "depth24plus-stencil8";
|
|
58503
59336
|
var STENCIL_STATES = {
|
|
58504
59337
|
none: {
|
|
@@ -58575,6 +59408,16 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58575
59408
|
this.sampleCount = 1;
|
|
58576
59409
|
this.pipelines = /* @__PURE__ */ new Map();
|
|
58577
59410
|
this.vertexLayouts = /* @__PURE__ */ new Map();
|
|
59411
|
+
this.inFrame = false;
|
|
59412
|
+
this.inFrameMisses = 0;
|
|
59413
|
+
this.inFrameMissKeys = [];
|
|
59414
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
59415
|
+
this.collecting = void 0;
|
|
59416
|
+
this.warmQueue = [];
|
|
59417
|
+
this.warmInFlight = 0;
|
|
59418
|
+
this.touchQueue = /* @__PURE__ */ new Map();
|
|
59419
|
+
this.scratchTargets = /* @__PURE__ */ new Map();
|
|
59420
|
+
this.generation = 0;
|
|
58578
59421
|
this.frameLayout = device.createBindGroupLayout({
|
|
58579
59422
|
label: "melonJS frame globals layout",
|
|
58580
59423
|
entries: [
|
|
@@ -58800,24 +59643,33 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58800
59643
|
);
|
|
58801
59644
|
}
|
|
58802
59645
|
/**
|
|
58803
|
-
*
|
|
58804
|
-
*
|
|
59646
|
+
* The cache key for a pipeline state tuple.
|
|
59647
|
+
*
|
|
59648
|
+
* The ONE place a key is composed. A draw looks its pipeline up through
|
|
59649
|
+
* {@link WebGPUPipelineCache#get} and a warm-up builds one ahead of time
|
|
59650
|
+
* through {@link WebGPUPipelineCache#warm}, and both come here, so the two
|
|
59651
|
+
* cannot drift into building the same pipeline under two names: a warm-up
|
|
59652
|
+
* whose key differs from the draw's by one character compiles a pipeline
|
|
59653
|
+
* nothing will ever ask for, and the draw still compiles its own.
|
|
59654
|
+
*
|
|
59655
|
+
* The colour format and sample count default to the cache's CURRENT ones,
|
|
59656
|
+
* which the renderer rewrites per render target. A warm-up runs outside
|
|
59657
|
+
* any pass, where those describe whatever was bound last, so it passes
|
|
59658
|
+
* the target it is warming for explicitly.
|
|
59659
|
+
* @param {string} shaderKey - "quad" | "primitive" | "clear" | a registered family
|
|
58805
59660
|
* @param {string} topology - portable topology name ("triangle-list", …)
|
|
58806
59661
|
* @param {string} blendMode - blend mode (normalized internally)
|
|
58807
59662
|
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
58808
59663
|
* @param {string} [stencilMode="none"] - "none" | "write" | "test" | "tag" | "mark"
|
|
58809
|
-
* @param {
|
|
58810
|
-
*
|
|
58811
|
-
*
|
|
58812
|
-
*
|
|
58813
|
-
* face-culling axes. Omitted by the whole 2D tier, whose keys and
|
|
58814
|
-
* descriptors stay byte-identical to a build without a mesh path.
|
|
58815
|
-
* @returns {GPURenderPipeline} the pipeline
|
|
59664
|
+
* @param {object} [meshState] - the mesh pass state, see {@link WebGPUPipelineCache#get}
|
|
59665
|
+
* @param {string} [format] - colour target format, the current one by default
|
|
59666
|
+
* @param {number} [sampleCount] - pass sample count, the current one by default
|
|
59667
|
+
* @returns {string} the key
|
|
58816
59668
|
*/
|
|
58817
|
-
|
|
59669
|
+
keyFor(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode = "none", meshState, format = this.format, sampleCount = this.sampleCount) {
|
|
58818
59670
|
const blend = normalizeBlendMode(blendMode);
|
|
58819
|
-
const pma = premultipliedAlpha !== false;
|
|
58820
|
-
let key = `${shaderKey}|${topology}|${blend}|${pma ? 1 : 0}|${stencilMode}|${
|
|
59671
|
+
const pma = blend === "none" || premultipliedAlpha !== false;
|
|
59672
|
+
let key = `${shaderKey}|${topology}|${blend}|${pma ? 1 : 0}|${stencilMode}|${format}|${sampleCount}`;
|
|
58821
59673
|
if (meshState) {
|
|
58822
59674
|
key += `|mesh:${meshState.cullMode}:${meshState.frontFace}`;
|
|
58823
59675
|
if (meshState.depthWrite === false) {
|
|
@@ -58830,32 +59682,281 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58830
59682
|
key += "|fog";
|
|
58831
59683
|
}
|
|
58832
59684
|
}
|
|
58833
|
-
|
|
59685
|
+
return key;
|
|
59686
|
+
}
|
|
59687
|
+
/**
|
|
59688
|
+
* Fetch (building on first use) the pipeline for the given state tuple.
|
|
59689
|
+
* @param {string} shaderKey - "quad" | "primitive" | "clear"
|
|
59690
|
+
* @param {string} topology - portable topology name ("triangle-list", …)
|
|
59691
|
+
* @param {string} blendMode - blend mode (normalized internally)
|
|
59692
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59693
|
+
* @param {string} [stencilMode="none"] - "none" | "write" | "test" | "tag" | "mark"
|
|
59694
|
+
* @param {{cullMode: string, frontFace: string, depthWrite?: boolean, depthTest?: boolean, fog?: boolean}} [meshState] - mesh pass
|
|
59695
|
+
* state: its presence switches the depth half of the attachment on —
|
|
59696
|
+
* depth writes enabled, "less-equal" testing (the GL mesh mode's
|
|
59697
|
+
* LEQUAL, keeping coplanar geometry stable) — and sets the per-mesh
|
|
59698
|
+
* face-culling axes. Omitted by the whole 2D tier, whose keys and
|
|
59699
|
+
* descriptors stay byte-identical to a build without a mesh path.
|
|
59700
|
+
* @returns {GPURenderPipeline} the pipeline
|
|
59701
|
+
*/
|
|
59702
|
+
get(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode = "none", meshState) {
|
|
59703
|
+
const key = this.keyFor(
|
|
59704
|
+
shaderKey,
|
|
59705
|
+
topology,
|
|
59706
|
+
blendMode,
|
|
59707
|
+
premultipliedAlpha,
|
|
59708
|
+
stencilMode,
|
|
59709
|
+
meshState
|
|
59710
|
+
);
|
|
58834
59711
|
let pipeline = this.pipelines.get(key);
|
|
58835
59712
|
if (typeof pipeline === "undefined") {
|
|
58836
|
-
const stencil = STENCIL_STATES[stencilMode] ?? STENCIL_STATES.none;
|
|
58837
|
-
const vertexLayout = this.vertexLayouts.get(
|
|
58838
|
-
this.vertexLayoutAliases.get(shaderKey) ?? shaderKey
|
|
58839
|
-
);
|
|
58840
|
-
const primitive = {
|
|
58841
|
-
topology,
|
|
58842
|
-
stripIndexFormat: topology.endsWith("-strip") ? "uint32" : void 0
|
|
58843
|
-
};
|
|
58844
|
-
if (meshState) {
|
|
58845
|
-
primitive.cullMode = meshState.cullMode;
|
|
58846
|
-
primitive.frontFace = meshState.frontFace;
|
|
58847
|
-
}
|
|
58848
59713
|
pipeline = this.device.createRenderPipeline(
|
|
58849
|
-
this._descriptor(
|
|
58850
|
-
|
|
59714
|
+
this._descriptor(
|
|
59715
|
+
key,
|
|
59716
|
+
shaderKey,
|
|
59717
|
+
topology,
|
|
59718
|
+
blendMode,
|
|
59719
|
+
premultipliedAlpha,
|
|
59720
|
+
stencilMode,
|
|
58851
59721
|
meshState,
|
|
58852
|
-
|
|
58853
|
-
|
|
59722
|
+
this.format,
|
|
59723
|
+
this.sampleCount
|
|
59724
|
+
)
|
|
58854
59725
|
);
|
|
58855
59726
|
this.pipelines.set(key, pipeline);
|
|
59727
|
+
if (this.inFrame === true) {
|
|
59728
|
+
this.inFrameMisses++;
|
|
59729
|
+
this.inFrameMissKeys.push(key);
|
|
59730
|
+
if (this.inFrameMissKeys.length > MISS_KEYS_KEPT) {
|
|
59731
|
+
this.inFrameMissKeys.shift();
|
|
59732
|
+
}
|
|
59733
|
+
}
|
|
58856
59734
|
}
|
|
58857
59735
|
return pipeline;
|
|
58858
59736
|
}
|
|
59737
|
+
/**
|
|
59738
|
+
* Build a pipeline ahead of the draw that will need it, without blocking.
|
|
59739
|
+
*
|
|
59740
|
+
* Takes the same tuple as {@link WebGPUPipelineCache#get}, plus the colour
|
|
59741
|
+
* format and sample count of the target the draw will land on, since a
|
|
59742
|
+
* warm-up runs outside any pass. Compiles through
|
|
59743
|
+
* `createRenderPipelineAsync` where the device has it, so the work
|
|
59744
|
+
* happens off the frame, and files the result under the same key `get`
|
|
59745
|
+
* will look up. Asking twice for one pipeline compiles it once.
|
|
59746
|
+
*
|
|
59747
|
+
* Never rejects. A pipeline that fails to build is left for the draw to
|
|
59748
|
+
* build and report, exactly as it would have without the warm-up.
|
|
59749
|
+
* @param {string} shaderKey - the family, already registered
|
|
59750
|
+
* @param {string} topology - portable topology name
|
|
59751
|
+
* @param {string} blendMode - blend mode
|
|
59752
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59753
|
+
* @param {string} stencilMode - stencil mode
|
|
59754
|
+
* @param {object|undefined} meshState - mesh pass state, read NOW: a later
|
|
59755
|
+
* change to the object does not reach the pipeline being built
|
|
59756
|
+
* @param {string} format - colour target format the draw will use
|
|
59757
|
+
* @param {number} sampleCount - sample count the draw will use
|
|
59758
|
+
* @returns {Promise<void>} settles once the pipeline is in the cache, or
|
|
59759
|
+
* has failed to get there
|
|
59760
|
+
*/
|
|
59761
|
+
warm(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode, meshState, format, sampleCount) {
|
|
59762
|
+
const key = this.keyFor(
|
|
59763
|
+
shaderKey,
|
|
59764
|
+
topology,
|
|
59765
|
+
blendMode,
|
|
59766
|
+
premultipliedAlpha,
|
|
59767
|
+
stencilMode,
|
|
59768
|
+
meshState,
|
|
59769
|
+
format,
|
|
59770
|
+
sampleCount
|
|
59771
|
+
);
|
|
59772
|
+
if (this.pipelines.has(key)) {
|
|
59773
|
+
return Promise.resolve();
|
|
59774
|
+
}
|
|
59775
|
+
const inFlight = this.pending.get(key);
|
|
59776
|
+
if (inFlight !== void 0) {
|
|
59777
|
+
this.collecting?.push(inFlight);
|
|
59778
|
+
return inFlight;
|
|
59779
|
+
}
|
|
59780
|
+
const descriptor = this._descriptor(
|
|
59781
|
+
key,
|
|
59782
|
+
shaderKey,
|
|
59783
|
+
topology,
|
|
59784
|
+
blendMode,
|
|
59785
|
+
premultipliedAlpha,
|
|
59786
|
+
stencilMode,
|
|
59787
|
+
meshState,
|
|
59788
|
+
format,
|
|
59789
|
+
sampleCount
|
|
59790
|
+
);
|
|
59791
|
+
const generation = this.generation;
|
|
59792
|
+
const promise = new Promise((resolve) => {
|
|
59793
|
+
this.warmQueue.push({
|
|
59794
|
+
run: () => {
|
|
59795
|
+
if (this.pipelines.has(key)) {
|
|
59796
|
+
return void 0;
|
|
59797
|
+
}
|
|
59798
|
+
return typeof this.device.createRenderPipelineAsync === "function" ? this.device.createRenderPipelineAsync(descriptor) : this.device.createRenderPipeline(descriptor);
|
|
59799
|
+
},
|
|
59800
|
+
resolve: (pipeline) => {
|
|
59801
|
+
this.pending.delete(key);
|
|
59802
|
+
if (pipeline !== void 0 && generation === this.generation && !this.pipelines.has(key)) {
|
|
59803
|
+
this.pipelines.set(key, pipeline);
|
|
59804
|
+
this._touch(pipeline, format, sampleCount);
|
|
59805
|
+
}
|
|
59806
|
+
resolve();
|
|
59807
|
+
}
|
|
59808
|
+
});
|
|
59809
|
+
});
|
|
59810
|
+
this.pending.set(key, promise);
|
|
59811
|
+
this.collecting?.push(promise);
|
|
59812
|
+
this._pumpWarmQueue();
|
|
59813
|
+
return promise;
|
|
59814
|
+
}
|
|
59815
|
+
/**
|
|
59816
|
+
* Run `ask` and hand back the warm-ups it asked for that are not built
|
|
59817
|
+
* yet: the ones it started, and the ones already compiling that it asked
|
|
59818
|
+
* for again. A pipeline already in the cache is not waited on, so a scene
|
|
59819
|
+
* entered a second time hands back nothing.
|
|
59820
|
+
* @param {Function} ask - asks for warm-ups, through {@link WebGPUPipelineCache#warm}
|
|
59821
|
+
* @returns {Promise<void>[]} one promise per pipeline still on its way
|
|
59822
|
+
*/
|
|
59823
|
+
collect(ask) {
|
|
59824
|
+
const outer = this.collecting;
|
|
59825
|
+
const asked = [];
|
|
59826
|
+
this.collecting = asked;
|
|
59827
|
+
try {
|
|
59828
|
+
ask();
|
|
59829
|
+
} finally {
|
|
59830
|
+
this.collecting = outer;
|
|
59831
|
+
}
|
|
59832
|
+
return asked;
|
|
59833
|
+
}
|
|
59834
|
+
/**
|
|
59835
|
+
* Start queued warm-ups until {@link MAX_WARM_IN_FLIGHT} are compiling.
|
|
59836
|
+
* @ignore
|
|
59837
|
+
* @internal
|
|
59838
|
+
*/
|
|
59839
|
+
_pumpWarmQueue() {
|
|
59840
|
+
while (this.warmInFlight < MAX_WARM_IN_FLIGHT && this.warmQueue.length > 0) {
|
|
59841
|
+
const job = this.warmQueue.shift();
|
|
59842
|
+
this.warmInFlight++;
|
|
59843
|
+
Promise.resolve().then(job.run).then(job.resolve, () => {
|
|
59844
|
+
job.resolve(void 0);
|
|
59845
|
+
}).finally(() => {
|
|
59846
|
+
this.warmInFlight--;
|
|
59847
|
+
this._pumpWarmQueue();
|
|
59848
|
+
});
|
|
59849
|
+
}
|
|
59850
|
+
}
|
|
59851
|
+
/**
|
|
59852
|
+
* Bind a freshly warmed pipeline once, in a pass of its own, so it is
|
|
59853
|
+
* compiled now rather than at the first draw that uses it.
|
|
59854
|
+
*
|
|
59855
|
+
* Some implementations accept `createRenderPipelineAsync` and still defer
|
|
59856
|
+
* the native compile to the first `setPipeline`, which puts it right back
|
|
59857
|
+
* in the frame the warm-up was meant to keep it out of. Setting it on a
|
|
59858
|
+
* throwaway 1×1 pass makes that first use happen here, while nothing is
|
|
59859
|
+
* on screen waiting for it. Where the compile already happened this is
|
|
59860
|
+
* one cheap bind.
|
|
59861
|
+
* @param {GPURenderPipeline} pipeline - the pipeline to bind
|
|
59862
|
+
* @param {string} format - its colour format
|
|
59863
|
+
* @param {number} sampleCount - its sample count
|
|
59864
|
+
* @ignore
|
|
59865
|
+
* @internal
|
|
59866
|
+
*/
|
|
59867
|
+
_touch(pipeline, format, sampleCount) {
|
|
59868
|
+
const target = `${format}|${sampleCount}`;
|
|
59869
|
+
let batch = this.touchQueue.get(target);
|
|
59870
|
+
if (batch === void 0) {
|
|
59871
|
+
batch = [];
|
|
59872
|
+
this.touchQueue.set(target, batch);
|
|
59873
|
+
}
|
|
59874
|
+
batch.push(pipeline);
|
|
59875
|
+
if (this.touchQueue.size === 1 && batch.length === 1) {
|
|
59876
|
+
queueMicrotask(() => {
|
|
59877
|
+
this._flushTouches();
|
|
59878
|
+
});
|
|
59879
|
+
}
|
|
59880
|
+
}
|
|
59881
|
+
/**
|
|
59882
|
+
* Record and submit the passes queued by {@link WebGPUPipelineCache#_touch}.
|
|
59883
|
+
* @ignore
|
|
59884
|
+
* @internal
|
|
59885
|
+
*/
|
|
59886
|
+
_flushTouches() {
|
|
59887
|
+
const device = this.device;
|
|
59888
|
+
const batches = [...this.touchQueue];
|
|
59889
|
+
this.touchQueue.clear();
|
|
59890
|
+
if (typeof device.createCommandEncoder !== "function") {
|
|
59891
|
+
return;
|
|
59892
|
+
}
|
|
59893
|
+
try {
|
|
59894
|
+
const encoder = device.createCommandEncoder({
|
|
59895
|
+
label: "melonJS pipeline warm-up"
|
|
59896
|
+
});
|
|
59897
|
+
for (const [target, pipelines] of batches) {
|
|
59898
|
+
const [format, samples] = target.split("|");
|
|
59899
|
+
const scratch = this._scratchTarget(format, Number(samples));
|
|
59900
|
+
const pass = encoder.beginRenderPass({
|
|
59901
|
+
label: "melonJS pipeline warm-up",
|
|
59902
|
+
colorAttachments: [
|
|
59903
|
+
{ view: scratch.colorView, loadOp: "clear", storeOp: "discard" }
|
|
59904
|
+
],
|
|
59905
|
+
depthStencilAttachment: {
|
|
59906
|
+
view: scratch.depthView,
|
|
59907
|
+
depthClearValue: 1,
|
|
59908
|
+
depthLoadOp: "clear",
|
|
59909
|
+
depthStoreOp: "discard",
|
|
59910
|
+
stencilLoadOp: "clear",
|
|
59911
|
+
stencilStoreOp: "discard"
|
|
59912
|
+
}
|
|
59913
|
+
});
|
|
59914
|
+
for (const pipeline of pipelines) {
|
|
59915
|
+
pass.setPipeline(pipeline);
|
|
59916
|
+
}
|
|
59917
|
+
pass.end();
|
|
59918
|
+
}
|
|
59919
|
+
device.queue.submit([encoder.finish()]);
|
|
59920
|
+
} catch {
|
|
59921
|
+
}
|
|
59922
|
+
}
|
|
59923
|
+
/**
|
|
59924
|
+
* The 1×1 colour + depth-stencil pair a warm-up pass binds for one
|
|
59925
|
+
* target shape, built once per shape.
|
|
59926
|
+
* @param {string} format - colour format
|
|
59927
|
+
* @param {number} sampleCount - sample count
|
|
59928
|
+
* @returns {{colorView: GPUTextureView, depthView: GPUTextureView}} the views
|
|
59929
|
+
* @ignore
|
|
59930
|
+
* @internal
|
|
59931
|
+
*/
|
|
59932
|
+
_scratchTarget(format, sampleCount) {
|
|
59933
|
+
const key = `${format}|${sampleCount}`;
|
|
59934
|
+
let scratch = this.scratchTargets.get(key);
|
|
59935
|
+
if (scratch === void 0) {
|
|
59936
|
+
const color = this.device.createTexture({
|
|
59937
|
+
label: "melonJS warm-up target",
|
|
59938
|
+
size: [1, 1],
|
|
59939
|
+
format,
|
|
59940
|
+
sampleCount,
|
|
59941
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
59942
|
+
});
|
|
59943
|
+
const depth = this.device.createTexture({
|
|
59944
|
+
label: "melonJS warm-up depth",
|
|
59945
|
+
size: [1, 1],
|
|
59946
|
+
format: DEPTH_STENCIL_FORMAT,
|
|
59947
|
+
sampleCount,
|
|
59948
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
59949
|
+
});
|
|
59950
|
+
scratch = {
|
|
59951
|
+
color,
|
|
59952
|
+
depth,
|
|
59953
|
+
colorView: color.createView(),
|
|
59954
|
+
depthView: depth.createView()
|
|
59955
|
+
};
|
|
59956
|
+
this.scratchTargets.set(key, scratch);
|
|
59957
|
+
}
|
|
59958
|
+
return scratch;
|
|
59959
|
+
}
|
|
58859
59960
|
/**
|
|
58860
59961
|
* Assemble a pipeline descriptor.
|
|
58861
59962
|
*
|
|
@@ -58866,17 +59967,33 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58866
59967
|
* silently builds a second one under a key nothing will hit again.
|
|
58867
59968
|
* @param {string} key - the cache key, used as the label
|
|
58868
59969
|
* @param {string} shaderKey - the module family
|
|
58869
|
-
* @param {string}
|
|
58870
|
-
* @param {
|
|
58871
|
-
* @param {
|
|
58872
|
-
* @param {
|
|
58873
|
-
* @param {object}
|
|
59970
|
+
* @param {string} topology - portable topology name
|
|
59971
|
+
* @param {string} blendMode - blend mode (normalized here)
|
|
59972
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59973
|
+
* @param {string} stencilMode - stencil mode
|
|
59974
|
+
* @param {object|undefined} meshState - mesh pass state
|
|
59975
|
+
* @param {string} format - colour target format
|
|
59976
|
+
* @param {number} sampleCount - pass sample count
|
|
58874
59977
|
* @returns {GPURenderPipelineDescriptor} the descriptor
|
|
58875
59978
|
* @ignore
|
|
58876
59979
|
* @internal
|
|
58877
59980
|
*/
|
|
58878
|
-
_descriptor(key, shaderKey,
|
|
58879
|
-
const
|
|
59981
|
+
_descriptor(key, shaderKey, topology, blendMode, premultipliedAlpha, stencilMode, meshState, format, sampleCount) {
|
|
59982
|
+
const blend = normalizeBlendMode(blendMode);
|
|
59983
|
+
const pma = premultipliedAlpha !== false;
|
|
59984
|
+
const stencil = STENCIL_STATES[stencilMode] ?? STENCIL_STATES.none;
|
|
59985
|
+
const vertexLayout = this.vertexLayouts.get(
|
|
59986
|
+
this.vertexLayoutAliases.get(shaderKey) ?? shaderKey
|
|
59987
|
+
);
|
|
59988
|
+
const primitive = {
|
|
59989
|
+
topology,
|
|
59990
|
+
stripIndexFormat: topology.endsWith("-strip") ? "uint32" : void 0
|
|
59991
|
+
};
|
|
59992
|
+
if (meshState) {
|
|
59993
|
+
primitive.cullMode = meshState.cullMode;
|
|
59994
|
+
primitive.frontFace = meshState.frontFace;
|
|
59995
|
+
}
|
|
59996
|
+
const fogFlag = meshState?.fog === true ? 1 : 0;
|
|
58880
59997
|
return {
|
|
58881
59998
|
label: `melonJS ${key}`,
|
|
58882
59999
|
layout: this.pipelineLayouts[shaderKey],
|
|
@@ -58892,7 +60009,7 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58892
60009
|
...meshState ? { constants: { enable_fog: fogFlag } } : {},
|
|
58893
60010
|
targets: [
|
|
58894
60011
|
{
|
|
58895
|
-
format
|
|
60012
|
+
format,
|
|
58896
60013
|
blend: shaderKey === "clear" ? void 0 : gpuBlendState(blend, pma),
|
|
58897
60014
|
writeMask: stencil.colorWriteMask
|
|
58898
60015
|
}
|
|
@@ -58915,7 +60032,7 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58915
60032
|
stencilReadMask: stencil.readMask,
|
|
58916
60033
|
stencilWriteMask: stencil.writeMask
|
|
58917
60034
|
},
|
|
58918
|
-
multisample: { count:
|
|
60035
|
+
multisample: { count: sampleCount }
|
|
58919
60036
|
};
|
|
58920
60037
|
}
|
|
58921
60038
|
/**
|
|
@@ -58924,6 +60041,17 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58924
60041
|
*/
|
|
58925
60042
|
clear() {
|
|
58926
60043
|
this.pipelines.clear();
|
|
60044
|
+
this.generation++;
|
|
60045
|
+
for (const job of this.warmQueue.splice(0)) {
|
|
60046
|
+
job.resolve(void 0);
|
|
60047
|
+
}
|
|
60048
|
+
this.pending.clear();
|
|
60049
|
+
this.touchQueue.clear();
|
|
60050
|
+
for (const scratch of this.scratchTargets.values()) {
|
|
60051
|
+
scratch.color.destroy?.();
|
|
60052
|
+
scratch.depth.destroy?.();
|
|
60053
|
+
}
|
|
60054
|
+
this.scratchTargets.clear();
|
|
58927
60055
|
}
|
|
58928
60056
|
};
|
|
58929
60057
|
|
|
@@ -59049,7 +60177,13 @@ var WebGPUQuadBatcher = class extends WebGPUBatcher {
|
|
|
59049
60177
|
Math.min(u0, u1),
|
|
59050
60178
|
Math.min(v0, v1)
|
|
59051
60179
|
);
|
|
59052
|
-
|
|
60180
|
+
const scale2 = effect._vertexScale;
|
|
60181
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
60182
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
60183
|
+
this.pushQuadVertices(q[0], q[1], q[2], q[3], u0, v0, u1, v1, tint, 0);
|
|
60184
|
+
} else {
|
|
60185
|
+
this.pushQuadVertices(x, y, w, h, u0, v0, u1, v1, tint, 0);
|
|
60186
|
+
}
|
|
59053
60187
|
this.flush();
|
|
59054
60188
|
return;
|
|
59055
60189
|
}
|
|
@@ -60708,7 +61842,11 @@ function uploadCompressedTexture(device, texture, image, metrics) {
|
|
|
60708
61842
|
bytesPerRow: blocksPerRow * metrics.bytes,
|
|
60709
61843
|
rowsPerImage: rows
|
|
60710
61844
|
},
|
|
60711
|
-
|
|
61845
|
+
// whole blocks: a level smaller than one block (the 2×2 and 1×1
|
|
61846
|
+
// tail of a 4×4-block chain) is stored padded to a full block,
|
|
61847
|
+
// and a copy of its logical size fails validation, leaving the
|
|
61848
|
+
// level empty
|
|
61849
|
+
[blocksPerRow * metrics.blockW, rows * metrics.blockH]
|
|
60712
61850
|
);
|
|
60713
61851
|
}
|
|
60714
61852
|
}
|
|
@@ -60978,6 +62116,71 @@ var WebGPUTextureStore = class extends TextureStore {
|
|
|
60978
62116
|
this.lastRecord = record;
|
|
60979
62117
|
return this.bindGroupFor(record, texture, wrap, options.mipmaps === true);
|
|
60980
62118
|
}
|
|
62119
|
+
/**
|
|
62120
|
+
* Build the mip-chain blitter, once. Split out of
|
|
62121
|
+
* {@link WebGPUTextureStore#generateMipmaps} so the warm-up can build it
|
|
62122
|
+
* before the first mipmapped texture is uploaded, which is otherwise in
|
|
62123
|
+
* the middle of the frame that first draws a mesh.
|
|
62124
|
+
* @ignore
|
|
62125
|
+
* @internal
|
|
62126
|
+
*/
|
|
62127
|
+
ensureMipPipeline() {
|
|
62128
|
+
if (typeof this.mipPipeline !== "undefined") {
|
|
62129
|
+
return;
|
|
62130
|
+
}
|
|
62131
|
+
const device = this.device;
|
|
62132
|
+
const module = device.createShaderModule({
|
|
62133
|
+
label: "melonJS mip blit",
|
|
62134
|
+
code: mipblit_default
|
|
62135
|
+
});
|
|
62136
|
+
this.mipLayout = device.createBindGroupLayout({
|
|
62137
|
+
label: "melonJS mip blit layout",
|
|
62138
|
+
entries: [
|
|
62139
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
62140
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
|
|
62141
|
+
]
|
|
62142
|
+
});
|
|
62143
|
+
this.mipPipeline = device.createRenderPipeline({
|
|
62144
|
+
label: "melonJS mip blit",
|
|
62145
|
+
layout: device.createPipelineLayout({
|
|
62146
|
+
bindGroupLayouts: [this.mipLayout]
|
|
62147
|
+
}),
|
|
62148
|
+
vertex: { module, entryPoint: "vertex_main" },
|
|
62149
|
+
fragment: {
|
|
62150
|
+
module,
|
|
62151
|
+
entryPoint: "fragment_main",
|
|
62152
|
+
targets: [{ format: "rgba8unorm" }]
|
|
62153
|
+
},
|
|
62154
|
+
primitive: { topology: "triangle-list" }
|
|
62155
|
+
});
|
|
62156
|
+
this.mipSampler = device.createSampler({
|
|
62157
|
+
label: "melonJS mip blit sampler",
|
|
62158
|
+
magFilter: "linear",
|
|
62159
|
+
minFilter: "linear"
|
|
62160
|
+
});
|
|
62161
|
+
}
|
|
62162
|
+
/**
|
|
62163
|
+
* Build the mip-chain blitter and run it once, on a throwaway 2×2
|
|
62164
|
+
* texture, so its native compile has happened before the first real
|
|
62165
|
+
* texture needs it. Creating a pipeline is not enough on every
|
|
62166
|
+
* implementation: some defer the compile to first use, so this uses it.
|
|
62167
|
+
* @ignore
|
|
62168
|
+
* @internal
|
|
62169
|
+
*/
|
|
62170
|
+
warmMipmaps() {
|
|
62171
|
+
if (typeof this.mipPipeline !== "undefined") {
|
|
62172
|
+
return;
|
|
62173
|
+
}
|
|
62174
|
+
const texture = this.device.createTexture({
|
|
62175
|
+
label: "melonJS mip warm-up",
|
|
62176
|
+
size: [2, 2],
|
|
62177
|
+
format: "rgba8unorm",
|
|
62178
|
+
mipLevelCount: 2,
|
|
62179
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
|
|
62180
|
+
});
|
|
62181
|
+
this.generateMipmaps({ texture, mipLevelCount: 2 });
|
|
62182
|
+
texture.destroy();
|
|
62183
|
+
}
|
|
60981
62184
|
/**
|
|
60982
62185
|
* Build the full mip chain of a freshly-uploaded record: one blit pass
|
|
60983
62186
|
* per level, each rendering the previous level with linear filtering.
|
|
@@ -60990,37 +62193,7 @@ var WebGPUTextureStore = class extends TextureStore {
|
|
|
60990
62193
|
*/
|
|
60991
62194
|
generateMipmaps(record) {
|
|
60992
62195
|
const device = this.device;
|
|
60993
|
-
|
|
60994
|
-
const module = device.createShaderModule({
|
|
60995
|
-
label: "melonJS mip blit",
|
|
60996
|
-
code: mipblit_default
|
|
60997
|
-
});
|
|
60998
|
-
this.mipLayout = device.createBindGroupLayout({
|
|
60999
|
-
label: "melonJS mip blit layout",
|
|
61000
|
-
entries: [
|
|
61001
|
-
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
61002
|
-
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
|
|
61003
|
-
]
|
|
61004
|
-
});
|
|
61005
|
-
this.mipPipeline = device.createRenderPipeline({
|
|
61006
|
-
label: "melonJS mip blit",
|
|
61007
|
-
layout: device.createPipelineLayout({
|
|
61008
|
-
bindGroupLayouts: [this.mipLayout]
|
|
61009
|
-
}),
|
|
61010
|
-
vertex: { module, entryPoint: "vertex_main" },
|
|
61011
|
-
fragment: {
|
|
61012
|
-
module,
|
|
61013
|
-
entryPoint: "fragment_main",
|
|
61014
|
-
targets: [{ format: "rgba8unorm" }]
|
|
61015
|
-
},
|
|
61016
|
-
primitive: { topology: "triangle-list" }
|
|
61017
|
-
});
|
|
61018
|
-
this.mipSampler = device.createSampler({
|
|
61019
|
-
label: "melonJS mip blit sampler",
|
|
61020
|
-
magFilter: "linear",
|
|
61021
|
-
minFilter: "linear"
|
|
61022
|
-
});
|
|
61023
|
-
}
|
|
62196
|
+
this.ensureMipPipeline();
|
|
61024
62197
|
const encoder = device.createCommandEncoder({ label: "melonJS mipgen" });
|
|
61025
62198
|
for (let level2 = 1; level2 < record.mipLevelCount; level2++) {
|
|
61026
62199
|
const bindGroup = device.createBindGroup({
|
|
@@ -61541,6 +62714,7 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61541
62714
|
this.uniformRing.reset();
|
|
61542
62715
|
this.effectUniformArena.reset();
|
|
61543
62716
|
this.frameId++;
|
|
62717
|
+
this.pipelineCache.inFrame = true;
|
|
61544
62718
|
this.currentRenderTarget = null;
|
|
61545
62719
|
this.pendingColorClear = false;
|
|
61546
62720
|
this.effectPassDepth = 0;
|
|
@@ -61935,6 +63109,448 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61935
63109
|
);
|
|
61936
63110
|
return frame;
|
|
61937
63111
|
}
|
|
63112
|
+
/**
|
|
63113
|
+
* The post-effect render-target pool, created on first use.
|
|
63114
|
+
* @returns {RenderTargetPool} the pool
|
|
63115
|
+
* @ignore
|
|
63116
|
+
* @internal
|
|
63117
|
+
*/
|
|
63118
|
+
_postEffectPool() {
|
|
63119
|
+
this._renderTargetPool ??= new RenderTargetPool(
|
|
63120
|
+
(w, h, isCapture, index) => {
|
|
63121
|
+
return new WebGPURenderTarget(this, w, h, {
|
|
63122
|
+
sampleCount: isCapture === true ? this.canvasSampleCount : 1,
|
|
63123
|
+
format: index < 2 ? this._colorFormat : this.preferredFormat
|
|
63124
|
+
});
|
|
63125
|
+
}
|
|
63126
|
+
);
|
|
63127
|
+
return this._renderTargetPool;
|
|
63128
|
+
}
|
|
63129
|
+
/**
|
|
63130
|
+
* The pipeline-cache family of an effect's WGSL realization, building its
|
|
63131
|
+
* module and layouts first if this device has not seen it yet.
|
|
63132
|
+
* @param {ShaderEffect} effect - an effect with a valid WGSL realization
|
|
63133
|
+
* @returns {string} the family key
|
|
63134
|
+
* @ignore
|
|
63135
|
+
* @internal
|
|
63136
|
+
*/
|
|
63137
|
+
_effectFamily(effect) {
|
|
63138
|
+
const realization = effect.wgslRealization;
|
|
63139
|
+
if (realization.gpu === null || realization.gpu.epoch !== this.pipelineCache.epoch) {
|
|
63140
|
+
buildEffectGPU(this, effect, realization);
|
|
63141
|
+
}
|
|
63142
|
+
return realization.gpu.key;
|
|
63143
|
+
}
|
|
63144
|
+
/**
|
|
63145
|
+
* Run `warm` and hand back the pipelines it asked for that are still
|
|
63146
|
+
* compiling, which on this backend is every one not already cached.
|
|
63147
|
+
* @param {Function} warm - issues the warm-ups
|
|
63148
|
+
* @returns {Promise<void>[]} one promise per pipeline on its way
|
|
63149
|
+
* @override
|
|
63150
|
+
* @ignore
|
|
63151
|
+
* @internal
|
|
63152
|
+
*/
|
|
63153
|
+
_collectWarms(warm) {
|
|
63154
|
+
if (this.pipelineCache == null) {
|
|
63155
|
+
return super._collectWarms(warm);
|
|
63156
|
+
}
|
|
63157
|
+
return this.pipelineCache.collect(warm);
|
|
63158
|
+
}
|
|
63159
|
+
/**
|
|
63160
|
+
* Build, off the frame, every pipeline a renderable's post effects can
|
|
63161
|
+
* draw with, and the render targets its chain will composite through.
|
|
63162
|
+
*
|
|
63163
|
+
* Which pipeline an effect draws with depends on where it sits: an
|
|
63164
|
+
* effect alone on a sprite draws the sprite itself (blended with the
|
|
63165
|
+
* sprite's own mode), one alone on a mesh is spliced into the mesh
|
|
63166
|
+
* shader, and one in a chain is a full-screen blit into either the next
|
|
63167
|
+
* offscreen target or the one below. Its colour format depends on that
|
|
63168
|
+
* target too, and under `hdr` the camera's targets are half-float while
|
|
63169
|
+
* the canvas is not. So this warms every position the effect can reach
|
|
63170
|
+
* with the effects ATTACHED, enabled or not, since turning one on moves
|
|
63171
|
+
* the others: enabling a camera's second effect sends its first one
|
|
63172
|
+
* from the canvas into the ping-pong target, a different pipeline.
|
|
63173
|
+
*
|
|
63174
|
+
* Each key is composed by the same function the draw uses, from the
|
|
63175
|
+
* renderable's own state (blend mode, culling, transparency, fog), so
|
|
63176
|
+
* what is built here is what the draw will ask for.
|
|
63177
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
63178
|
+
* @override
|
|
63179
|
+
* @ignore
|
|
63180
|
+
* @internal
|
|
63181
|
+
*/
|
|
63182
|
+
_warmPostEffects(renderable) {
|
|
63183
|
+
if (!this._canWarm()) {
|
|
63184
|
+
return;
|
|
63185
|
+
}
|
|
63186
|
+
const cache3 = this.pipelineCache;
|
|
63187
|
+
const effects = renderable.postEffects.filter((fx) => {
|
|
63188
|
+
return fx?.wgslRealization?.valid === true;
|
|
63189
|
+
});
|
|
63190
|
+
const count = effects.length;
|
|
63191
|
+
if (count === 0) {
|
|
63192
|
+
return;
|
|
63193
|
+
}
|
|
63194
|
+
const isCamera = renderable._postEffectManaged === true;
|
|
63195
|
+
const needsCapture = renderable.postEffectNeedsCapture === true;
|
|
63196
|
+
const pooled = isCamera || count > 1 || needsCapture;
|
|
63197
|
+
const canvas = [this.canvasFormat, this.canvasSampleCount];
|
|
63198
|
+
const scene = this._sceneTargets();
|
|
63199
|
+
const blit = (family, blendMode2, [format, sampleCount]) => {
|
|
63200
|
+
void cache3.warm(
|
|
63201
|
+
family,
|
|
63202
|
+
"triangle-list",
|
|
63203
|
+
blendMode2,
|
|
63204
|
+
true,
|
|
63205
|
+
"none",
|
|
63206
|
+
void 0,
|
|
63207
|
+
format,
|
|
63208
|
+
sampleCount
|
|
63209
|
+
);
|
|
63210
|
+
};
|
|
63211
|
+
if (pooled) {
|
|
63212
|
+
const pool2 = this._postEffectPool();
|
|
63213
|
+
const { width, height } = this.getCanvas();
|
|
63214
|
+
pool2.reserve(
|
|
63215
|
+
isCamera,
|
|
63216
|
+
this._postEffectCapacity(renderable, effects),
|
|
63217
|
+
width,
|
|
63218
|
+
height
|
|
63219
|
+
);
|
|
63220
|
+
}
|
|
63221
|
+
if (isCamera) {
|
|
63222
|
+
for (const fx of effects) {
|
|
63223
|
+
const family = this._effectFamily(fx);
|
|
63224
|
+
blit(family, "none", [this._colorFormat, 1]);
|
|
63225
|
+
blit(family, "none", canvas);
|
|
63226
|
+
}
|
|
63227
|
+
this._warmToneMap();
|
|
63228
|
+
return;
|
|
63229
|
+
}
|
|
63230
|
+
const blendMode = renderable.blendMode ?? "normal";
|
|
63231
|
+
const meshHost = this._hostsMeshEffect(renderable);
|
|
63232
|
+
for (let i = 0; i < count; i++) {
|
|
63233
|
+
const fx = effects[i];
|
|
63234
|
+
const family = this._effectFamily(fx);
|
|
63235
|
+
if (!needsCapture) {
|
|
63236
|
+
if (meshHost) {
|
|
63237
|
+
this._warmHostedEffect(renderable, fx, scene);
|
|
63238
|
+
} else {
|
|
63239
|
+
for (const target of scene) {
|
|
63240
|
+
blit(family, blendMode, target);
|
|
63241
|
+
}
|
|
63242
|
+
}
|
|
63243
|
+
}
|
|
63244
|
+
if (pooled) {
|
|
63245
|
+
for (const target of scene) {
|
|
63246
|
+
blit(family, blendMode, target);
|
|
63247
|
+
}
|
|
63248
|
+
if (i < count - 1) {
|
|
63249
|
+
blit(family, "none", [this.preferredFormat, 1]);
|
|
63250
|
+
}
|
|
63251
|
+
}
|
|
63252
|
+
}
|
|
63253
|
+
if (pooled && !meshHost) {
|
|
63254
|
+
blit("quad", blendMode, [this.preferredFormat, this.canvasSampleCount]);
|
|
63255
|
+
}
|
|
63256
|
+
}
|
|
63257
|
+
/**
|
|
63258
|
+
* The `[format, sampleCount]` targets an ordinary renderable can be drawn
|
|
63259
|
+
* into: the canvas, or the camera's capture target when the camera runs
|
|
63260
|
+
* an effect chain of its own. The two differ only in format, and only
|
|
63261
|
+
* under `hdr`, so this is usually one target.
|
|
63262
|
+
* @returns {Array<Array<string|number>>} the targets
|
|
63263
|
+
* @ignore
|
|
63264
|
+
* @internal
|
|
63265
|
+
*/
|
|
63266
|
+
_sceneTargets() {
|
|
63267
|
+
const canvas = [this.canvasFormat, this.canvasSampleCount];
|
|
63268
|
+
return this._colorFormat === this.canvasFormat ? [canvas] : [canvas, [this._colorFormat, this.canvasSampleCount]];
|
|
63269
|
+
}
|
|
63270
|
+
/**
|
|
63271
|
+
* Whether the warm-ups can run: a live device and a pipeline cache.
|
|
63272
|
+
* @returns {boolean} true when ready
|
|
63273
|
+
* @ignore
|
|
63274
|
+
* @internal
|
|
63275
|
+
*/
|
|
63276
|
+
_canWarm() {
|
|
63277
|
+
return this.pipelineCache != null && typeof this.device !== "undefined" && this.isContextValid !== false;
|
|
63278
|
+
}
|
|
63279
|
+
/**
|
|
63280
|
+
* The mesh batcher a mesh draws through.
|
|
63281
|
+
* @param {object} mesh - the mesh
|
|
63282
|
+
* @returns {object|undefined} the batcher
|
|
63283
|
+
* @ignore
|
|
63284
|
+
* @internal
|
|
63285
|
+
*/
|
|
63286
|
+
_meshBatcherFor(mesh) {
|
|
63287
|
+
return this.batchers.get(
|
|
63288
|
+
mesh.lit === true && this.batchers.has("litMesh") ? "litMesh" : "mesh"
|
|
63289
|
+
);
|
|
63290
|
+
}
|
|
63291
|
+
/**
|
|
63292
|
+
* Warm the pipelines a mesh will draw with when it hosts an effect: the
|
|
63293
|
+
* effect spliced into the mesh shader.
|
|
63294
|
+
* @param {object} mesh - the hosting mesh
|
|
63295
|
+
* @param {ShaderEffect} effect - the effect it hosts
|
|
63296
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63297
|
+
* @ignore
|
|
63298
|
+
* @internal
|
|
63299
|
+
*/
|
|
63300
|
+
_warmHostedEffect(mesh, effect, scene) {
|
|
63301
|
+
const realization = effect.wgslRealization;
|
|
63302
|
+
if (meshHostingBlocker(realization.builtins, realization.textures, "wgsl") !== null) {
|
|
63303
|
+
return;
|
|
63304
|
+
}
|
|
63305
|
+
const batcher = this._meshBatcherFor(mesh);
|
|
63306
|
+
if (batcher === void 0 || realization.meshModule(batcher.shaderSource?.() ?? "") === null) {
|
|
63307
|
+
return;
|
|
63308
|
+
}
|
|
63309
|
+
const previous = batcher.hostedEffect;
|
|
63310
|
+
let family;
|
|
63311
|
+
batcher.hostedEffect = effect;
|
|
63312
|
+
try {
|
|
63313
|
+
family = batcher.activeShaderKey();
|
|
63314
|
+
} finally {
|
|
63315
|
+
batcher.hostedEffect = previous;
|
|
63316
|
+
}
|
|
63317
|
+
this._warmMeshDraw(mesh, family, scene);
|
|
63318
|
+
}
|
|
63319
|
+
/**
|
|
63320
|
+
* Warm the pipelines one mesh draw resolves to, keyed on the mesh's own
|
|
63321
|
+
* culling, depth test and transparency exactly as `drawMesh` keys them:
|
|
63322
|
+
* the opaque pass, the transparent pass, or both when `transparent` is
|
|
63323
|
+
* left unset (blended whenever the mesh is faded).
|
|
63324
|
+
* @param {object} mesh - the mesh
|
|
63325
|
+
* @param {string} family - the pipeline family it draws with
|
|
63326
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63327
|
+
* @ignore
|
|
63328
|
+
* @internal
|
|
63329
|
+
*/
|
|
63330
|
+
_warmMeshDraw(mesh, family, scene) {
|
|
63331
|
+
for (const fog of this._warmFog ?? [this._fog3d != null]) {
|
|
63332
|
+
this._warmMeshDrawFog(mesh, family, scene, fog);
|
|
63333
|
+
}
|
|
63334
|
+
}
|
|
63335
|
+
/**
|
|
63336
|
+
* {@link WebGPURenderer#_warmMeshDraw} for one fog state.
|
|
63337
|
+
* @param {object} mesh - the mesh
|
|
63338
|
+
* @param {string} family - the pipeline family it draws with
|
|
63339
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63340
|
+
* @param {boolean} fog - whether it draws fogged
|
|
63341
|
+
* @ignore
|
|
63342
|
+
* @internal
|
|
63343
|
+
*/
|
|
63344
|
+
_warmMeshDrawFog(mesh, family, scene, fog) {
|
|
63345
|
+
const culling = mesh.cullBackFaces === true;
|
|
63346
|
+
const state4 = {
|
|
63347
|
+
cullMode: culling ? "back" : "none",
|
|
63348
|
+
// the retained path's winding rule, see `drawMesh`
|
|
63349
|
+
frontFace: culling && this.supportsRetainedMesh === true && mesh.rightHanded !== true ? "cw" : "ccw",
|
|
63350
|
+
depthWrite: void 0,
|
|
63351
|
+
depthTest: mesh.depthTest === false ? false : void 0,
|
|
63352
|
+
// `undefined` rather than `false`, as the draw sets it: the key only
|
|
63353
|
+
// grows a fog suffix for `true`
|
|
63354
|
+
fog: fog ? true : void 0
|
|
63355
|
+
};
|
|
63356
|
+
const blendedOnly = mesh.transparent === true || mesh._blendedDraw === true;
|
|
63357
|
+
const blended = mesh.transparent !== false || mesh._blendedDraw === true;
|
|
63358
|
+
for (const [format, sampleCount] of scene) {
|
|
63359
|
+
if (!blendedOnly) {
|
|
63360
|
+
void this.pipelineCache.warm(
|
|
63361
|
+
family,
|
|
63362
|
+
"triangle-list",
|
|
63363
|
+
"none",
|
|
63364
|
+
true,
|
|
63365
|
+
"none",
|
|
63366
|
+
state4,
|
|
63367
|
+
format,
|
|
63368
|
+
sampleCount
|
|
63369
|
+
);
|
|
63370
|
+
}
|
|
63371
|
+
if (blended) {
|
|
63372
|
+
void this.pipelineCache.warm(
|
|
63373
|
+
family,
|
|
63374
|
+
"triangle-list",
|
|
63375
|
+
mesh._blendedDraw === true ? "normal" : mesh.blendMode,
|
|
63376
|
+
true,
|
|
63377
|
+
"none",
|
|
63378
|
+
{ ...state4, depthWrite: false },
|
|
63379
|
+
format,
|
|
63380
|
+
sampleCount
|
|
63381
|
+
);
|
|
63382
|
+
}
|
|
63383
|
+
}
|
|
63384
|
+
}
|
|
63385
|
+
/**
|
|
63386
|
+
* Warm the renderable's own draw: a mesh through its mesh family, and
|
|
63387
|
+
* everything else that draws a texture through the quad family, each with
|
|
63388
|
+
* the renderable's blend mode. Containers draw nothing of their own, and
|
|
63389
|
+
* a camera is a chain, warmed with its effects.
|
|
63390
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
63391
|
+
* @override
|
|
63392
|
+
* @ignore
|
|
63393
|
+
* @internal
|
|
63394
|
+
*/
|
|
63395
|
+
_warmDraw(renderable) {
|
|
63396
|
+
if (!this._canWarm() || renderable._postEffectManaged === true || Array.isArray(renderable.children)) {
|
|
63397
|
+
return;
|
|
63398
|
+
}
|
|
63399
|
+
const scene = this._sceneTargets();
|
|
63400
|
+
if (this._hostsMeshEffect(renderable)) {
|
|
63401
|
+
const batcher = this._meshBatcherFor(renderable);
|
|
63402
|
+
if (batcher?.shaderKey !== void 0) {
|
|
63403
|
+
this._warmMeshDraw(renderable, batcher.shaderKey, scene);
|
|
63404
|
+
}
|
|
63405
|
+
return;
|
|
63406
|
+
}
|
|
63407
|
+
if (renderable.instanceLayout !== void 0) {
|
|
63408
|
+
this._warmInstancedDraw(renderable, scene);
|
|
63409
|
+
return;
|
|
63410
|
+
}
|
|
63411
|
+
const blendMode = renderable.blendMode ?? "normal";
|
|
63412
|
+
for (const [format, sampleCount] of scene) {
|
|
63413
|
+
void this.pipelineCache.warm(
|
|
63414
|
+
"quad",
|
|
63415
|
+
"triangle-list",
|
|
63416
|
+
blendMode,
|
|
63417
|
+
true,
|
|
63418
|
+
"none",
|
|
63419
|
+
void 0,
|
|
63420
|
+
format,
|
|
63421
|
+
sampleCount
|
|
63422
|
+
);
|
|
63423
|
+
}
|
|
63424
|
+
}
|
|
63425
|
+
/**
|
|
63426
|
+
* Warm what an `InstancedMesh` draws with: its instanced family, and the
|
|
63427
|
+
* instanced ground-shadow family when it casts one.
|
|
63428
|
+
*
|
|
63429
|
+
* Both families are registered per instance record layout, on first use,
|
|
63430
|
+
* which the warm-up used to leave to the first draw. Nothing about them
|
|
63431
|
+
* needs the draw, though: the layout is the mesh's own, so registering
|
|
63432
|
+
* them here builds the same module and vertex layout the draw would, and
|
|
63433
|
+
* the keys follow the same rules as an ordinary mesh's. A scatter of
|
|
63434
|
+
* props is one of these, and it is exactly the kind of thing a stage
|
|
63435
|
+
* builds when it starts and draws on its next frame.
|
|
63436
|
+
* @param {object} mesh - the instanced mesh
|
|
63437
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63438
|
+
* @ignore
|
|
63439
|
+
* @internal
|
|
63440
|
+
*/
|
|
63441
|
+
_warmInstancedDraw(mesh, scene) {
|
|
63442
|
+
if (this.supportsInstancing !== true) {
|
|
63443
|
+
return;
|
|
63444
|
+
}
|
|
63445
|
+
const batcher = this._meshBatcherFor(mesh);
|
|
63446
|
+
if (typeof batcher?.instancedFamilyFor !== "function") {
|
|
63447
|
+
return;
|
|
63448
|
+
}
|
|
63449
|
+
this._warmMeshDraw(
|
|
63450
|
+
mesh,
|
|
63451
|
+
batcher.instancedFamilyFor(mesh.instanceLayout),
|
|
63452
|
+
scene
|
|
63453
|
+
);
|
|
63454
|
+
if (mesh._castsGroundShadow?.(this) !== true || typeof batcher.instancedShadowFamily !== "function") {
|
|
63455
|
+
return;
|
|
63456
|
+
}
|
|
63457
|
+
const family = batcher.instancedShadowFamily(mesh.instanceLayout);
|
|
63458
|
+
for (const fog of this._warmFog ?? [this._fog3d != null]) {
|
|
63459
|
+
const state4 = {
|
|
63460
|
+
cullMode: "none",
|
|
63461
|
+
frontFace: "ccw",
|
|
63462
|
+
depthWrite: false,
|
|
63463
|
+
depthTest: mesh.depthTest === false ? false : void 0,
|
|
63464
|
+
fog: fog ? true : void 0
|
|
63465
|
+
};
|
|
63466
|
+
for (const [format, sampleCount] of scene) {
|
|
63467
|
+
void this.pipelineCache.warm(
|
|
63468
|
+
family,
|
|
63469
|
+
"triangle-list",
|
|
63470
|
+
"normal",
|
|
63471
|
+
true,
|
|
63472
|
+
"none",
|
|
63473
|
+
state4,
|
|
63474
|
+
format,
|
|
63475
|
+
sampleCount
|
|
63476
|
+
);
|
|
63477
|
+
}
|
|
63478
|
+
}
|
|
63479
|
+
}
|
|
63480
|
+
/**
|
|
63481
|
+
* Build the advanced blend shader and its offscreen target before the
|
|
63482
|
+
* first draw in one of those modes, and warm the two pipelines a bracket
|
|
63483
|
+
* uses: the draw itself into the 8-bit offscreen, and the composite back
|
|
63484
|
+
* onto the scene.
|
|
63485
|
+
* @override
|
|
63486
|
+
* @ignore
|
|
63487
|
+
* @internal
|
|
63488
|
+
*/
|
|
63489
|
+
_warmAdvancedBlend() {
|
|
63490
|
+
if (!this._canWarm()) {
|
|
63491
|
+
return;
|
|
63492
|
+
}
|
|
63493
|
+
if (typeof this._advancedBlendEffect === "undefined") {
|
|
63494
|
+
this._advancedBlendEffect = new BlendEffect(this);
|
|
63495
|
+
}
|
|
63496
|
+
const effect = this._advancedBlendEffect;
|
|
63497
|
+
if (effect.enabled !== true || effect.wgslRealization?.valid !== true) {
|
|
63498
|
+
return;
|
|
63499
|
+
}
|
|
63500
|
+
const { width, height } = this.getCanvas();
|
|
63501
|
+
if (typeof this._advancedBlendTarget === "undefined") {
|
|
63502
|
+
this._advancedBlendTarget = new WebGPURenderTarget(this, width, height, {
|
|
63503
|
+
sampleCount: 1,
|
|
63504
|
+
format: this.preferredFormat
|
|
63505
|
+
});
|
|
63506
|
+
}
|
|
63507
|
+
void this.pipelineCache.warm(
|
|
63508
|
+
"quad",
|
|
63509
|
+
"triangle-list",
|
|
63510
|
+
"normal",
|
|
63511
|
+
true,
|
|
63512
|
+
"none",
|
|
63513
|
+
void 0,
|
|
63514
|
+
this.preferredFormat,
|
|
63515
|
+
1
|
|
63516
|
+
);
|
|
63517
|
+
const family = this._effectFamily(effect);
|
|
63518
|
+
for (const [format, sampleCount] of this._sceneTargets()) {
|
|
63519
|
+
void this.pipelineCache.warm(
|
|
63520
|
+
family,
|
|
63521
|
+
"triangle-list",
|
|
63522
|
+
"normal",
|
|
63523
|
+
true,
|
|
63524
|
+
"none",
|
|
63525
|
+
void 0,
|
|
63526
|
+
format,
|
|
63527
|
+
sampleCount
|
|
63528
|
+
);
|
|
63529
|
+
}
|
|
63530
|
+
}
|
|
63531
|
+
/**
|
|
63532
|
+
* Warm the tone curve's pipeline: always the last step of a camera chain,
|
|
63533
|
+
* so a full-screen blit onto the canvas with blending off.
|
|
63534
|
+
* @override
|
|
63535
|
+
* @ignore
|
|
63536
|
+
* @internal
|
|
63537
|
+
*/
|
|
63538
|
+
_warmToneMap() {
|
|
63539
|
+
const toneMap = this._toneMapEffect;
|
|
63540
|
+
if (this.settings?.prewarm !== true || toneMap?.wgslRealization?.valid !== true || !this._canWarm()) {
|
|
63541
|
+
return;
|
|
63542
|
+
}
|
|
63543
|
+
void this.pipelineCache.warm(
|
|
63544
|
+
this._effectFamily(toneMap),
|
|
63545
|
+
"triangle-list",
|
|
63546
|
+
"none",
|
|
63547
|
+
true,
|
|
63548
|
+
"none",
|
|
63549
|
+
void 0,
|
|
63550
|
+
this.canvasFormat,
|
|
63551
|
+
this.canvasSampleCount
|
|
63552
|
+
);
|
|
63553
|
+
}
|
|
61938
63554
|
/**
|
|
61939
63555
|
* Begin a post-effect pass for the given renderable — the WebGPU
|
|
61940
63556
|
* realization of the WebGL FBO path: the renderable's whole content
|
|
@@ -61967,17 +63583,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61967
63583
|
}
|
|
61968
63584
|
savedProjection.copy(this.projectionMatrix);
|
|
61969
63585
|
this.effectPassDepth++;
|
|
61970
|
-
|
|
61971
|
-
(w, h, isCapture, index) => {
|
|
61972
|
-
return new WebGPURenderTarget(this, w, h, {
|
|
61973
|
-
sampleCount: isCapture === true ? this.canvasSampleCount : 1,
|
|
61974
|
-
format: index < 2 ? this._colorFormat : this.preferredFormat
|
|
61975
|
-
});
|
|
61976
|
-
}
|
|
61977
|
-
);
|
|
61978
|
-
const rt = this._renderTargetPool.begin(
|
|
63586
|
+
const rt = this._postEffectPool().begin(
|
|
61979
63587
|
isCamera,
|
|
61980
|
-
|
|
63588
|
+
this._postEffectCapacity(renderable, effects),
|
|
61981
63589
|
canvas.width,
|
|
61982
63590
|
canvas.height
|
|
61983
63591
|
);
|
|
@@ -62222,23 +63830,24 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62222
63830
|
* WebGPU splits what WebGL fuses into one program. The **modules** for
|
|
62223
63831
|
* the engine's own families — quad, blit, primitive, clear, and the mesh
|
|
62224
63832
|
* tiers — are already created when the pipeline cache and the batchers
|
|
62225
|
-
* are constructed
|
|
62226
|
-
* is
|
|
62227
|
-
*
|
|
62228
|
-
*
|
|
62229
|
-
*
|
|
62230
|
-
*
|
|
62231
|
-
*
|
|
62232
|
-
*
|
|
62233
|
-
*
|
|
62234
|
-
*
|
|
62235
|
-
*
|
|
62236
|
-
*
|
|
62237
|
-
*
|
|
62238
|
-
*
|
|
62239
|
-
*
|
|
62240
|
-
*
|
|
62241
|
-
*
|
|
63833
|
+
* are constructed. A **pipeline** is where the real compile happens, and
|
|
63834
|
+
* it is the expensive part: tens of milliseconds each, paid in the frame
|
|
63835
|
+
* that first draws with it. But it is keyed by draw state (blend, cull,
|
|
63836
|
+
* depth write) and by the format of the target being drawn into, as well
|
|
63837
|
+
* as by shader, so the set a scene will ask for cannot be known from
|
|
63838
|
+
* here. Those are warmed when an effect is attached to a renderable
|
|
63839
|
+
* ({@link Renderable#addPostEffect}), which is the first moment the
|
|
63840
|
+
* renderable that decides them is known.
|
|
63841
|
+
*
|
|
63842
|
+
* What this does warm is what needs no renderable. A `ShaderEffect`'s
|
|
63843
|
+
* module: `prepareEffectBinding` builds it, and only ever from inside the
|
|
63844
|
+
* draw path. Every effect the loader compiled from a `{type: "shader"}`
|
|
63845
|
+
* asset is already in `shaderList`, so those can be brought forward —
|
|
63846
|
+
* which is what makes declaring a level's effects alongside that level's
|
|
63847
|
+
* assets worth doing. And the mip-chain blitter every mipmapped texture
|
|
63848
|
+
* goes through on upload, when textures are filtered (`textureFilter`
|
|
63849
|
+
* resolving to `"linear"`; nearest filtering never builds a chain).
|
|
63850
|
+
* @returns {Promise<void>} settles once those are built
|
|
62242
63851
|
* @see {@link Renderer#prewarm}
|
|
62243
63852
|
*/
|
|
62244
63853
|
prewarm() {
|
|
@@ -62252,6 +63861,28 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62252
63861
|
buildEffectGPU(this, effect, realization);
|
|
62253
63862
|
}
|
|
62254
63863
|
}
|
|
63864
|
+
if (this.getDefaultTextureFilter() === "linear") {
|
|
63865
|
+
this.textureStore.warmMipmaps();
|
|
63866
|
+
}
|
|
63867
|
+
const warms = [];
|
|
63868
|
+
if (this._canWarm()) {
|
|
63869
|
+
for (const [format, sampleCount] of this._sceneTargets()) {
|
|
63870
|
+
warms.push(
|
|
63871
|
+
this.pipelineCache.warm(
|
|
63872
|
+
"clear",
|
|
63873
|
+
"triangle-list",
|
|
63874
|
+
"none",
|
|
63875
|
+
true,
|
|
63876
|
+
"none",
|
|
63877
|
+
void 0,
|
|
63878
|
+
format,
|
|
63879
|
+
sampleCount
|
|
63880
|
+
)
|
|
63881
|
+
);
|
|
63882
|
+
}
|
|
63883
|
+
}
|
|
63884
|
+
return Promise.all(warms).then(() => {
|
|
63885
|
+
});
|
|
62255
63886
|
});
|
|
62256
63887
|
}
|
|
62257
63888
|
/**
|
|
@@ -62273,6 +63904,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62273
63904
|
this.frameTextureView = null;
|
|
62274
63905
|
this.frameTexture = null;
|
|
62275
63906
|
}
|
|
63907
|
+
if (this.pipelineCache != null) {
|
|
63908
|
+
this.pipelineCache.inFrame = false;
|
|
63909
|
+
}
|
|
62276
63910
|
this.destroyRetiredTextures();
|
|
62277
63911
|
}
|
|
62278
63912
|
/**
|
|
@@ -62292,6 +63926,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62292
63926
|
}
|
|
62293
63927
|
this.frameTextureView = null;
|
|
62294
63928
|
this.frameTexture = null;
|
|
63929
|
+
if (this.pipelineCache != null) {
|
|
63930
|
+
this.pipelineCache.inFrame = false;
|
|
63931
|
+
}
|
|
62295
63932
|
this.currentRenderTarget = null;
|
|
62296
63933
|
this.pendingColorClear = false;
|
|
62297
63934
|
this.pendingDepthClear = false;
|
|
@@ -64050,6 +65687,10 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
64050
65687
|
batcher.init(this);
|
|
64051
65688
|
}
|
|
64052
65689
|
emit(ONCONTEXT_RESTORED, this);
|
|
65690
|
+
if (this.settings.prewarm === true) {
|
|
65691
|
+
this.prewarm().catch(() => {
|
|
65692
|
+
});
|
|
65693
|
+
}
|
|
64053
65694
|
}
|
|
64054
65695
|
/**
|
|
64055
65696
|
* Returns the WebGPU canvas context
|
|
@@ -69963,8 +71604,110 @@ var ShineEffect = class extends ShaderEffect {
|
|
|
69963
71604
|
}
|
|
69964
71605
|
};
|
|
69965
71606
|
|
|
69966
|
-
// src/video/effects/
|
|
71607
|
+
// src/video/effects/squish.js
|
|
69967
71608
|
var wgslFragment15 = `
|
|
71609
|
+
struct SquishUniforms {
|
|
71610
|
+
uSquishFrame : vec4f,
|
|
71611
|
+
uSquishXFactor : f32,
|
|
71612
|
+
uSquishBulge : f32,
|
|
71613
|
+
};
|
|
71614
|
+
@group(3) @binding(0) var<uniform> fx : SquishUniforms;
|
|
71615
|
+
|
|
71616
|
+
fn apply(color : vec4f, uv : vec2f) -> vec4f {
|
|
71617
|
+
// -1..1 across the quad: the sprite's frame, stretched by the vertex scale
|
|
71618
|
+
var p = (uv - fx.uSquishFrame.xy) / fx.uSquishFrame.zw * 2.0 - 1.0;
|
|
71619
|
+
// undo the stretch: the sprite keeps its width, in the middle of the
|
|
71620
|
+
// wider quad, with room on both sides
|
|
71621
|
+
p.x = p.x * fx.uSquishXFactor;
|
|
71622
|
+
// each row widens by its share of the bulge: all of it across the
|
|
71623
|
+
// middle, none at the top and bottom
|
|
71624
|
+
p.x = p.x / max(1.0 + fx.uSquishBulge * sqrt(max(1.0 - p.y * p.y, 0.0)), 0.0001);
|
|
71625
|
+
let local = (p + 1.0) * 0.5;
|
|
71626
|
+
// sampled before the test, since textureSample needs uniform control flow
|
|
71627
|
+
let texel = textureSample(uTexture, uSampler, fx.uSquishFrame.xy + clamp(local, vec2f(0.0), vec2f(1.0)) * fx.uSquishFrame.zw) * vColor;
|
|
71628
|
+
let inside = all(local >= vec2f(0.0)) && all(local <= vec2f(1.0));
|
|
71629
|
+
return select(vec4f(0.0), texel, inside);
|
|
71630
|
+
}
|
|
71631
|
+
`;
|
|
71632
|
+
var SquishEffect = class _SquishEffect extends ShaderEffect {
|
|
71633
|
+
/**
|
|
71634
|
+
* @param {Renderer} renderer - the current renderer instance
|
|
71635
|
+
* @param {object} [options] - effect options
|
|
71636
|
+
* @param {number} [options.bulge=0] - how far the middle bulges out (positive) or pinches in (negative), from -1 to 1
|
|
71637
|
+
*/
|
|
71638
|
+
constructor(renderer, options = {}) {
|
|
71639
|
+
super(renderer, {
|
|
71640
|
+
glsl: `
|
|
71641
|
+
uniform vec4 uSquishFrame;
|
|
71642
|
+
uniform float uSquishXFactor;
|
|
71643
|
+
uniform float uSquishBulge;
|
|
71644
|
+
vec4 apply(vec4 color, vec2 uv) {
|
|
71645
|
+
// -1..1 across the quad: the sprite's frame, stretched by the vertex scale
|
|
71646
|
+
vec2 p = (uv - uSquishFrame.xy) / uSquishFrame.zw * 2.0 - 1.0;
|
|
71647
|
+
// undo the stretch: the sprite keeps its width, in the middle
|
|
71648
|
+
// of the wider quad, with room on both sides
|
|
71649
|
+
p.x *= uSquishXFactor;
|
|
71650
|
+
// each row widens by its share of the bulge: all of it across
|
|
71651
|
+
// the middle, none at the top and bottom
|
|
71652
|
+
p.x /= max(1.0 + uSquishBulge * sqrt(max(1.0 - p.y * p.y, 0.0)), 0.0001);
|
|
71653
|
+
vec2 local = (p + 1.0) * 0.5;
|
|
71654
|
+
// transparent past the sprite: the room around it, or the
|
|
71655
|
+
// indent of a pinch
|
|
71656
|
+
if (local.x < 0.0 || local.x > 1.0 || local.y < 0.0 || local.y > 1.0) {
|
|
71657
|
+
return vec4(0.0);
|
|
71658
|
+
}
|
|
71659
|
+
return texture2D(uSampler, uSquishFrame.xy + local * uSquishFrame.zw) * vColor;
|
|
71660
|
+
}
|
|
71661
|
+
`,
|
|
71662
|
+
wgsl: wgslFragment15
|
|
71663
|
+
});
|
|
71664
|
+
this._bulge = 0;
|
|
71665
|
+
this._frame = new Float32Array([0, 0, 1, 1]);
|
|
71666
|
+
this.setUniform("uSquishFrame", this._frame);
|
|
71667
|
+
this.setBulge(options.bulge ?? 0);
|
|
71668
|
+
}
|
|
71669
|
+
/**
|
|
71670
|
+
* set how far the middle of the sprite bulges out, or pinches in
|
|
71671
|
+
* @param {number} bulge - from -1 to 1: positive bulges out, up to twice the width across the middle; negative pinches in, down to nothing at -1; 0 leaves the sprite as it is
|
|
71672
|
+
*/
|
|
71673
|
+
setBulge(bulge) {
|
|
71674
|
+
const value = Math.min(Math.max(bulge, -1), 1);
|
|
71675
|
+
this._bulge = value;
|
|
71676
|
+
const xFactor = 1 + Math.max(value, 0);
|
|
71677
|
+
this._vertexScale.x = xFactor;
|
|
71678
|
+
this.setUniform("uSquishXFactor", xFactor);
|
|
71679
|
+
this.setUniform("uSquishBulge", value);
|
|
71680
|
+
}
|
|
71681
|
+
/**
|
|
71682
|
+
* The batchers hand every effect the frame it draws through here (it is
|
|
71683
|
+
* what `noise_uv` runs across), and the bulge is measured against the
|
|
71684
|
+
* same frame: where the sprite sits in its atlas, in UV units. A render
|
|
71685
|
+
* target blit hands over the whole target.
|
|
71686
|
+
* @ignore
|
|
71687
|
+
* @internal
|
|
71688
|
+
*/
|
|
71689
|
+
_setNoiseUVRect(sourceWidth, sourceHeight, objectWidth, objectHeight, u0, v0) {
|
|
71690
|
+
const frame = this._frame;
|
|
71691
|
+
frame[0] = u0;
|
|
71692
|
+
frame[1] = v0;
|
|
71693
|
+
frame[2] = Math.max(Math.abs(objectWidth), 1) / Math.max(sourceWidth, 1);
|
|
71694
|
+
frame[3] = Math.max(Math.abs(objectHeight), 1) / Math.max(sourceHeight, 1);
|
|
71695
|
+
this.setUniform("uSquishFrame", frame);
|
|
71696
|
+
}
|
|
71697
|
+
/**
|
|
71698
|
+
* Create an independent copy of this effect, with the same bulge.
|
|
71699
|
+
* @returns {SquishEffect} a new effect
|
|
71700
|
+
*/
|
|
71701
|
+
clone() {
|
|
71702
|
+
if (this.destroyed) {
|
|
71703
|
+
throw new Error("SquishEffect.clone: effect has been destroyed");
|
|
71704
|
+
}
|
|
71705
|
+
return new _SquishEffect(this._renderer, { bulge: this._bulge });
|
|
71706
|
+
}
|
|
71707
|
+
};
|
|
71708
|
+
|
|
71709
|
+
// src/video/effects/tintPulse.js
|
|
71710
|
+
var wgslFragment16 = `
|
|
69968
71711
|
struct TintPulseUniforms {
|
|
69969
71712
|
uPulseColor : vec3f,
|
|
69970
71713
|
uPulseSpeed : f32,
|
|
@@ -69998,7 +71741,7 @@ var TintPulseEffect = class extends ShaderEffect {
|
|
|
69998
71741
|
return vec4(mix(color.rgb, uPulseColor * color.a, pulse), color.a);
|
|
69999
71742
|
}
|
|
70000
71743
|
`,
|
|
70001
|
-
wgsl:
|
|
71744
|
+
wgsl: wgslFragment16
|
|
70002
71745
|
});
|
|
70003
71746
|
const color = options.color ?? [1, 0, 0];
|
|
70004
71747
|
this.setUniform("uPulseColor", new Float32Array(color));
|
|
@@ -70025,7 +71768,7 @@ var fragment = `
|
|
|
70025
71768
|
return vec4(color.rgb * v, color.a);
|
|
70026
71769
|
}
|
|
70027
71770
|
`;
|
|
70028
|
-
var
|
|
71771
|
+
var wgslFragment17 = `
|
|
70029
71772
|
struct VignetteUniforms {
|
|
70030
71773
|
uStrength : f32,
|
|
70031
71774
|
uSize : f32,
|
|
@@ -70046,7 +71789,7 @@ var VignetteEffect = class extends ShaderEffect {
|
|
|
70046
71789
|
* @param {number} [options.size=25.0] - vignette spread multiplier (higher = smaller dark area)
|
|
70047
71790
|
*/
|
|
70048
71791
|
constructor(renderer, options = {}) {
|
|
70049
|
-
super(renderer, { glsl: fragment, wgsl:
|
|
71792
|
+
super(renderer, { glsl: fragment, wgsl: wgslFragment17 });
|
|
70050
71793
|
this.strength = options.strength ?? 0.15;
|
|
70051
71794
|
this.size = options.size ?? 25;
|
|
70052
71795
|
this.setUniform("uStrength", this.strength);
|
|
@@ -70071,7 +71814,7 @@ var VignetteEffect = class extends ShaderEffect {
|
|
|
70071
71814
|
};
|
|
70072
71815
|
|
|
70073
71816
|
// src/video/effects/wave.js
|
|
70074
|
-
var
|
|
71817
|
+
var wgslFragment18 = `
|
|
70075
71818
|
struct WaveUniforms {
|
|
70076
71819
|
uAmplitude : f32,
|
|
70077
71820
|
uFrequency : f32,
|
|
@@ -70107,7 +71850,7 @@ var WaveEffect = class extends ShaderEffect {
|
|
|
70107
71850
|
return texture2D(uSampler, distorted) * vColor;
|
|
70108
71851
|
}
|
|
70109
71852
|
`,
|
|
70110
|
-
wgsl:
|
|
71853
|
+
wgsl: wgslFragment18
|
|
70111
71854
|
});
|
|
70112
71855
|
this.setUniform("uAmplitude", options.amplitude ?? 0.01);
|
|
70113
71856
|
this.setUniform("uFrequency", options.frequency ?? 10);
|
|
@@ -71704,6 +73447,7 @@ export {
|
|
|
71704
73447
|
Sphere,
|
|
71705
73448
|
Sprite,
|
|
71706
73449
|
Sprite3d,
|
|
73450
|
+
SquishEffect,
|
|
71707
73451
|
Stage,
|
|
71708
73452
|
TMXHexagonalRenderer,
|
|
71709
73453
|
TMXIsometricRenderer,
|