melonjs 20.9.0 → 20.10.0
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/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1616 -225
- package/build/index.js.map +4 -4
- 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/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 +22 -4
- package/skills/melonjs-performance/SKILL.md +7 -3
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* melonJS Game Engine - 20.
|
|
2
|
+
* melonJS Game Engine - 20.10.0
|
|
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;
|
|
@@ -17562,6 +17574,7 @@ var FrameAnimation = class {
|
|
|
17562
17574
|
|
|
17563
17575
|
// src/renderable/sprite.js
|
|
17564
17576
|
var FLICKER_INTERVAL_MS = 33;
|
|
17577
|
+
var VIDEO_GESTURES = ["touchstart", "touchend", "click", "keydown"];
|
|
17565
17578
|
var Sprite = class extends Renderable {
|
|
17566
17579
|
/**
|
|
17567
17580
|
* @param {number} x - the x coordinates of the sprite object
|
|
@@ -17668,8 +17681,12 @@ var Sprite = class extends Renderable {
|
|
|
17668
17681
|
}
|
|
17669
17682
|
this.removeStatePauseListener = on(STATE_PAUSE, () => {
|
|
17670
17683
|
this.image.pause();
|
|
17684
|
+
this._cancelVideoPlay();
|
|
17671
17685
|
});
|
|
17672
17686
|
this._lastVideoFrameVersion = -1;
|
|
17687
|
+
this._videoPlayPending = false;
|
|
17688
|
+
this._videoPlayRequest = 0;
|
|
17689
|
+
this._videoGestureListener = void 0;
|
|
17673
17690
|
if (typeof this.image.requestVideoFrameCallback === "function") {
|
|
17674
17691
|
this.image.version ??= 0;
|
|
17675
17692
|
const tick2 = () => {
|
|
@@ -18144,8 +18161,8 @@ var Sprite = class extends Renderable {
|
|
|
18144
18161
|
if (this.isVideo) {
|
|
18145
18162
|
if (this.animationpause) {
|
|
18146
18163
|
this.image.pause();
|
|
18147
|
-
} else if (this.image.paused) {
|
|
18148
|
-
this.
|
|
18164
|
+
} else if (this.image.paused && this._videoPlayPending !== true) {
|
|
18165
|
+
this._playVideo();
|
|
18149
18166
|
}
|
|
18150
18167
|
if (this.image.version === void 0) {
|
|
18151
18168
|
if (!this.image.paused) {
|
|
@@ -18241,6 +18258,86 @@ var Sprite = class extends Renderable {
|
|
|
18241
18258
|
// dw,dh
|
|
18242
18259
|
);
|
|
18243
18260
|
}
|
|
18261
|
+
/**
|
|
18262
|
+
* Start the video and handle the browser's answer. Before the user has
|
|
18263
|
+
* interacted with the page, browsers refuse playback: calling `play()`
|
|
18264
|
+
* again on every frame only logged an uncaught error per frame, so a
|
|
18265
|
+
* refusal now waits for the next gesture instead. Any other failure
|
|
18266
|
+
* (e.g. a `pause()` interrupting the request) retries on the next frame.
|
|
18267
|
+
* @ignore
|
|
18268
|
+
* @internal
|
|
18269
|
+
*/
|
|
18270
|
+
_playVideo() {
|
|
18271
|
+
const request = ++this._videoPlayRequest;
|
|
18272
|
+
this._videoPlayPending = true;
|
|
18273
|
+
Promise.resolve(this.image.play()).then(
|
|
18274
|
+
() => {
|
|
18275
|
+
this._videoPlayPending = false;
|
|
18276
|
+
},
|
|
18277
|
+
(error) => {
|
|
18278
|
+
if (request !== this._videoPlayRequest) {
|
|
18279
|
+
return;
|
|
18280
|
+
}
|
|
18281
|
+
if (error?.name === "NotAllowedError") {
|
|
18282
|
+
this._awaitVideoGesture();
|
|
18283
|
+
} else {
|
|
18284
|
+
this._videoPlayPending = false;
|
|
18285
|
+
}
|
|
18286
|
+
}
|
|
18287
|
+
);
|
|
18288
|
+
}
|
|
18289
|
+
/**
|
|
18290
|
+
* Wait for the next user gesture and start the video from inside its
|
|
18291
|
+
* handler, the moment a browser that refused playback allows it.
|
|
18292
|
+
* @ignore
|
|
18293
|
+
* @internal
|
|
18294
|
+
*/
|
|
18295
|
+
_awaitVideoGesture() {
|
|
18296
|
+
if (this._videoGestureListener !== void 0) {
|
|
18297
|
+
return;
|
|
18298
|
+
}
|
|
18299
|
+
this._videoGestureListener = () => {
|
|
18300
|
+
this._removeVideoGestureListener();
|
|
18301
|
+
this._videoPlayPending = false;
|
|
18302
|
+
if (this.animationpause !== true && this.image.paused) {
|
|
18303
|
+
this._playVideo();
|
|
18304
|
+
}
|
|
18305
|
+
};
|
|
18306
|
+
for (const type of VIDEO_GESTURES) {
|
|
18307
|
+
globalThis.document.addEventListener(
|
|
18308
|
+
type,
|
|
18309
|
+
this._videoGestureListener,
|
|
18310
|
+
true
|
|
18311
|
+
);
|
|
18312
|
+
}
|
|
18313
|
+
}
|
|
18314
|
+
/**
|
|
18315
|
+
* Drop a pending `play()` request and stop waiting for a gesture.
|
|
18316
|
+
* @ignore
|
|
18317
|
+
* @internal
|
|
18318
|
+
*/
|
|
18319
|
+
_cancelVideoPlay() {
|
|
18320
|
+
this._videoPlayRequest++;
|
|
18321
|
+
this._videoPlayPending = false;
|
|
18322
|
+
this._removeVideoGestureListener();
|
|
18323
|
+
}
|
|
18324
|
+
/**
|
|
18325
|
+
* @ignore
|
|
18326
|
+
* @internal
|
|
18327
|
+
*/
|
|
18328
|
+
_removeVideoGestureListener() {
|
|
18329
|
+
if (this._videoGestureListener === void 0) {
|
|
18330
|
+
return;
|
|
18331
|
+
}
|
|
18332
|
+
for (const type of VIDEO_GESTURES) {
|
|
18333
|
+
globalThis.document.removeEventListener(
|
|
18334
|
+
type,
|
|
18335
|
+
this._videoGestureListener,
|
|
18336
|
+
true
|
|
18337
|
+
);
|
|
18338
|
+
}
|
|
18339
|
+
this._videoGestureListener = void 0;
|
|
18340
|
+
}
|
|
18244
18341
|
/**
|
|
18245
18342
|
* Destroy function<br>
|
|
18246
18343
|
* @ignore
|
|
@@ -18252,6 +18349,7 @@ var Sprite = class extends Renderable {
|
|
|
18252
18349
|
this.offset = void 0;
|
|
18253
18350
|
if (this.isVideo) {
|
|
18254
18351
|
this.removeStatePauseListener();
|
|
18352
|
+
this._cancelVideoPlay();
|
|
18255
18353
|
if (typeof this._videoFrameHandle !== "undefined") {
|
|
18256
18354
|
this.image.cancelVideoFrameCallback(this._videoFrameHandle);
|
|
18257
18355
|
delete this.image.version;
|
|
@@ -21693,6 +21791,85 @@ var Path2D = class {
|
|
|
21693
21791
|
};
|
|
21694
21792
|
var path2d_default = Path2D;
|
|
21695
21793
|
|
|
21794
|
+
// src/video/blendmodes.js
|
|
21795
|
+
var ADVANCED_BLEND_MODES = [
|
|
21796
|
+
"difference",
|
|
21797
|
+
"overlay",
|
|
21798
|
+
"hard-light",
|
|
21799
|
+
"color-dodge",
|
|
21800
|
+
"color-burn",
|
|
21801
|
+
"soft-light",
|
|
21802
|
+
// `darken` and `lighten` joined the shader path in 20.2: fixed-function
|
|
21803
|
+
// MIN/MAX compute `min(src, dst)` and nothing else, so there is nowhere to
|
|
21804
|
+
// put the `(1 - srcAlpha) * dst` term source-over contributes after the
|
|
21805
|
+
// blend. A translucent source lost the backdrop's share entirely — a white
|
|
21806
|
+
// `lighten` glow over a light backdrop rendered invisible. APPENDED, never
|
|
21807
|
+
// inserted: the index is the shader's branch value.
|
|
21808
|
+
"darken",
|
|
21809
|
+
"lighten"
|
|
21810
|
+
];
|
|
21811
|
+
function isAdvancedBlendMode(mode) {
|
|
21812
|
+
return ADVANCED_BLEND_MODES.indexOf(mode) !== -1;
|
|
21813
|
+
}
|
|
21814
|
+
function advancedBlendModeIndex(mode) {
|
|
21815
|
+
return ADVANCED_BLEND_MODES.indexOf(mode);
|
|
21816
|
+
}
|
|
21817
|
+
function normalizeBlendMode(mode) {
|
|
21818
|
+
switch (mode) {
|
|
21819
|
+
case "none":
|
|
21820
|
+
case "multiply":
|
|
21821
|
+
case "screen":
|
|
21822
|
+
case "exclusion":
|
|
21823
|
+
case "normal":
|
|
21824
|
+
return mode;
|
|
21825
|
+
case "additive":
|
|
21826
|
+
case "add":
|
|
21827
|
+
case "lighter":
|
|
21828
|
+
return "additive";
|
|
21829
|
+
default:
|
|
21830
|
+
return "normal";
|
|
21831
|
+
}
|
|
21832
|
+
}
|
|
21833
|
+
function isSupportedBlendMode(mode) {
|
|
21834
|
+
return isAdvancedBlendMode(mode) || mode === "normal" || normalizeBlendMode(mode) !== "normal";
|
|
21835
|
+
}
|
|
21836
|
+
function reportedBlendMode(mode) {
|
|
21837
|
+
return isSupportedBlendMode(mode) ? mode : "normal";
|
|
21838
|
+
}
|
|
21839
|
+
function blendStateFor(mode, premultipliedAlpha) {
|
|
21840
|
+
const src = premultipliedAlpha ? "one" : "src-alpha";
|
|
21841
|
+
switch (mode) {
|
|
21842
|
+
case "none":
|
|
21843
|
+
return void 0;
|
|
21844
|
+
case "additive":
|
|
21845
|
+
return { operation: "add", srcFactor: src, dstFactor: "one" };
|
|
21846
|
+
case "multiply":
|
|
21847
|
+
return {
|
|
21848
|
+
operation: "add",
|
|
21849
|
+
srcFactor: "dst",
|
|
21850
|
+
dstFactor: "one-minus-src-alpha"
|
|
21851
|
+
};
|
|
21852
|
+
case "exclusion":
|
|
21853
|
+
return {
|
|
21854
|
+
operation: "add",
|
|
21855
|
+
srcFactor: "one-minus-dst",
|
|
21856
|
+
dstFactor: "one-minus-src"
|
|
21857
|
+
};
|
|
21858
|
+
case "screen":
|
|
21859
|
+
return {
|
|
21860
|
+
operation: "add",
|
|
21861
|
+
srcFactor: "one",
|
|
21862
|
+
dstFactor: "one-minus-src"
|
|
21863
|
+
};
|
|
21864
|
+
default:
|
|
21865
|
+
return {
|
|
21866
|
+
operation: "add",
|
|
21867
|
+
srcFactor: src,
|
|
21868
|
+
dstFactor: "one-minus-src-alpha"
|
|
21869
|
+
};
|
|
21870
|
+
}
|
|
21871
|
+
}
|
|
21872
|
+
|
|
21696
21873
|
// src/video/effects/tonemap.js
|
|
21697
21874
|
var CURVES = {
|
|
21698
21875
|
/**
|
|
@@ -22171,12 +22348,25 @@ var Renderer = class _Renderer {
|
|
|
22171
22348
|
*
|
|
22172
22349
|
* Two consequences worth knowing:
|
|
22173
22350
|
*
|
|
22174
|
-
* - **A game that never calls `loader.preload()`
|
|
22175
|
-
*
|
|
22176
|
-
* and still want the warm-up.
|
|
22351
|
+
* - **A game that never calls `loader.preload()` never gets this part.**
|
|
22352
|
+
* Call it yourself if you load assets some other way and still want it.
|
|
22177
22353
|
* - It is deliberately NOT called from `Application.init()`. That runs
|
|
22178
22354
|
* before anything is on screen, so warming there would only lengthen a
|
|
22179
22355
|
* blank page instead of hiding behind a progress bar.
|
|
22356
|
+
*
|
|
22357
|
+
* This is the half that needs no renderable. The other half cannot run
|
|
22358
|
+
* here, because what it compiles depends on the renderable drawing: under
|
|
22359
|
+
* WebGPU a pipeline is keyed by blend mode, culling, transparency and the
|
|
22360
|
+
* format of the target as well as by shader. So, behind the same
|
|
22361
|
+
* `prewarm` setting, the renderer also warms a renderable when it gains
|
|
22362
|
+
* a post effect ({@link Renderable#addPostEffect}) and when it is added
|
|
22363
|
+
* to the world ({@link Container#addChild}): its effect pipelines, its
|
|
22364
|
+
* own draw, the render targets its effect chain composites through, and
|
|
22365
|
+
* the advanced blend machinery if its blend mode needs it. That work is
|
|
22366
|
+
* started after the caller's code has run and finishes off the frame, so
|
|
22367
|
+
* it pays off when a renderable is set up at least a frame before it is
|
|
22368
|
+
* first drawn: build pools and attach their effects when a stage starts,
|
|
22369
|
+
* not when the object is first shown.
|
|
22180
22370
|
* @returns {Promise<void>} settles when the built-in programs are ready
|
|
22181
22371
|
* @example
|
|
22182
22372
|
* // the usual case: nothing to write. `preload()` warms on the way through
|
|
@@ -23096,6 +23286,152 @@ var Renderer = class _Renderer {
|
|
|
23096
23286
|
}
|
|
23097
23287
|
return effects;
|
|
23098
23288
|
}
|
|
23289
|
+
/**
|
|
23290
|
+
* How many effects a renderable's chain has to have render targets for:
|
|
23291
|
+
* every one attached, not just the ones enabled right now.
|
|
23292
|
+
*
|
|
23293
|
+
* Sizing the pool by the enabled count meant the ping-pong target was
|
|
23294
|
+
* only created on the frame a second effect was switched on, which is a
|
|
23295
|
+
* frame in the middle of play by construction (a hit flash, a power-up),
|
|
23296
|
+
* and creating a target there is a stall in that frame. A disabled effect
|
|
23297
|
+
* is one the game intends to turn on, so its target is made up front.
|
|
23298
|
+
* @param {Renderable} renderable - the renderable whose chain this is
|
|
23299
|
+
* @param {ShaderEffect[]} effects - its enabled chain, from `_effectChainFor`
|
|
23300
|
+
* @returns {number} the number of chain slots to provision
|
|
23301
|
+
* @ignore
|
|
23302
|
+
* @internal
|
|
23303
|
+
*/
|
|
23304
|
+
_postEffectCapacity(renderable, effects) {
|
|
23305
|
+
return Math.max(effects.length, renderable.postEffects.length);
|
|
23306
|
+
}
|
|
23307
|
+
/**
|
|
23308
|
+
* Warm, ahead of its first draw, what a renderable will need: the
|
|
23309
|
+
* pipelines, programs and render targets that would otherwise be built
|
|
23310
|
+
* on the frame it first shows up.
|
|
23311
|
+
*
|
|
23312
|
+
* Called when a renderable gains a post effect
|
|
23313
|
+
* ({@link Renderable#addPostEffect}) and when it enters the world
|
|
23314
|
+
* ({@link Container#addChild}), the two moments its draw becomes
|
|
23315
|
+
* knowable. Deferred to a microtask so it runs once the caller's own code
|
|
23316
|
+
* has finished, which is when the rest of the picture exists: a
|
|
23317
|
+
* renderable usually gets its effect before it is added to the world,
|
|
23318
|
+
* sized, or given its blend mode, and those all decide which pipeline its
|
|
23319
|
+
* draw will ask for. Several requests in one task collapse into one
|
|
23320
|
+
* warm-up per renderable.
|
|
23321
|
+
*
|
|
23322
|
+
* Honours the `prewarm` setting, the same switch as the loading-screen
|
|
23323
|
+
* warm-up.
|
|
23324
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23325
|
+
* @ignore
|
|
23326
|
+
* @internal
|
|
23327
|
+
*/
|
|
23328
|
+
_queueWarm(renderable) {
|
|
23329
|
+
if (this.settings?.prewarm !== true || this.shaderLanguage == null) {
|
|
23330
|
+
return;
|
|
23331
|
+
}
|
|
23332
|
+
if (this._warmHosts === void 0) {
|
|
23333
|
+
this._warmHosts = /* @__PURE__ */ new Set();
|
|
23334
|
+
}
|
|
23335
|
+
if (this._warmHosts.size === 0) {
|
|
23336
|
+
queueMicrotask(() => {
|
|
23337
|
+
this._flushWarm();
|
|
23338
|
+
});
|
|
23339
|
+
}
|
|
23340
|
+
this._warmHosts.add(renderable);
|
|
23341
|
+
}
|
|
23342
|
+
/**
|
|
23343
|
+
* Run the warm-ups queued by {@link Renderer#_queueWarm}.
|
|
23344
|
+
* @ignore
|
|
23345
|
+
* @internal
|
|
23346
|
+
*/
|
|
23347
|
+
_flushWarm() {
|
|
23348
|
+
const hosts = [...this._warmHosts];
|
|
23349
|
+
this._warmHosts.clear();
|
|
23350
|
+
for (const host of hosts) {
|
|
23351
|
+
try {
|
|
23352
|
+
this._warmRenderable(host);
|
|
23353
|
+
} catch {
|
|
23354
|
+
}
|
|
23355
|
+
}
|
|
23356
|
+
}
|
|
23357
|
+
/**
|
|
23358
|
+
* Warm one renderable, and everything under it when it is a container:
|
|
23359
|
+
* adding a container to the world brings its whole subtree in with it,
|
|
23360
|
+
* without an `addChild` per descendant.
|
|
23361
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23362
|
+
* @ignore
|
|
23363
|
+
* @internal
|
|
23364
|
+
*/
|
|
23365
|
+
_warmRenderable(renderable) {
|
|
23366
|
+
if (renderable == null) {
|
|
23367
|
+
return;
|
|
23368
|
+
}
|
|
23369
|
+
if (renderable.postEffects?.length > 0) {
|
|
23370
|
+
this._warmPostEffects(renderable);
|
|
23371
|
+
}
|
|
23372
|
+
this._warmDraw(renderable);
|
|
23373
|
+
if (isAdvancedBlendMode(renderable.blendMode)) {
|
|
23374
|
+
this._warmAdvancedBlend();
|
|
23375
|
+
}
|
|
23376
|
+
const children = renderable.children;
|
|
23377
|
+
if (Array.isArray(children)) {
|
|
23378
|
+
for (const child of children) {
|
|
23379
|
+
this._warmRenderable(child);
|
|
23380
|
+
}
|
|
23381
|
+
}
|
|
23382
|
+
}
|
|
23383
|
+
/**
|
|
23384
|
+
* Warm everything one renderable's post effects will need. Each backend
|
|
23385
|
+
* knows what that is; this base has no shaders, so nothing.
|
|
23386
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23387
|
+
* @ignore
|
|
23388
|
+
* @internal
|
|
23389
|
+
*/
|
|
23390
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
23391
|
+
_warmPostEffects(renderable) {
|
|
23392
|
+
}
|
|
23393
|
+
/**
|
|
23394
|
+
* Warm the renderable's own draw, the one it makes without any effect.
|
|
23395
|
+
* Only meaningful where that is a pipeline keyed by draw state; nothing
|
|
23396
|
+
* to do on this base.
|
|
23397
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
23398
|
+
* @ignore
|
|
23399
|
+
* @internal
|
|
23400
|
+
*/
|
|
23401
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
23402
|
+
_warmDraw(renderable) {
|
|
23403
|
+
}
|
|
23404
|
+
/**
|
|
23405
|
+
* Warm the advanced blend modes' machinery: the blend shader, built on
|
|
23406
|
+
* the first draw that uses one of those modes, and its offscreen target.
|
|
23407
|
+
* Nothing to do on this base.
|
|
23408
|
+
* @ignore
|
|
23409
|
+
* @internal
|
|
23410
|
+
*/
|
|
23411
|
+
_warmAdvancedBlend() {
|
|
23412
|
+
}
|
|
23413
|
+
/**
|
|
23414
|
+
* Whether a renderable draws through `drawMesh`, where a lone effect is
|
|
23415
|
+
* spliced into the mesh shader instead of being drawn as a quad.
|
|
23416
|
+
* Duck-typed on fields only a `Mesh` carries, so the renderer does not
|
|
23417
|
+
* have to import the renderable classes. An instanced mesh never hosts
|
|
23418
|
+
* an effect: its shading is a separate family that knows nothing of one.
|
|
23419
|
+
* @param {Renderable} renderable - the renderable to test
|
|
23420
|
+
* @returns {boolean} true for a non-instanced mesh
|
|
23421
|
+
* @ignore
|
|
23422
|
+
* @internal
|
|
23423
|
+
*/
|
|
23424
|
+
_hostsMeshEffect(renderable) {
|
|
23425
|
+
return typeof renderable.cullBackFaces === "boolean" && renderable.indices !== void 0 && renderable.instanceLayout === void 0;
|
|
23426
|
+
}
|
|
23427
|
+
/**
|
|
23428
|
+
* Warm the tone curve's pipeline once it exists. Nothing to do on a
|
|
23429
|
+
* backend that compiles it in its constructor.
|
|
23430
|
+
* @ignore
|
|
23431
|
+
* @internal
|
|
23432
|
+
*/
|
|
23433
|
+
_warmToneMap() {
|
|
23434
|
+
}
|
|
23099
23435
|
/**
|
|
23100
23436
|
* Set the tone curve applied at the end of the camera's chain.
|
|
23101
23437
|
*
|
|
@@ -23172,6 +23508,7 @@ var Renderer = class _Renderer {
|
|
|
23172
23508
|
this._toneMapEffect?.destroy?.();
|
|
23173
23509
|
this._toneMapEffect = new ToneMappingEffect(this, { mode });
|
|
23174
23510
|
this._toneMapEffect.shared = true;
|
|
23511
|
+
this._warmToneMap();
|
|
23175
23512
|
}
|
|
23176
23513
|
this._toneMapEffect.setExposure(this.settings.toneMappingExposure ?? 1);
|
|
23177
23514
|
this._toneMapEffect.setWhite(this.settings.toneMappingWhite ?? 0);
|
|
@@ -28585,6 +28922,7 @@ var Container = class _Container extends Renderable {
|
|
|
28585
28922
|
registerChildBody(cchild, worldContainer);
|
|
28586
28923
|
});
|
|
28587
28924
|
}
|
|
28925
|
+
worldContainer?.app?.renderer?._queueWarm?.(child);
|
|
28588
28926
|
}
|
|
28589
28927
|
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
|
|
28590
28928
|
child.onActivateEvent();
|
|
@@ -28627,6 +28965,7 @@ var Container = class _Container extends Renderable {
|
|
|
28627
28965
|
registerChildBody(cchild, worldContainer);
|
|
28628
28966
|
});
|
|
28629
28967
|
}
|
|
28968
|
+
worldContainer?.app?.renderer?._queueWarm?.(child);
|
|
28630
28969
|
}
|
|
28631
28970
|
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
|
|
28632
28971
|
child.onActivateEvent();
|
|
@@ -41909,6 +42248,23 @@ var WebGLIndexBuffer = class extends IndexBuffer {
|
|
|
41909
42248
|
}
|
|
41910
42249
|
};
|
|
41911
42250
|
|
|
42251
|
+
// src/video/webgl/buffer/deletable.js
|
|
42252
|
+
var lostOnce = /* @__PURE__ */ new WeakSet();
|
|
42253
|
+
function markContextLost(gl) {
|
|
42254
|
+
if (gl) {
|
|
42255
|
+
lostOnce.add(gl);
|
|
42256
|
+
}
|
|
42257
|
+
}
|
|
42258
|
+
function deletable(gl, probe, handle) {
|
|
42259
|
+
if (gl.isContextLost() === true) {
|
|
42260
|
+
return false;
|
|
42261
|
+
}
|
|
42262
|
+
if (lostOnce.has(gl) === false) {
|
|
42263
|
+
return true;
|
|
42264
|
+
}
|
|
42265
|
+
return probe.call(gl, handle) === true;
|
|
42266
|
+
}
|
|
42267
|
+
|
|
41912
42268
|
// src/video/webgl/buffer/vertexstate.js
|
|
41913
42269
|
function bufferGroups(descriptor) {
|
|
41914
42270
|
if (Array.isArray(descriptor.buffers)) {
|
|
@@ -42057,12 +42413,15 @@ var WebGLVertexState = class {
|
|
|
42057
42413
|
/**
|
|
42058
42414
|
* Delete the underlying vertex array object, if any. Idempotent, and
|
|
42059
42415
|
* safe on a lost context: deleting an object belonging to a lost
|
|
42060
|
-
* context would queue `INVALID_OPERATION
|
|
42061
|
-
* it
|
|
42416
|
+
* context would queue `INVALID_OPERATION`, because deletion counts as
|
|
42417
|
+
* *using* it.
|
|
42418
|
+
*
|
|
42419
|
+
* The probe behind `deletable` is synchronous, so it is skipped unless
|
|
42420
|
+
* this context has actually lost one — see `buffer/deletable.js`.
|
|
42062
42421
|
*/
|
|
42063
42422
|
release() {
|
|
42064
42423
|
if (this.handle !== null) {
|
|
42065
|
-
if (this.gl.isVertexArray
|
|
42424
|
+
if (deletable(this.gl, this.gl.isVertexArray, this.handle)) {
|
|
42066
42425
|
this.gl.deleteVertexArray(this.handle);
|
|
42067
42426
|
}
|
|
42068
42427
|
this.handle = null;
|
|
@@ -43054,6 +43413,14 @@ function transformQuadCorners(m, x, y, w, h, z) {
|
|
|
43054
43413
|
}
|
|
43055
43414
|
return corners;
|
|
43056
43415
|
}
|
|
43416
|
+
var scaled = new Float64Array(4);
|
|
43417
|
+
function scaleQuad(x, y, w, h, sx, sy) {
|
|
43418
|
+
scaled[0] = x + w * (1 - sx) / 2;
|
|
43419
|
+
scaled[1] = y + h * (1 - sy) / 2;
|
|
43420
|
+
scaled[2] = w * sx;
|
|
43421
|
+
scaled[3] = h * sy;
|
|
43422
|
+
return scaled;
|
|
43423
|
+
}
|
|
43057
43424
|
|
|
43058
43425
|
// src/video/webgl/shaders/multitexture.js
|
|
43059
43426
|
function buildMultiTextureFragment(maxTextures) {
|
|
@@ -43092,6 +43459,8 @@ var immutableStorage = /* @__PURE__ */ new WeakMap();
|
|
|
43092
43459
|
function mipLevels(w, h) {
|
|
43093
43460
|
return Math.floor(Math.log2(Math.max(w, h))) + 1;
|
|
43094
43461
|
}
|
|
43462
|
+
var COMPRESSED_RGB_ETC1_WEBGL3 = 36196;
|
|
43463
|
+
var COMPRESSED_RGB8_ETC22 = 37492;
|
|
43095
43464
|
var MaterialBatcher = class extends WebGLBatcher {
|
|
43096
43465
|
/**
|
|
43097
43466
|
* Initialize the textured batcher
|
|
@@ -43201,11 +43570,20 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43201
43570
|
this.currentTextureUnit = unit;
|
|
43202
43571
|
const resourceOwned = pixels !== null && typeof pixels.upload === "function";
|
|
43203
43572
|
const compressed = pixels !== null && pixels.compressed === true;
|
|
43573
|
+
let compressedFormat = compressed ? pixels.format : void 0;
|
|
43574
|
+
let mutableETC1 = false;
|
|
43575
|
+
if (compressedFormat === COMPRESSED_RGB_ETC1_WEBGL3) {
|
|
43576
|
+
if (this.renderer.getSupportedCompressedTextureFormats().etc2) {
|
|
43577
|
+
compressedFormat = COMPRESSED_RGB8_ETC22;
|
|
43578
|
+
} else {
|
|
43579
|
+
mutableETC1 = true;
|
|
43580
|
+
}
|
|
43581
|
+
}
|
|
43204
43582
|
if (!resourceOwned) {
|
|
43205
43583
|
const storageW = Math.max(1, w | 0);
|
|
43206
43584
|
const storageH = Math.max(1, h | 0);
|
|
43207
43585
|
const levels2 = compressed ? pixels.mipmaps.length : mipmap === true ? mipLevels(storageW, storageH) : 1;
|
|
43208
|
-
const format = compressed ?
|
|
43586
|
+
const format = compressed ? compressedFormat : gl.RGBA8;
|
|
43209
43587
|
let storage = immutableStorage.get(currentTexture);
|
|
43210
43588
|
if (storage && (storage.width !== storageW || storage.height !== storageH || storage.levels !== levels2 || storage.format !== format)) {
|
|
43211
43589
|
gl.deleteTexture(currentTexture);
|
|
@@ -43214,7 +43592,7 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43214
43592
|
this.boundTextures[unit] = currentTexture;
|
|
43215
43593
|
storage = void 0;
|
|
43216
43594
|
}
|
|
43217
|
-
if (!storage) {
|
|
43595
|
+
if (!storage && !mutableETC1) {
|
|
43218
43596
|
gl.texStorage2D(gl.TEXTURE_2D, levels2, format, storageW, storageH);
|
|
43219
43597
|
immutableStorage.set(currentTexture, {
|
|
43220
43598
|
width: storageW,
|
|
@@ -43233,18 +43611,37 @@ var MaterialBatcher = class extends WebGLBatcher {
|
|
|
43233
43611
|
pixels.upload(gl, gl.TEXTURE_2D);
|
|
43234
43612
|
} else if (compressed) {
|
|
43235
43613
|
const mipmaps = pixels.mipmaps;
|
|
43236
|
-
|
|
43237
|
-
gl.
|
|
43614
|
+
if (mutableETC1) {
|
|
43615
|
+
gl.texParameteri(
|
|
43238
43616
|
gl.TEXTURE_2D,
|
|
43239
|
-
|
|
43240
|
-
|
|
43241
|
-
0,
|
|
43242
|
-
mipmaps[i].width,
|
|
43243
|
-
mipmaps[i].height,
|
|
43244
|
-
pixels.format,
|
|
43245
|
-
mipmaps[i].data
|
|
43617
|
+
gl.TEXTURE_MAX_LEVEL,
|
|
43618
|
+
mipmaps.length - 1
|
|
43246
43619
|
);
|
|
43247
43620
|
}
|
|
43621
|
+
for (let i = 0; i < mipmaps.length; i++) {
|
|
43622
|
+
if (mutableETC1) {
|
|
43623
|
+
gl.compressedTexImage2D(
|
|
43624
|
+
gl.TEXTURE_2D,
|
|
43625
|
+
i,
|
|
43626
|
+
compressedFormat,
|
|
43627
|
+
mipmaps[i].width,
|
|
43628
|
+
mipmaps[i].height,
|
|
43629
|
+
0,
|
|
43630
|
+
mipmaps[i].data
|
|
43631
|
+
);
|
|
43632
|
+
} else {
|
|
43633
|
+
gl.compressedTexSubImage2D(
|
|
43634
|
+
gl.TEXTURE_2D,
|
|
43635
|
+
i,
|
|
43636
|
+
0,
|
|
43637
|
+
0,
|
|
43638
|
+
mipmaps[i].width,
|
|
43639
|
+
mipmaps[i].height,
|
|
43640
|
+
compressedFormat,
|
|
43641
|
+
mipmaps[i].data
|
|
43642
|
+
);
|
|
43643
|
+
}
|
|
43644
|
+
}
|
|
43248
43645
|
} else if (pixels === null) {
|
|
43249
43646
|
} else if (typeof pixels.byteLength !== "undefined") {
|
|
43250
43647
|
gl.texSubImage2D(
|
|
@@ -43653,6 +44050,14 @@ var QuadBatcher = class extends MaterialBatcher {
|
|
|
43653
44050
|
);
|
|
43654
44051
|
}
|
|
43655
44052
|
this.currentShader._setUVYDir?.(1);
|
|
44053
|
+
const scale2 = this.currentShader._vertexScale;
|
|
44054
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
44055
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
44056
|
+
x = q[0];
|
|
44057
|
+
y = q[1];
|
|
44058
|
+
w = q[2];
|
|
44059
|
+
h = q[3];
|
|
44060
|
+
}
|
|
43656
44061
|
}
|
|
43657
44062
|
const [vec0, vec1, vec2, vec3] = transformQuadCorners(
|
|
43658
44063
|
this.viewMatrix,
|
|
@@ -43670,85 +44075,6 @@ var QuadBatcher = class extends MaterialBatcher {
|
|
|
43670
44075
|
}
|
|
43671
44076
|
};
|
|
43672
44077
|
|
|
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
44078
|
// src/video/effects/blendEffect.js
|
|
43753
44079
|
var wgslFragment2 = `
|
|
43754
44080
|
struct BlendUniforms {
|
|
@@ -44032,6 +44358,26 @@ var RenderTargetPool = class {
|
|
|
44032
44358
|
}
|
|
44033
44359
|
return rt;
|
|
44034
44360
|
}
|
|
44361
|
+
/**
|
|
44362
|
+
* Create, ahead of the pass that will use them, the targets a top-level
|
|
44363
|
+
* chain needs: the camera pair, or the first sprite pair. Targets that
|
|
44364
|
+
* already exist are left as they are (the pass resizes them when it
|
|
44365
|
+
* begins), and the active-pass stack is not touched, so this is safe
|
|
44366
|
+
* outside any pass.
|
|
44367
|
+
* @param {boolean} isCamera - true for the camera pair (0/1), false for the first sprite pair (2/3)
|
|
44368
|
+
* @param {number} effectCount - the number of effects in the chain
|
|
44369
|
+
* @param {number} width - target width in pixels
|
|
44370
|
+
* @param {number} height - target height in pixels
|
|
44371
|
+
*/
|
|
44372
|
+
reserve(isCamera, effectCount, width, height) {
|
|
44373
|
+
const base = isCamera ? 0 : 2;
|
|
44374
|
+
if (!this._pool[base]) {
|
|
44375
|
+
this.get(base, width, height);
|
|
44376
|
+
}
|
|
44377
|
+
if (effectCount > 1 && !this._pool[base + 1]) {
|
|
44378
|
+
this.get(base + 1, width, height);
|
|
44379
|
+
}
|
|
44380
|
+
}
|
|
44035
44381
|
/**
|
|
44036
44382
|
* Get the capture render target for the current active pass.
|
|
44037
44383
|
* @returns {RenderTarget|undefined} the capture target, or undefined if no active pass
|
|
@@ -44751,7 +45097,7 @@ var UniformBlock = class {
|
|
|
44751
45097
|
destroy() {
|
|
44752
45098
|
const gl = this.gl;
|
|
44753
45099
|
if (this.buffer !== null) {
|
|
44754
|
-
if (gl.isBuffer
|
|
45100
|
+
if (deletable(gl, gl.isBuffer, this.buffer)) {
|
|
44755
45101
|
gl.deleteBuffer(this.buffer);
|
|
44756
45102
|
}
|
|
44757
45103
|
this.buffer = null;
|
|
@@ -45004,14 +45350,13 @@ var WebGLInstanceBuffer = class {
|
|
|
45004
45350
|
);
|
|
45005
45351
|
}
|
|
45006
45352
|
/**
|
|
45007
|
-
* Release the GL buffer. Safe to call twice, and on a lost context
|
|
45008
|
-
*
|
|
45009
|
-
* context died raises INVALID_OPERATION.
|
|
45353
|
+
* Release the GL buffer. Safe to call twice, and on a lost context:
|
|
45354
|
+
* deleting a handle whose context died raises INVALID_OPERATION.
|
|
45010
45355
|
*/
|
|
45011
45356
|
destroy() {
|
|
45012
45357
|
const gl = this.gl;
|
|
45013
45358
|
if (this.buffer !== null) {
|
|
45014
|
-
if (gl.isBuffer
|
|
45359
|
+
if (deletable(gl, gl.isBuffer, this.buffer)) {
|
|
45015
45360
|
gl.deleteBuffer(this.buffer);
|
|
45016
45361
|
}
|
|
45017
45362
|
this.buffer = null;
|
|
@@ -45097,13 +45442,13 @@ var RetainedGeometry = class {
|
|
|
45097
45442
|
this.vertexState = null;
|
|
45098
45443
|
}
|
|
45099
45444
|
if (this.vertexBuffer !== null) {
|
|
45100
|
-
if (gl.isBuffer
|
|
45445
|
+
if (deletable(gl, gl.isBuffer, this.vertexBuffer)) {
|
|
45101
45446
|
gl.deleteBuffer(this.vertexBuffer);
|
|
45102
45447
|
}
|
|
45103
45448
|
this.vertexBuffer = null;
|
|
45104
45449
|
}
|
|
45105
45450
|
if (this.glIndexBuffer !== null) {
|
|
45106
|
-
if (gl.isBuffer
|
|
45451
|
+
if (deletable(gl, gl.isBuffer, this.glIndexBuffer)) {
|
|
45107
45452
|
gl.deleteBuffer(this.glIndexBuffer);
|
|
45108
45453
|
}
|
|
45109
45454
|
this.glIndexBuffer = null;
|
|
@@ -45142,6 +45487,14 @@ var _EYE_POSITION = new Float32Array(3);
|
|
|
45142
45487
|
var _FOG_COLOR = new Float32Array(3);
|
|
45143
45488
|
var _FOG_PARAMS = new Float32Array(4);
|
|
45144
45489
|
var _FOG_HEIGHT = new Float32Array(4);
|
|
45490
|
+
function prewarmYield() {
|
|
45491
|
+
if (globalThis.document?.hidden === true) {
|
|
45492
|
+
return Promise.resolve();
|
|
45493
|
+
}
|
|
45494
|
+
return new Promise((resolve) => {
|
|
45495
|
+
setTimeout(resolve, 0);
|
|
45496
|
+
});
|
|
45497
|
+
}
|
|
45145
45498
|
var MeshBatcher = class extends MaterialBatcher {
|
|
45146
45499
|
/**
|
|
45147
45500
|
* Initialize the mesh batcher
|
|
@@ -45725,7 +46078,21 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45725
46078
|
* @internal
|
|
45726
46079
|
*/
|
|
45727
46080
|
effectShaderFor(effect) {
|
|
45728
|
-
|
|
46081
|
+
return this._effectShader(effect, this._fogDefine());
|
|
46082
|
+
}
|
|
46083
|
+
/**
|
|
46084
|
+
* The spliced program for one effect under one fog state.
|
|
46085
|
+
*
|
|
46086
|
+
* Split out of {@link MeshBatcher#effectShaderFor} so `prewarm` can build
|
|
46087
|
+
* BOTH fog variants at load, rather than only the one the current scene
|
|
46088
|
+
* happens to be using.
|
|
46089
|
+
* @param {object} effect - the hosted ShaderEffect
|
|
46090
|
+
* @param {string} fog - the fog define, or ""
|
|
46091
|
+
* @returns {GLShader|null} the spliced program, or null if it will not build
|
|
46092
|
+
* @ignore
|
|
46093
|
+
* @internal
|
|
46094
|
+
*/
|
|
46095
|
+
_effectShader(effect, fog) {
|
|
45729
46096
|
const sources = this._shaderSources();
|
|
45730
46097
|
const fragment2 = spliceEffect(sources.fragment, effect._meshBody);
|
|
45731
46098
|
if (fragment2 === null) {
|
|
@@ -45858,17 +46225,14 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45858
46225
|
* @ignore
|
|
45859
46226
|
* @internal
|
|
45860
46227
|
*/
|
|
45861
|
-
|
|
45862
|
-
|
|
45863
|
-
|
|
45864
|
-
|
|
45865
|
-
|
|
45866
|
-
|
|
45867
|
-
setTimeout(resolve, 0);
|
|
45868
|
-
});
|
|
45869
|
-
};
|
|
46228
|
+
/**
|
|
46229
|
+
* @param {object[]} [effects] - hosted ShaderEffects to warm the spliced
|
|
46230
|
+
* mesh variants for, normally every preloaded `{type: "shader"}` asset
|
|
46231
|
+
*/
|
|
46232
|
+
async prewarm(effects = []) {
|
|
46233
|
+
const yieldToFrame = prewarmYield;
|
|
45870
46234
|
const alive = () => {
|
|
45871
|
-
return this.
|
|
46235
|
+
return this._prewarmAlive();
|
|
45872
46236
|
};
|
|
45873
46237
|
const sources = this._shaderSources();
|
|
45874
46238
|
const instanced = this._instancedShaderSources();
|
|
@@ -45906,6 +46270,54 @@ var MeshBatcher = class extends MaterialBatcher {
|
|
|
45906
46270
|
return;
|
|
45907
46271
|
}
|
|
45908
46272
|
}
|
|
46273
|
+
await this.prewarmEffects(effects);
|
|
46274
|
+
}
|
|
46275
|
+
/**
|
|
46276
|
+
* Build the spliced mesh variant of each effect, in both fog states.
|
|
46277
|
+
*
|
|
46278
|
+
* A ShaderEffect hosted on a mesh does NOT draw with the program its
|
|
46279
|
+
* asset compiled to: the body is spliced into the mesh shader, which is a
|
|
46280
|
+
* different program again, and it was built on the frame the effect first
|
|
46281
|
+
* drew. Preloading the shader looked like it covered this and did not, so
|
|
46282
|
+
* a game that declared every effect in its manifest still stuttered the
|
|
46283
|
+
* first time each one appeared. Warming them here is what makes the
|
|
46284
|
+
* preload mean what it looks like it means.
|
|
46285
|
+
*
|
|
46286
|
+
* Separate from {@link MeshBatcher#prewarm} because the two halves have
|
|
46287
|
+
* different lifetimes: the batcher's own variants are built once, while
|
|
46288
|
+
* the effects grow with every preload.
|
|
46289
|
+
* @param {object[]} [effects] - hosted ShaderEffects, normally the
|
|
46290
|
+
* preloaded `{type: "shader"}` assets
|
|
46291
|
+
* @returns {Promise<void>} settles once every variant is linked
|
|
46292
|
+
* @ignore
|
|
46293
|
+
* @internal
|
|
46294
|
+
*/
|
|
46295
|
+
async prewarmEffects(effects = []) {
|
|
46296
|
+
for (const fogDefine of ["", "#define FOG\n"]) {
|
|
46297
|
+
for (const effect of effects) {
|
|
46298
|
+
if (effect?._meshBody === void 0) {
|
|
46299
|
+
continue;
|
|
46300
|
+
}
|
|
46301
|
+
this._effectShader(effect, fogDefine);
|
|
46302
|
+
await prewarmYield();
|
|
46303
|
+
if (!this._prewarmAlive()) {
|
|
46304
|
+
return;
|
|
46305
|
+
}
|
|
46306
|
+
}
|
|
46307
|
+
}
|
|
46308
|
+
}
|
|
46309
|
+
/**
|
|
46310
|
+
* Whether a warm-up may keep building into this batcher. A yield is a
|
|
46311
|
+
* window in which the application can be destroyed or the context lost,
|
|
46312
|
+
* and building into a batcher that has been torn down leaks a `GLShader`
|
|
46313
|
+
* per remaining variant, each subscribed to the global context-loss
|
|
46314
|
+
* events with nothing left to destroy it.
|
|
46315
|
+
* @returns {boolean} true while the context is live
|
|
46316
|
+
* @ignore
|
|
46317
|
+
* @internal
|
|
46318
|
+
*/
|
|
46319
|
+
_prewarmAlive() {
|
|
46320
|
+
return this.gl !== null && this.gl?.isContextLost?.() !== true;
|
|
45909
46321
|
}
|
|
45910
46322
|
/**
|
|
45911
46323
|
* Whether the bound program is one this batcher would pick for a plain
|
|
@@ -47418,6 +47830,14 @@ var LitQuadBatcher = class extends QuadBatcher {
|
|
|
47418
47830
|
);
|
|
47419
47831
|
}
|
|
47420
47832
|
this.currentShader._setUVYDir?.(1);
|
|
47833
|
+
const scale2 = this.currentShader._vertexScale;
|
|
47834
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
47835
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
47836
|
+
x = q[0];
|
|
47837
|
+
y = q[1];
|
|
47838
|
+
w = q[2];
|
|
47839
|
+
h = q[3];
|
|
47840
|
+
}
|
|
47421
47841
|
}
|
|
47422
47842
|
let normalTextureId = -1;
|
|
47423
47843
|
let shininess = 0;
|
|
@@ -48123,6 +48543,7 @@ function initBlendEnums(gl) {
|
|
|
48123
48543
|
"one-minus-dst-alpha": gl.ONE_MINUS_DST_ALPHA
|
|
48124
48544
|
};
|
|
48125
48545
|
}
|
|
48546
|
+
var MESH_EFFECT_LINK_BUDGET = 4;
|
|
48126
48547
|
var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
48127
48548
|
/**
|
|
48128
48549
|
* @param {ApplicationSettings} [options] - optional parameters for the renderer
|
|
@@ -48214,6 +48635,7 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48214
48635
|
(e) => {
|
|
48215
48636
|
e.preventDefault();
|
|
48216
48637
|
this.isContextValid = false;
|
|
48638
|
+
markContextLost(this.gl);
|
|
48217
48639
|
this.vertexBuffer = null;
|
|
48218
48640
|
emit(ONCONTEXT_LOST, this);
|
|
48219
48641
|
},
|
|
@@ -48252,6 +48674,11 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48252
48674
|
if (renderer === this) {
|
|
48253
48675
|
this.currentProgram = void 0;
|
|
48254
48676
|
this._prewarmed = void 0;
|
|
48677
|
+
this._prewarmedEffects = /* @__PURE__ */ new WeakSet();
|
|
48678
|
+
if (this.settings.prewarm === true) {
|
|
48679
|
+
this.prewarm().catch(() => {
|
|
48680
|
+
});
|
|
48681
|
+
}
|
|
48255
48682
|
}
|
|
48256
48683
|
};
|
|
48257
48684
|
this.onCanvasResize = (width, height) => {
|
|
@@ -48407,18 +48834,181 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48407
48834
|
* @see {@link Renderer#prewarm}
|
|
48408
48835
|
*/
|
|
48409
48836
|
prewarm() {
|
|
48837
|
+
this._prewarmedEffects ??= /* @__PURE__ */ new WeakSet();
|
|
48838
|
+
const effects = Object.values(shaderList).filter((effect) => {
|
|
48839
|
+
return effect != null && !this._prewarmedEffects.has(effect);
|
|
48840
|
+
});
|
|
48841
|
+
for (const effect of effects) {
|
|
48842
|
+
this._prewarmedEffects.add(effect);
|
|
48843
|
+
}
|
|
48844
|
+
const batchers = [...this.batchers.values()];
|
|
48410
48845
|
if (this._prewarmed === void 0) {
|
|
48411
48846
|
this._prewarmed = Promise.all(
|
|
48412
|
-
|
|
48413
|
-
return batcher.prewarm();
|
|
48847
|
+
batchers.map((batcher) => {
|
|
48848
|
+
return batcher.prewarm(effects);
|
|
48414
48849
|
})
|
|
48415
48850
|
).then(() => {
|
|
48416
48851
|
this.setBatcher("quad");
|
|
48417
48852
|
});
|
|
48853
|
+
} else if (effects.length > 0) {
|
|
48854
|
+
this._prewarmed = this._prewarmed.then(() => {
|
|
48855
|
+
return Promise.all(
|
|
48856
|
+
batchers.map((batcher) => {
|
|
48857
|
+
return batcher.prewarmEffects?.(effects);
|
|
48858
|
+
})
|
|
48859
|
+
).then(() => {
|
|
48860
|
+
this.setBatcher("quad");
|
|
48861
|
+
});
|
|
48862
|
+
});
|
|
48418
48863
|
}
|
|
48419
48864
|
return this._prewarmed;
|
|
48420
48865
|
}
|
|
48866
|
+
/**
|
|
48867
|
+
* Warm what one renderable's post effects will need on this backend.
|
|
48868
|
+
*
|
|
48869
|
+
* An effect's own program is linked when the effect is constructed, so
|
|
48870
|
+
* what is left is what depends on where it is drawn: a mesh does not
|
|
48871
|
+
* draw an effect with that program but with the effect spliced into the
|
|
48872
|
+
* mesh shader, built on first draw; and a chain composites through
|
|
48873
|
+
* render targets, built on the frame the chain first runs or grows a
|
|
48874
|
+
* second effect.
|
|
48875
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
48876
|
+
* @override
|
|
48877
|
+
* @ignore
|
|
48878
|
+
* @internal
|
|
48879
|
+
*/
|
|
48880
|
+
_warmPostEffects(renderable) {
|
|
48881
|
+
const gl = this.gl;
|
|
48882
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
48883
|
+
return;
|
|
48884
|
+
}
|
|
48885
|
+
const effects = renderable.postEffects.filter((fx) => {
|
|
48886
|
+
return fx != null;
|
|
48887
|
+
});
|
|
48888
|
+
if (effects.length === 0) {
|
|
48889
|
+
return;
|
|
48890
|
+
}
|
|
48891
|
+
const isCamera = renderable._postEffectManaged === true;
|
|
48892
|
+
if (this._effectPassDepth === 0 && (isCamera || effects.length > 1 || renderable.postEffectNeedsCapture === true)) {
|
|
48893
|
+
const { width, height } = this.getCanvas();
|
|
48894
|
+
this.flush();
|
|
48895
|
+
this._renderTargetPool.reserve(
|
|
48896
|
+
isCamera,
|
|
48897
|
+
this._postEffectCapacity(renderable, effects),
|
|
48898
|
+
width,
|
|
48899
|
+
height
|
|
48900
|
+
);
|
|
48901
|
+
this.invalidateTextureUnit(0);
|
|
48902
|
+
}
|
|
48903
|
+
if (!this._hostsMeshEffect(renderable)) {
|
|
48904
|
+
return;
|
|
48905
|
+
}
|
|
48906
|
+
const batcher = this.batchers.get(
|
|
48907
|
+
renderable.lit === true ? "litMesh" : "mesh"
|
|
48908
|
+
);
|
|
48909
|
+
if (batcher === void 0) {
|
|
48910
|
+
return;
|
|
48911
|
+
}
|
|
48912
|
+
this._meshEffectWarmQueue ??= [];
|
|
48913
|
+
for (const fx of effects) {
|
|
48914
|
+
this._meshEffectWarmQueue.push(batcher, fx);
|
|
48915
|
+
}
|
|
48916
|
+
if (this._meshEffectWarmScheduled !== true) {
|
|
48917
|
+
this._meshEffectWarmScheduled = true;
|
|
48918
|
+
queueMicrotask(() => {
|
|
48919
|
+
this._linkQueuedMeshEffects();
|
|
48920
|
+
});
|
|
48921
|
+
}
|
|
48922
|
+
}
|
|
48923
|
+
/**
|
|
48924
|
+
* Link the spliced mesh programs queued by `_warmPostEffects`, a few
|
|
48925
|
+
* milliseconds' worth per task, so a pool's worth spreads over the next
|
|
48926
|
+
* frames instead of landing in one. A draw that comes first simply links
|
|
48927
|
+
* its own program, as it would have without the warm-up.
|
|
48928
|
+
* @ignore
|
|
48929
|
+
* @internal
|
|
48930
|
+
*/
|
|
48931
|
+
_linkQueuedMeshEffects() {
|
|
48932
|
+
const queue = this._meshEffectWarmQueue;
|
|
48933
|
+
const gl = this.gl;
|
|
48934
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
48935
|
+
queue.length = 0;
|
|
48936
|
+
this._meshEffectWarmScheduled = false;
|
|
48937
|
+
return;
|
|
48938
|
+
}
|
|
48939
|
+
const start = globalThis.performance.now();
|
|
48940
|
+
const previous = this.currentProgram;
|
|
48941
|
+
try {
|
|
48942
|
+
while (queue.length > 0 && globalThis.performance.now() - start < MESH_EFFECT_LINK_BUDGET) {
|
|
48943
|
+
const batcher = queue.shift();
|
|
48944
|
+
const fx = queue.shift();
|
|
48945
|
+
if (fx.destroyed !== true && batcher.canHostEffect?.(fx) === true) {
|
|
48946
|
+
batcher.effectShaderFor(fx);
|
|
48947
|
+
}
|
|
48948
|
+
}
|
|
48949
|
+
} finally {
|
|
48950
|
+
this._restoreProgram(previous);
|
|
48951
|
+
}
|
|
48952
|
+
if (queue.length === 0) {
|
|
48953
|
+
this._meshEffectWarmScheduled = false;
|
|
48954
|
+
} else if (globalThis.document?.hidden === true) {
|
|
48955
|
+
this._linkQueuedMeshEffects();
|
|
48956
|
+
} else {
|
|
48957
|
+
setTimeout(() => {
|
|
48958
|
+
this._linkQueuedMeshEffects();
|
|
48959
|
+
}, 0);
|
|
48960
|
+
}
|
|
48961
|
+
}
|
|
48962
|
+
/**
|
|
48963
|
+
* Build the advanced blend shader and its offscreen target before the
|
|
48964
|
+
* first draw in one of those modes, which otherwise compiles the shader
|
|
48965
|
+
* and creates the target in the middle of that frame.
|
|
48966
|
+
* @override
|
|
48967
|
+
* @ignore
|
|
48968
|
+
* @internal
|
|
48969
|
+
*/
|
|
48970
|
+
_warmAdvancedBlend() {
|
|
48971
|
+
const gl = this.gl;
|
|
48972
|
+
if (gl === null || gl.isContextLost?.() === true) {
|
|
48973
|
+
return;
|
|
48974
|
+
}
|
|
48975
|
+
if (typeof this._advancedBlendEffect === "undefined") {
|
|
48976
|
+
const previous = this.currentProgram;
|
|
48977
|
+
try {
|
|
48978
|
+
this._advancedBlendEffect = new BlendEffect(this);
|
|
48979
|
+
} finally {
|
|
48980
|
+
this._restoreProgram(previous);
|
|
48981
|
+
}
|
|
48982
|
+
}
|
|
48983
|
+
if (typeof this._advancedBlendTarget === "undefined" && this._effectPassDepth === 0 && this._advancedBlendOpen !== true) {
|
|
48984
|
+
const { width, height } = this.getCanvas();
|
|
48985
|
+
this.flush();
|
|
48986
|
+
this._advancedBlendTarget = new WebGLRenderTarget(gl, width, height, {
|
|
48987
|
+
samples: 0,
|
|
48988
|
+
format: gl.RGBA8
|
|
48989
|
+
});
|
|
48990
|
+
this.invalidateTextureUnit(0);
|
|
48991
|
+
}
|
|
48992
|
+
}
|
|
48993
|
+
/**
|
|
48994
|
+
* Put back the program a warm-up found bound. Linking leaves the new
|
|
48995
|
+
* program bound, and the batchers trust what they last bound, so a
|
|
48996
|
+
* warm-up between two draws would otherwise hand the next one a
|
|
48997
|
+
* stranger's program.
|
|
48998
|
+
* @param {WebGLProgram|undefined} previous - what was bound before
|
|
48999
|
+
* @ignore
|
|
49000
|
+
* @internal
|
|
49001
|
+
*/
|
|
49002
|
+
_restoreProgram(previous) {
|
|
49003
|
+
if (previous != null && this.currentProgram !== previous) {
|
|
49004
|
+
this.gl.useProgram(previous);
|
|
49005
|
+
this.currentProgram = previous;
|
|
49006
|
+
}
|
|
49007
|
+
}
|
|
48421
49008
|
destroy() {
|
|
49009
|
+
if (this._meshEffectWarmQueue !== void 0) {
|
|
49010
|
+
this._meshEffectWarmQueue.length = 0;
|
|
49011
|
+
}
|
|
48422
49012
|
this._toneMapEffect?.destroy?.();
|
|
48423
49013
|
this._toneMapEffect = void 0;
|
|
48424
49014
|
off(GAME_RESET, this.onGameReset);
|
|
@@ -48956,7 +49546,12 @@ var WebGLRenderer = class _WebGLRenderer extends Renderer {
|
|
|
48956
49546
|
}
|
|
48957
49547
|
savedProjection.copy(this.projectionMatrix);
|
|
48958
49548
|
this._effectPassDepth++;
|
|
48959
|
-
const rt = this._renderTargetPool.begin(
|
|
49549
|
+
const rt = this._renderTargetPool.begin(
|
|
49550
|
+
isCamera,
|
|
49551
|
+
this._postEffectCapacity(renderable, effects),
|
|
49552
|
+
w,
|
|
49553
|
+
h
|
|
49554
|
+
);
|
|
48960
49555
|
this.invalidateTextureUnit(0);
|
|
48961
49556
|
rt.bind();
|
|
48962
49557
|
this.setViewport(0, 0, w, h);
|
|
@@ -56056,7 +56651,7 @@ var Trigger = class extends Renderable {
|
|
|
56056
56651
|
};
|
|
56057
56652
|
|
|
56058
56653
|
// src/version.ts
|
|
56059
|
-
var version = "20.
|
|
56654
|
+
var version = "20.10.0";
|
|
56060
56655
|
|
|
56061
56656
|
// src/system/bootstrap.ts
|
|
56062
56657
|
var initialized = false;
|
|
@@ -58499,6 +59094,8 @@ function gpuBlendState(mode, premultipliedAlpha) {
|
|
|
58499
59094
|
return typeof component === "undefined" ? void 0 : { color: component, alpha: component };
|
|
58500
59095
|
}
|
|
58501
59096
|
var MAX_QUAD_TEXTURES = 8;
|
|
59097
|
+
var MAX_WARM_IN_FLIGHT = 16;
|
|
59098
|
+
var MISS_KEYS_KEPT = 64;
|
|
58502
59099
|
var DEPTH_STENCIL_FORMAT = "depth24plus-stencil8";
|
|
58503
59100
|
var STENCIL_STATES = {
|
|
58504
59101
|
none: {
|
|
@@ -58575,6 +59172,15 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58575
59172
|
this.sampleCount = 1;
|
|
58576
59173
|
this.pipelines = /* @__PURE__ */ new Map();
|
|
58577
59174
|
this.vertexLayouts = /* @__PURE__ */ new Map();
|
|
59175
|
+
this.inFrame = false;
|
|
59176
|
+
this.inFrameMisses = 0;
|
|
59177
|
+
this.inFrameMissKeys = [];
|
|
59178
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
59179
|
+
this.warmQueue = [];
|
|
59180
|
+
this.warmInFlight = 0;
|
|
59181
|
+
this.touchQueue = /* @__PURE__ */ new Map();
|
|
59182
|
+
this.scratchTargets = /* @__PURE__ */ new Map();
|
|
59183
|
+
this.generation = 0;
|
|
58578
59184
|
this.frameLayout = device.createBindGroupLayout({
|
|
58579
59185
|
label: "melonJS frame globals layout",
|
|
58580
59186
|
entries: [
|
|
@@ -58800,24 +59406,33 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58800
59406
|
);
|
|
58801
59407
|
}
|
|
58802
59408
|
/**
|
|
58803
|
-
*
|
|
58804
|
-
*
|
|
59409
|
+
* The cache key for a pipeline state tuple.
|
|
59410
|
+
*
|
|
59411
|
+
* The ONE place a key is composed. A draw looks its pipeline up through
|
|
59412
|
+
* {@link WebGPUPipelineCache#get} and a warm-up builds one ahead of time
|
|
59413
|
+
* through {@link WebGPUPipelineCache#warm}, and both come here, so the two
|
|
59414
|
+
* cannot drift into building the same pipeline under two names: a warm-up
|
|
59415
|
+
* whose key differs from the draw's by one character compiles a pipeline
|
|
59416
|
+
* nothing will ever ask for, and the draw still compiles its own.
|
|
59417
|
+
*
|
|
59418
|
+
* The colour format and sample count default to the cache's CURRENT ones,
|
|
59419
|
+
* which the renderer rewrites per render target. A warm-up runs outside
|
|
59420
|
+
* any pass, where those describe whatever was bound last, so it passes
|
|
59421
|
+
* the target it is warming for explicitly.
|
|
59422
|
+
* @param {string} shaderKey - "quad" | "primitive" | "clear" | a registered family
|
|
58805
59423
|
* @param {string} topology - portable topology name ("triangle-list", …)
|
|
58806
59424
|
* @param {string} blendMode - blend mode (normalized internally)
|
|
58807
59425
|
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
58808
59426
|
* @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
|
|
59427
|
+
* @param {object} [meshState] - the mesh pass state, see {@link WebGPUPipelineCache#get}
|
|
59428
|
+
* @param {string} [format] - colour target format, the current one by default
|
|
59429
|
+
* @param {number} [sampleCount] - pass sample count, the current one by default
|
|
59430
|
+
* @returns {string} the key
|
|
58816
59431
|
*/
|
|
58817
|
-
|
|
59432
|
+
keyFor(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode = "none", meshState, format = this.format, sampleCount = this.sampleCount) {
|
|
58818
59433
|
const blend = normalizeBlendMode(blendMode);
|
|
58819
|
-
const pma = premultipliedAlpha !== false;
|
|
58820
|
-
let key = `${shaderKey}|${topology}|${blend}|${pma ? 1 : 0}|${stencilMode}|${
|
|
59434
|
+
const pma = blend === "none" || premultipliedAlpha !== false;
|
|
59435
|
+
let key = `${shaderKey}|${topology}|${blend}|${pma ? 1 : 0}|${stencilMode}|${format}|${sampleCount}`;
|
|
58821
59436
|
if (meshState) {
|
|
58822
59437
|
key += `|mesh:${meshState.cullMode}:${meshState.frontFace}`;
|
|
58823
59438
|
if (meshState.depthWrite === false) {
|
|
@@ -58830,32 +59445,257 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58830
59445
|
key += "|fog";
|
|
58831
59446
|
}
|
|
58832
59447
|
}
|
|
58833
|
-
|
|
59448
|
+
return key;
|
|
59449
|
+
}
|
|
59450
|
+
/**
|
|
59451
|
+
* Fetch (building on first use) the pipeline for the given state tuple.
|
|
59452
|
+
* @param {string} shaderKey - "quad" | "primitive" | "clear"
|
|
59453
|
+
* @param {string} topology - portable topology name ("triangle-list", …)
|
|
59454
|
+
* @param {string} blendMode - blend mode (normalized internally)
|
|
59455
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59456
|
+
* @param {string} [stencilMode="none"] - "none" | "write" | "test" | "tag" | "mark"
|
|
59457
|
+
* @param {{cullMode: string, frontFace: string, depthWrite?: boolean, depthTest?: boolean, fog?: boolean}} [meshState] - mesh pass
|
|
59458
|
+
* state: its presence switches the depth half of the attachment on —
|
|
59459
|
+
* depth writes enabled, "less-equal" testing (the GL mesh mode's
|
|
59460
|
+
* LEQUAL, keeping coplanar geometry stable) — and sets the per-mesh
|
|
59461
|
+
* face-culling axes. Omitted by the whole 2D tier, whose keys and
|
|
59462
|
+
* descriptors stay byte-identical to a build without a mesh path.
|
|
59463
|
+
* @returns {GPURenderPipeline} the pipeline
|
|
59464
|
+
*/
|
|
59465
|
+
get(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode = "none", meshState) {
|
|
59466
|
+
const key = this.keyFor(
|
|
59467
|
+
shaderKey,
|
|
59468
|
+
topology,
|
|
59469
|
+
blendMode,
|
|
59470
|
+
premultipliedAlpha,
|
|
59471
|
+
stencilMode,
|
|
59472
|
+
meshState
|
|
59473
|
+
);
|
|
58834
59474
|
let pipeline = this.pipelines.get(key);
|
|
58835
59475
|
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
59476
|
pipeline = this.device.createRenderPipeline(
|
|
58849
|
-
this._descriptor(
|
|
58850
|
-
|
|
59477
|
+
this._descriptor(
|
|
59478
|
+
key,
|
|
59479
|
+
shaderKey,
|
|
59480
|
+
topology,
|
|
59481
|
+
blendMode,
|
|
59482
|
+
premultipliedAlpha,
|
|
59483
|
+
stencilMode,
|
|
58851
59484
|
meshState,
|
|
58852
|
-
|
|
58853
|
-
|
|
59485
|
+
this.format,
|
|
59486
|
+
this.sampleCount
|
|
59487
|
+
)
|
|
58854
59488
|
);
|
|
58855
59489
|
this.pipelines.set(key, pipeline);
|
|
59490
|
+
if (this.inFrame === true) {
|
|
59491
|
+
this.inFrameMisses++;
|
|
59492
|
+
this.inFrameMissKeys.push(key);
|
|
59493
|
+
if (this.inFrameMissKeys.length > MISS_KEYS_KEPT) {
|
|
59494
|
+
this.inFrameMissKeys.shift();
|
|
59495
|
+
}
|
|
59496
|
+
}
|
|
58856
59497
|
}
|
|
58857
59498
|
return pipeline;
|
|
58858
59499
|
}
|
|
59500
|
+
/**
|
|
59501
|
+
* Build a pipeline ahead of the draw that will need it, without blocking.
|
|
59502
|
+
*
|
|
59503
|
+
* Takes the same tuple as {@link WebGPUPipelineCache#get}, plus the colour
|
|
59504
|
+
* format and sample count of the target the draw will land on, since a
|
|
59505
|
+
* warm-up runs outside any pass. Compiles through
|
|
59506
|
+
* `createRenderPipelineAsync` where the device has it, so the work
|
|
59507
|
+
* happens off the frame, and files the result under the same key `get`
|
|
59508
|
+
* will look up. Asking twice for one pipeline compiles it once.
|
|
59509
|
+
*
|
|
59510
|
+
* Never rejects. A pipeline that fails to build is left for the draw to
|
|
59511
|
+
* build and report, exactly as it would have without the warm-up.
|
|
59512
|
+
* @param {string} shaderKey - the family, already registered
|
|
59513
|
+
* @param {string} topology - portable topology name
|
|
59514
|
+
* @param {string} blendMode - blend mode
|
|
59515
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59516
|
+
* @param {string} stencilMode - stencil mode
|
|
59517
|
+
* @param {object|undefined} meshState - mesh pass state, read NOW: a later
|
|
59518
|
+
* change to the object does not reach the pipeline being built
|
|
59519
|
+
* @param {string} format - colour target format the draw will use
|
|
59520
|
+
* @param {number} sampleCount - sample count the draw will use
|
|
59521
|
+
* @returns {Promise<void>} settles once the pipeline is in the cache, or
|
|
59522
|
+
* has failed to get there
|
|
59523
|
+
*/
|
|
59524
|
+
warm(shaderKey, topology, blendMode, premultipliedAlpha, stencilMode, meshState, format, sampleCount) {
|
|
59525
|
+
const key = this.keyFor(
|
|
59526
|
+
shaderKey,
|
|
59527
|
+
topology,
|
|
59528
|
+
blendMode,
|
|
59529
|
+
premultipliedAlpha,
|
|
59530
|
+
stencilMode,
|
|
59531
|
+
meshState,
|
|
59532
|
+
format,
|
|
59533
|
+
sampleCount
|
|
59534
|
+
);
|
|
59535
|
+
if (this.pipelines.has(key)) {
|
|
59536
|
+
return Promise.resolve();
|
|
59537
|
+
}
|
|
59538
|
+
const inFlight = this.pending.get(key);
|
|
59539
|
+
if (inFlight !== void 0) {
|
|
59540
|
+
return inFlight;
|
|
59541
|
+
}
|
|
59542
|
+
const descriptor = this._descriptor(
|
|
59543
|
+
key,
|
|
59544
|
+
shaderKey,
|
|
59545
|
+
topology,
|
|
59546
|
+
blendMode,
|
|
59547
|
+
premultipliedAlpha,
|
|
59548
|
+
stencilMode,
|
|
59549
|
+
meshState,
|
|
59550
|
+
format,
|
|
59551
|
+
sampleCount
|
|
59552
|
+
);
|
|
59553
|
+
const generation = this.generation;
|
|
59554
|
+
const promise = new Promise((resolve) => {
|
|
59555
|
+
this.warmQueue.push({
|
|
59556
|
+
run: () => {
|
|
59557
|
+
return typeof this.device.createRenderPipelineAsync === "function" ? this.device.createRenderPipelineAsync(descriptor) : this.device.createRenderPipeline(descriptor);
|
|
59558
|
+
},
|
|
59559
|
+
resolve: (pipeline) => {
|
|
59560
|
+
this.pending.delete(key);
|
|
59561
|
+
if (pipeline !== void 0 && generation === this.generation && !this.pipelines.has(key)) {
|
|
59562
|
+
this.pipelines.set(key, pipeline);
|
|
59563
|
+
this._touch(pipeline, format, sampleCount);
|
|
59564
|
+
}
|
|
59565
|
+
resolve();
|
|
59566
|
+
}
|
|
59567
|
+
});
|
|
59568
|
+
});
|
|
59569
|
+
this.pending.set(key, promise);
|
|
59570
|
+
this._pumpWarmQueue();
|
|
59571
|
+
return promise;
|
|
59572
|
+
}
|
|
59573
|
+
/**
|
|
59574
|
+
* Start queued warm-ups until {@link MAX_WARM_IN_FLIGHT} are compiling.
|
|
59575
|
+
* @ignore
|
|
59576
|
+
* @internal
|
|
59577
|
+
*/
|
|
59578
|
+
_pumpWarmQueue() {
|
|
59579
|
+
while (this.warmInFlight < MAX_WARM_IN_FLIGHT && this.warmQueue.length > 0) {
|
|
59580
|
+
const job = this.warmQueue.shift();
|
|
59581
|
+
this.warmInFlight++;
|
|
59582
|
+
Promise.resolve().then(job.run).then(job.resolve, () => {
|
|
59583
|
+
job.resolve(void 0);
|
|
59584
|
+
}).finally(() => {
|
|
59585
|
+
this.warmInFlight--;
|
|
59586
|
+
this._pumpWarmQueue();
|
|
59587
|
+
});
|
|
59588
|
+
}
|
|
59589
|
+
}
|
|
59590
|
+
/**
|
|
59591
|
+
* Bind a freshly warmed pipeline once, in a pass of its own, so it is
|
|
59592
|
+
* compiled now rather than at the first draw that uses it.
|
|
59593
|
+
*
|
|
59594
|
+
* Some implementations accept `createRenderPipelineAsync` and still defer
|
|
59595
|
+
* the native compile to the first `setPipeline`, which puts it right back
|
|
59596
|
+
* in the frame the warm-up was meant to keep it out of. Setting it on a
|
|
59597
|
+
* throwaway 1×1 pass makes that first use happen here, while nothing is
|
|
59598
|
+
* on screen waiting for it. Where the compile already happened this is
|
|
59599
|
+
* one cheap bind.
|
|
59600
|
+
* @param {GPURenderPipeline} pipeline - the pipeline to bind
|
|
59601
|
+
* @param {string} format - its colour format
|
|
59602
|
+
* @param {number} sampleCount - its sample count
|
|
59603
|
+
* @ignore
|
|
59604
|
+
* @internal
|
|
59605
|
+
*/
|
|
59606
|
+
_touch(pipeline, format, sampleCount) {
|
|
59607
|
+
const target = `${format}|${sampleCount}`;
|
|
59608
|
+
let batch = this.touchQueue.get(target);
|
|
59609
|
+
if (batch === void 0) {
|
|
59610
|
+
batch = [];
|
|
59611
|
+
this.touchQueue.set(target, batch);
|
|
59612
|
+
}
|
|
59613
|
+
batch.push(pipeline);
|
|
59614
|
+
if (this.touchQueue.size === 1 && batch.length === 1) {
|
|
59615
|
+
queueMicrotask(() => {
|
|
59616
|
+
this._flushTouches();
|
|
59617
|
+
});
|
|
59618
|
+
}
|
|
59619
|
+
}
|
|
59620
|
+
/**
|
|
59621
|
+
* Record and submit the passes queued by {@link WebGPUPipelineCache#_touch}.
|
|
59622
|
+
* @ignore
|
|
59623
|
+
* @internal
|
|
59624
|
+
*/
|
|
59625
|
+
_flushTouches() {
|
|
59626
|
+
const device = this.device;
|
|
59627
|
+
const batches = [...this.touchQueue];
|
|
59628
|
+
this.touchQueue.clear();
|
|
59629
|
+
if (typeof device.createCommandEncoder !== "function") {
|
|
59630
|
+
return;
|
|
59631
|
+
}
|
|
59632
|
+
try {
|
|
59633
|
+
const encoder = device.createCommandEncoder({
|
|
59634
|
+
label: "melonJS pipeline warm-up"
|
|
59635
|
+
});
|
|
59636
|
+
for (const [target, pipelines] of batches) {
|
|
59637
|
+
const [format, samples] = target.split("|");
|
|
59638
|
+
const scratch = this._scratchTarget(format, Number(samples));
|
|
59639
|
+
const pass = encoder.beginRenderPass({
|
|
59640
|
+
label: "melonJS pipeline warm-up",
|
|
59641
|
+
colorAttachments: [
|
|
59642
|
+
{ view: scratch.colorView, loadOp: "clear", storeOp: "discard" }
|
|
59643
|
+
],
|
|
59644
|
+
depthStencilAttachment: {
|
|
59645
|
+
view: scratch.depthView,
|
|
59646
|
+
depthClearValue: 1,
|
|
59647
|
+
depthLoadOp: "clear",
|
|
59648
|
+
depthStoreOp: "discard",
|
|
59649
|
+
stencilLoadOp: "clear",
|
|
59650
|
+
stencilStoreOp: "discard"
|
|
59651
|
+
}
|
|
59652
|
+
});
|
|
59653
|
+
for (const pipeline of pipelines) {
|
|
59654
|
+
pass.setPipeline(pipeline);
|
|
59655
|
+
}
|
|
59656
|
+
pass.end();
|
|
59657
|
+
}
|
|
59658
|
+
device.queue.submit([encoder.finish()]);
|
|
59659
|
+
} catch {
|
|
59660
|
+
}
|
|
59661
|
+
}
|
|
59662
|
+
/**
|
|
59663
|
+
* The 1×1 colour + depth-stencil pair a warm-up pass binds for one
|
|
59664
|
+
* target shape, built once per shape.
|
|
59665
|
+
* @param {string} format - colour format
|
|
59666
|
+
* @param {number} sampleCount - sample count
|
|
59667
|
+
* @returns {{colorView: GPUTextureView, depthView: GPUTextureView}} the views
|
|
59668
|
+
* @ignore
|
|
59669
|
+
* @internal
|
|
59670
|
+
*/
|
|
59671
|
+
_scratchTarget(format, sampleCount) {
|
|
59672
|
+
const key = `${format}|${sampleCount}`;
|
|
59673
|
+
let scratch = this.scratchTargets.get(key);
|
|
59674
|
+
if (scratch === void 0) {
|
|
59675
|
+
const color = this.device.createTexture({
|
|
59676
|
+
label: "melonJS warm-up target",
|
|
59677
|
+
size: [1, 1],
|
|
59678
|
+
format,
|
|
59679
|
+
sampleCount,
|
|
59680
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
59681
|
+
});
|
|
59682
|
+
const depth = this.device.createTexture({
|
|
59683
|
+
label: "melonJS warm-up depth",
|
|
59684
|
+
size: [1, 1],
|
|
59685
|
+
format: DEPTH_STENCIL_FORMAT,
|
|
59686
|
+
sampleCount,
|
|
59687
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
59688
|
+
});
|
|
59689
|
+
scratch = {
|
|
59690
|
+
color,
|
|
59691
|
+
depth,
|
|
59692
|
+
colorView: color.createView(),
|
|
59693
|
+
depthView: depth.createView()
|
|
59694
|
+
};
|
|
59695
|
+
this.scratchTargets.set(key, scratch);
|
|
59696
|
+
}
|
|
59697
|
+
return scratch;
|
|
59698
|
+
}
|
|
58859
59699
|
/**
|
|
58860
59700
|
* Assemble a pipeline descriptor.
|
|
58861
59701
|
*
|
|
@@ -58866,17 +59706,33 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58866
59706
|
* silently builds a second one under a key nothing will hit again.
|
|
58867
59707
|
* @param {string} key - the cache key, used as the label
|
|
58868
59708
|
* @param {string} shaderKey - the module family
|
|
58869
|
-
* @param {string}
|
|
58870
|
-
* @param {
|
|
58871
|
-
* @param {
|
|
58872
|
-
* @param {
|
|
58873
|
-
* @param {object}
|
|
59709
|
+
* @param {string} topology - portable topology name
|
|
59710
|
+
* @param {string} blendMode - blend mode (normalized here)
|
|
59711
|
+
* @param {boolean} premultipliedAlpha - source premultiplication flag
|
|
59712
|
+
* @param {string} stencilMode - stencil mode
|
|
59713
|
+
* @param {object|undefined} meshState - mesh pass state
|
|
59714
|
+
* @param {string} format - colour target format
|
|
59715
|
+
* @param {number} sampleCount - pass sample count
|
|
58874
59716
|
* @returns {GPURenderPipelineDescriptor} the descriptor
|
|
58875
59717
|
* @ignore
|
|
58876
59718
|
* @internal
|
|
58877
59719
|
*/
|
|
58878
|
-
_descriptor(key, shaderKey,
|
|
58879
|
-
const
|
|
59720
|
+
_descriptor(key, shaderKey, topology, blendMode, premultipliedAlpha, stencilMode, meshState, format, sampleCount) {
|
|
59721
|
+
const blend = normalizeBlendMode(blendMode);
|
|
59722
|
+
const pma = premultipliedAlpha !== false;
|
|
59723
|
+
const stencil = STENCIL_STATES[stencilMode] ?? STENCIL_STATES.none;
|
|
59724
|
+
const vertexLayout = this.vertexLayouts.get(
|
|
59725
|
+
this.vertexLayoutAliases.get(shaderKey) ?? shaderKey
|
|
59726
|
+
);
|
|
59727
|
+
const primitive = {
|
|
59728
|
+
topology,
|
|
59729
|
+
stripIndexFormat: topology.endsWith("-strip") ? "uint32" : void 0
|
|
59730
|
+
};
|
|
59731
|
+
if (meshState) {
|
|
59732
|
+
primitive.cullMode = meshState.cullMode;
|
|
59733
|
+
primitive.frontFace = meshState.frontFace;
|
|
59734
|
+
}
|
|
59735
|
+
const fogFlag = meshState?.fog === true ? 1 : 0;
|
|
58880
59736
|
return {
|
|
58881
59737
|
label: `melonJS ${key}`,
|
|
58882
59738
|
layout: this.pipelineLayouts[shaderKey],
|
|
@@ -58892,7 +59748,7 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58892
59748
|
...meshState ? { constants: { enable_fog: fogFlag } } : {},
|
|
58893
59749
|
targets: [
|
|
58894
59750
|
{
|
|
58895
|
-
format
|
|
59751
|
+
format,
|
|
58896
59752
|
blend: shaderKey === "clear" ? void 0 : gpuBlendState(blend, pma),
|
|
58897
59753
|
writeMask: stencil.colorWriteMask
|
|
58898
59754
|
}
|
|
@@ -58915,7 +59771,7 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58915
59771
|
stencilReadMask: stencil.readMask,
|
|
58916
59772
|
stencilWriteMask: stencil.writeMask
|
|
58917
59773
|
},
|
|
58918
|
-
multisample: { count:
|
|
59774
|
+
multisample: { count: sampleCount }
|
|
58919
59775
|
};
|
|
58920
59776
|
}
|
|
58921
59777
|
/**
|
|
@@ -58924,6 +59780,17 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
|
|
|
58924
59780
|
*/
|
|
58925
59781
|
clear() {
|
|
58926
59782
|
this.pipelines.clear();
|
|
59783
|
+
this.generation++;
|
|
59784
|
+
for (const job of this.warmQueue.splice(0)) {
|
|
59785
|
+
job.resolve(void 0);
|
|
59786
|
+
}
|
|
59787
|
+
this.pending.clear();
|
|
59788
|
+
this.touchQueue.clear();
|
|
59789
|
+
for (const scratch of this.scratchTargets.values()) {
|
|
59790
|
+
scratch.color.destroy?.();
|
|
59791
|
+
scratch.depth.destroy?.();
|
|
59792
|
+
}
|
|
59793
|
+
this.scratchTargets.clear();
|
|
58927
59794
|
}
|
|
58928
59795
|
};
|
|
58929
59796
|
|
|
@@ -59049,7 +59916,13 @@ var WebGPUQuadBatcher = class extends WebGPUBatcher {
|
|
|
59049
59916
|
Math.min(u0, u1),
|
|
59050
59917
|
Math.min(v0, v1)
|
|
59051
59918
|
);
|
|
59052
|
-
|
|
59919
|
+
const scale2 = effect._vertexScale;
|
|
59920
|
+
if (scale2 !== void 0 && (scale2.x !== 1 || scale2.y !== 1)) {
|
|
59921
|
+
const q = scaleQuad(x, y, w, h, scale2.x, scale2.y);
|
|
59922
|
+
this.pushQuadVertices(q[0], q[1], q[2], q[3], u0, v0, u1, v1, tint, 0);
|
|
59923
|
+
} else {
|
|
59924
|
+
this.pushQuadVertices(x, y, w, h, u0, v0, u1, v1, tint, 0);
|
|
59925
|
+
}
|
|
59053
59926
|
this.flush();
|
|
59054
59927
|
return;
|
|
59055
59928
|
}
|
|
@@ -60708,7 +61581,11 @@ function uploadCompressedTexture(device, texture, image, metrics) {
|
|
|
60708
61581
|
bytesPerRow: blocksPerRow * metrics.bytes,
|
|
60709
61582
|
rowsPerImage: rows
|
|
60710
61583
|
},
|
|
60711
|
-
|
|
61584
|
+
// whole blocks: a level smaller than one block (the 2×2 and 1×1
|
|
61585
|
+
// tail of a 4×4-block chain) is stored padded to a full block,
|
|
61586
|
+
// and a copy of its logical size fails validation, leaving the
|
|
61587
|
+
// level empty
|
|
61588
|
+
[blocksPerRow * metrics.blockW, rows * metrics.blockH]
|
|
60712
61589
|
);
|
|
60713
61590
|
}
|
|
60714
61591
|
}
|
|
@@ -60978,6 +61855,71 @@ var WebGPUTextureStore = class extends TextureStore {
|
|
|
60978
61855
|
this.lastRecord = record;
|
|
60979
61856
|
return this.bindGroupFor(record, texture, wrap, options.mipmaps === true);
|
|
60980
61857
|
}
|
|
61858
|
+
/**
|
|
61859
|
+
* Build the mip-chain blitter, once. Split out of
|
|
61860
|
+
* {@link WebGPUTextureStore#generateMipmaps} so the warm-up can build it
|
|
61861
|
+
* before the first mipmapped texture is uploaded, which is otherwise in
|
|
61862
|
+
* the middle of the frame that first draws a mesh.
|
|
61863
|
+
* @ignore
|
|
61864
|
+
* @internal
|
|
61865
|
+
*/
|
|
61866
|
+
ensureMipPipeline() {
|
|
61867
|
+
if (typeof this.mipPipeline !== "undefined") {
|
|
61868
|
+
return;
|
|
61869
|
+
}
|
|
61870
|
+
const device = this.device;
|
|
61871
|
+
const module = device.createShaderModule({
|
|
61872
|
+
label: "melonJS mip blit",
|
|
61873
|
+
code: mipblit_default
|
|
61874
|
+
});
|
|
61875
|
+
this.mipLayout = device.createBindGroupLayout({
|
|
61876
|
+
label: "melonJS mip blit layout",
|
|
61877
|
+
entries: [
|
|
61878
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
61879
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
|
|
61880
|
+
]
|
|
61881
|
+
});
|
|
61882
|
+
this.mipPipeline = device.createRenderPipeline({
|
|
61883
|
+
label: "melonJS mip blit",
|
|
61884
|
+
layout: device.createPipelineLayout({
|
|
61885
|
+
bindGroupLayouts: [this.mipLayout]
|
|
61886
|
+
}),
|
|
61887
|
+
vertex: { module, entryPoint: "vertex_main" },
|
|
61888
|
+
fragment: {
|
|
61889
|
+
module,
|
|
61890
|
+
entryPoint: "fragment_main",
|
|
61891
|
+
targets: [{ format: "rgba8unorm" }]
|
|
61892
|
+
},
|
|
61893
|
+
primitive: { topology: "triangle-list" }
|
|
61894
|
+
});
|
|
61895
|
+
this.mipSampler = device.createSampler({
|
|
61896
|
+
label: "melonJS mip blit sampler",
|
|
61897
|
+
magFilter: "linear",
|
|
61898
|
+
minFilter: "linear"
|
|
61899
|
+
});
|
|
61900
|
+
}
|
|
61901
|
+
/**
|
|
61902
|
+
* Build the mip-chain blitter and run it once, on a throwaway 2×2
|
|
61903
|
+
* texture, so its native compile has happened before the first real
|
|
61904
|
+
* texture needs it. Creating a pipeline is not enough on every
|
|
61905
|
+
* implementation: some defer the compile to first use, so this uses it.
|
|
61906
|
+
* @ignore
|
|
61907
|
+
* @internal
|
|
61908
|
+
*/
|
|
61909
|
+
warmMipmaps() {
|
|
61910
|
+
if (typeof this.mipPipeline !== "undefined") {
|
|
61911
|
+
return;
|
|
61912
|
+
}
|
|
61913
|
+
const texture = this.device.createTexture({
|
|
61914
|
+
label: "melonJS mip warm-up",
|
|
61915
|
+
size: [2, 2],
|
|
61916
|
+
format: "rgba8unorm",
|
|
61917
|
+
mipLevelCount: 2,
|
|
61918
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
|
|
61919
|
+
});
|
|
61920
|
+
this.generateMipmaps({ texture, mipLevelCount: 2 });
|
|
61921
|
+
texture.destroy();
|
|
61922
|
+
}
|
|
60981
61923
|
/**
|
|
60982
61924
|
* Build the full mip chain of a freshly-uploaded record: one blit pass
|
|
60983
61925
|
* per level, each rendering the previous level with linear filtering.
|
|
@@ -60990,37 +61932,7 @@ var WebGPUTextureStore = class extends TextureStore {
|
|
|
60990
61932
|
*/
|
|
60991
61933
|
generateMipmaps(record) {
|
|
60992
61934
|
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
|
-
}
|
|
61935
|
+
this.ensureMipPipeline();
|
|
61024
61936
|
const encoder = device.createCommandEncoder({ label: "melonJS mipgen" });
|
|
61025
61937
|
for (let level2 = 1; level2 < record.mipLevelCount; level2++) {
|
|
61026
61938
|
const bindGroup = device.createBindGroup({
|
|
@@ -61541,6 +62453,7 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61541
62453
|
this.uniformRing.reset();
|
|
61542
62454
|
this.effectUniformArena.reset();
|
|
61543
62455
|
this.frameId++;
|
|
62456
|
+
this.pipelineCache.inFrame = true;
|
|
61544
62457
|
this.currentRenderTarget = null;
|
|
61545
62458
|
this.pendingColorClear = false;
|
|
61546
62459
|
this.effectPassDepth = 0;
|
|
@@ -61935,6 +62848,361 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61935
62848
|
);
|
|
61936
62849
|
return frame;
|
|
61937
62850
|
}
|
|
62851
|
+
/**
|
|
62852
|
+
* The post-effect render-target pool, created on first use.
|
|
62853
|
+
* @returns {RenderTargetPool} the pool
|
|
62854
|
+
* @ignore
|
|
62855
|
+
* @internal
|
|
62856
|
+
*/
|
|
62857
|
+
_postEffectPool() {
|
|
62858
|
+
this._renderTargetPool ??= new RenderTargetPool(
|
|
62859
|
+
(w, h, isCapture, index) => {
|
|
62860
|
+
return new WebGPURenderTarget(this, w, h, {
|
|
62861
|
+
sampleCount: isCapture === true ? this.canvasSampleCount : 1,
|
|
62862
|
+
format: index < 2 ? this._colorFormat : this.preferredFormat
|
|
62863
|
+
});
|
|
62864
|
+
}
|
|
62865
|
+
);
|
|
62866
|
+
return this._renderTargetPool;
|
|
62867
|
+
}
|
|
62868
|
+
/**
|
|
62869
|
+
* The pipeline-cache family of an effect's WGSL realization, building its
|
|
62870
|
+
* module and layouts first if this device has not seen it yet.
|
|
62871
|
+
* @param {ShaderEffect} effect - an effect with a valid WGSL realization
|
|
62872
|
+
* @returns {string} the family key
|
|
62873
|
+
* @ignore
|
|
62874
|
+
* @internal
|
|
62875
|
+
*/
|
|
62876
|
+
_effectFamily(effect) {
|
|
62877
|
+
const realization = effect.wgslRealization;
|
|
62878
|
+
if (realization.gpu === null || realization.gpu.epoch !== this.pipelineCache.epoch) {
|
|
62879
|
+
buildEffectGPU(this, effect, realization);
|
|
62880
|
+
}
|
|
62881
|
+
return realization.gpu.key;
|
|
62882
|
+
}
|
|
62883
|
+
/**
|
|
62884
|
+
* Build, off the frame, every pipeline a renderable's post effects can
|
|
62885
|
+
* draw with, and the render targets its chain will composite through.
|
|
62886
|
+
*
|
|
62887
|
+
* Which pipeline an effect draws with depends on where it sits: an
|
|
62888
|
+
* effect alone on a sprite draws the sprite itself (blended with the
|
|
62889
|
+
* sprite's own mode), one alone on a mesh is spliced into the mesh
|
|
62890
|
+
* shader, and one in a chain is a full-screen blit into either the next
|
|
62891
|
+
* offscreen target or the one below. Its colour format depends on that
|
|
62892
|
+
* target too, and under `hdr` the camera's targets are half-float while
|
|
62893
|
+
* the canvas is not. So this warms every position the effect can reach
|
|
62894
|
+
* with the effects ATTACHED, enabled or not, since turning one on moves
|
|
62895
|
+
* the others: enabling a camera's second effect sends its first one
|
|
62896
|
+
* from the canvas into the ping-pong target, a different pipeline.
|
|
62897
|
+
*
|
|
62898
|
+
* Each key is composed by the same function the draw uses, from the
|
|
62899
|
+
* renderable's own state (blend mode, culling, transparency, fog), so
|
|
62900
|
+
* what is built here is what the draw will ask for.
|
|
62901
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
62902
|
+
* @override
|
|
62903
|
+
* @ignore
|
|
62904
|
+
* @internal
|
|
62905
|
+
*/
|
|
62906
|
+
_warmPostEffects(renderable) {
|
|
62907
|
+
if (!this._canWarm()) {
|
|
62908
|
+
return;
|
|
62909
|
+
}
|
|
62910
|
+
const cache3 = this.pipelineCache;
|
|
62911
|
+
const effects = renderable.postEffects.filter((fx) => {
|
|
62912
|
+
return fx?.wgslRealization?.valid === true;
|
|
62913
|
+
});
|
|
62914
|
+
const count = effects.length;
|
|
62915
|
+
if (count === 0) {
|
|
62916
|
+
return;
|
|
62917
|
+
}
|
|
62918
|
+
const isCamera = renderable._postEffectManaged === true;
|
|
62919
|
+
const needsCapture = renderable.postEffectNeedsCapture === true;
|
|
62920
|
+
const pooled = isCamera || count > 1 || needsCapture;
|
|
62921
|
+
const canvas = [this.canvasFormat, this.canvasSampleCount];
|
|
62922
|
+
const scene = this._sceneTargets();
|
|
62923
|
+
const blit = (family, blendMode2, [format, sampleCount]) => {
|
|
62924
|
+
void cache3.warm(
|
|
62925
|
+
family,
|
|
62926
|
+
"triangle-list",
|
|
62927
|
+
blendMode2,
|
|
62928
|
+
true,
|
|
62929
|
+
"none",
|
|
62930
|
+
void 0,
|
|
62931
|
+
format,
|
|
62932
|
+
sampleCount
|
|
62933
|
+
);
|
|
62934
|
+
};
|
|
62935
|
+
if (pooled) {
|
|
62936
|
+
const pool2 = this._postEffectPool();
|
|
62937
|
+
const { width, height } = this.getCanvas();
|
|
62938
|
+
pool2.reserve(
|
|
62939
|
+
isCamera,
|
|
62940
|
+
this._postEffectCapacity(renderable, effects),
|
|
62941
|
+
width,
|
|
62942
|
+
height
|
|
62943
|
+
);
|
|
62944
|
+
}
|
|
62945
|
+
if (isCamera) {
|
|
62946
|
+
for (const fx of effects) {
|
|
62947
|
+
const family = this._effectFamily(fx);
|
|
62948
|
+
blit(family, "none", [this._colorFormat, 1]);
|
|
62949
|
+
blit(family, "none", canvas);
|
|
62950
|
+
}
|
|
62951
|
+
this._warmToneMap();
|
|
62952
|
+
return;
|
|
62953
|
+
}
|
|
62954
|
+
const blendMode = renderable.blendMode ?? "normal";
|
|
62955
|
+
const meshHost = this._hostsMeshEffect(renderable);
|
|
62956
|
+
for (let i = 0; i < count; i++) {
|
|
62957
|
+
const fx = effects[i];
|
|
62958
|
+
const family = this._effectFamily(fx);
|
|
62959
|
+
if (!needsCapture) {
|
|
62960
|
+
if (meshHost) {
|
|
62961
|
+
this._warmHostedEffect(renderable, fx, scene);
|
|
62962
|
+
} else {
|
|
62963
|
+
for (const target of scene) {
|
|
62964
|
+
blit(family, blendMode, target);
|
|
62965
|
+
}
|
|
62966
|
+
}
|
|
62967
|
+
}
|
|
62968
|
+
if (pooled) {
|
|
62969
|
+
for (const target of scene) {
|
|
62970
|
+
blit(family, blendMode, target);
|
|
62971
|
+
}
|
|
62972
|
+
if (i < count - 1) {
|
|
62973
|
+
blit(family, "none", [this.preferredFormat, 1]);
|
|
62974
|
+
}
|
|
62975
|
+
}
|
|
62976
|
+
}
|
|
62977
|
+
if (pooled && !meshHost) {
|
|
62978
|
+
blit("quad", blendMode, [this.preferredFormat, this.canvasSampleCount]);
|
|
62979
|
+
}
|
|
62980
|
+
}
|
|
62981
|
+
/**
|
|
62982
|
+
* The `[format, sampleCount]` targets an ordinary renderable can be drawn
|
|
62983
|
+
* into: the canvas, or the camera's capture target when the camera runs
|
|
62984
|
+
* an effect chain of its own. The two differ only in format, and only
|
|
62985
|
+
* under `hdr`, so this is usually one target.
|
|
62986
|
+
* @returns {Array<Array<string|number>>} the targets
|
|
62987
|
+
* @ignore
|
|
62988
|
+
* @internal
|
|
62989
|
+
*/
|
|
62990
|
+
_sceneTargets() {
|
|
62991
|
+
const canvas = [this.canvasFormat, this.canvasSampleCount];
|
|
62992
|
+
return this._colorFormat === this.canvasFormat ? [canvas] : [canvas, [this._colorFormat, this.canvasSampleCount]];
|
|
62993
|
+
}
|
|
62994
|
+
/**
|
|
62995
|
+
* Whether the warm-ups can run: a live device and a pipeline cache.
|
|
62996
|
+
* @returns {boolean} true when ready
|
|
62997
|
+
* @ignore
|
|
62998
|
+
* @internal
|
|
62999
|
+
*/
|
|
63000
|
+
_canWarm() {
|
|
63001
|
+
return this.pipelineCache != null && typeof this.device !== "undefined" && this.isContextValid !== false;
|
|
63002
|
+
}
|
|
63003
|
+
/**
|
|
63004
|
+
* The mesh batcher a mesh draws through.
|
|
63005
|
+
* @param {object} mesh - the mesh
|
|
63006
|
+
* @returns {object|undefined} the batcher
|
|
63007
|
+
* @ignore
|
|
63008
|
+
* @internal
|
|
63009
|
+
*/
|
|
63010
|
+
_meshBatcherFor(mesh) {
|
|
63011
|
+
return this.batchers.get(
|
|
63012
|
+
mesh.lit === true && this.batchers.has("litMesh") ? "litMesh" : "mesh"
|
|
63013
|
+
);
|
|
63014
|
+
}
|
|
63015
|
+
/**
|
|
63016
|
+
* Warm the pipelines a mesh will draw with when it hosts an effect: the
|
|
63017
|
+
* effect spliced into the mesh shader.
|
|
63018
|
+
* @param {object} mesh - the hosting mesh
|
|
63019
|
+
* @param {ShaderEffect} effect - the effect it hosts
|
|
63020
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63021
|
+
* @ignore
|
|
63022
|
+
* @internal
|
|
63023
|
+
*/
|
|
63024
|
+
_warmHostedEffect(mesh, effect, scene) {
|
|
63025
|
+
const realization = effect.wgslRealization;
|
|
63026
|
+
if (meshHostingBlocker(realization.builtins, realization.textures, "wgsl") !== null) {
|
|
63027
|
+
return;
|
|
63028
|
+
}
|
|
63029
|
+
const batcher = this._meshBatcherFor(mesh);
|
|
63030
|
+
if (batcher === void 0 || realization.meshModule(batcher.shaderSource?.() ?? "") === null) {
|
|
63031
|
+
return;
|
|
63032
|
+
}
|
|
63033
|
+
const previous = batcher.hostedEffect;
|
|
63034
|
+
let family;
|
|
63035
|
+
batcher.hostedEffect = effect;
|
|
63036
|
+
try {
|
|
63037
|
+
family = batcher.activeShaderKey();
|
|
63038
|
+
} finally {
|
|
63039
|
+
batcher.hostedEffect = previous;
|
|
63040
|
+
}
|
|
63041
|
+
this._warmMeshDraw(mesh, family, scene);
|
|
63042
|
+
}
|
|
63043
|
+
/**
|
|
63044
|
+
* Warm the pipelines one mesh draw resolves to, keyed on the mesh's own
|
|
63045
|
+
* culling, depth test and transparency exactly as `drawMesh` keys them:
|
|
63046
|
+
* the opaque pass, the transparent pass, or both when `transparent` is
|
|
63047
|
+
* left unset (blended whenever the mesh is faded).
|
|
63048
|
+
* @param {object} mesh - the mesh
|
|
63049
|
+
* @param {string} family - the pipeline family it draws with
|
|
63050
|
+
* @param {Array<Array<string|number>>} scene - the targets it can land on
|
|
63051
|
+
* @ignore
|
|
63052
|
+
* @internal
|
|
63053
|
+
*/
|
|
63054
|
+
_warmMeshDraw(mesh, family, scene) {
|
|
63055
|
+
const culling = mesh.cullBackFaces === true;
|
|
63056
|
+
const state4 = {
|
|
63057
|
+
cullMode: culling ? "back" : "none",
|
|
63058
|
+
// the retained path's winding rule, see `drawMesh`
|
|
63059
|
+
frontFace: culling && this.supportsRetainedMesh === true && mesh.rightHanded !== true ? "cw" : "ccw",
|
|
63060
|
+
depthWrite: void 0,
|
|
63061
|
+
depthTest: mesh.depthTest === false ? false : void 0,
|
|
63062
|
+
fog: this._fog3d != null ? true : void 0
|
|
63063
|
+
};
|
|
63064
|
+
const blendedOnly = mesh.transparent === true || mesh._blendedDraw === true;
|
|
63065
|
+
const blended = mesh.transparent !== false || mesh._blendedDraw === true;
|
|
63066
|
+
for (const [format, sampleCount] of scene) {
|
|
63067
|
+
if (!blendedOnly) {
|
|
63068
|
+
void this.pipelineCache.warm(
|
|
63069
|
+
family,
|
|
63070
|
+
"triangle-list",
|
|
63071
|
+
"none",
|
|
63072
|
+
true,
|
|
63073
|
+
"none",
|
|
63074
|
+
state4,
|
|
63075
|
+
format,
|
|
63076
|
+
sampleCount
|
|
63077
|
+
);
|
|
63078
|
+
}
|
|
63079
|
+
if (blended) {
|
|
63080
|
+
void this.pipelineCache.warm(
|
|
63081
|
+
family,
|
|
63082
|
+
"triangle-list",
|
|
63083
|
+
mesh._blendedDraw === true ? "normal" : mesh.blendMode,
|
|
63084
|
+
true,
|
|
63085
|
+
"none",
|
|
63086
|
+
{ ...state4, depthWrite: false },
|
|
63087
|
+
format,
|
|
63088
|
+
sampleCount
|
|
63089
|
+
);
|
|
63090
|
+
}
|
|
63091
|
+
}
|
|
63092
|
+
}
|
|
63093
|
+
/**
|
|
63094
|
+
* Warm the renderable's own draw: a mesh through its mesh family, and
|
|
63095
|
+
* everything else that draws a texture through the quad family, each with
|
|
63096
|
+
* the renderable's blend mode. Containers draw nothing of their own, and
|
|
63097
|
+
* a camera is a chain, warmed with its effects.
|
|
63098
|
+
* @param {Renderable} renderable - the renderable to warm for
|
|
63099
|
+
* @override
|
|
63100
|
+
* @ignore
|
|
63101
|
+
* @internal
|
|
63102
|
+
*/
|
|
63103
|
+
_warmDraw(renderable) {
|
|
63104
|
+
if (!this._canWarm() || renderable._postEffectManaged === true || Array.isArray(renderable.children)) {
|
|
63105
|
+
return;
|
|
63106
|
+
}
|
|
63107
|
+
const scene = this._sceneTargets();
|
|
63108
|
+
if (this._hostsMeshEffect(renderable)) {
|
|
63109
|
+
const batcher = this._meshBatcherFor(renderable);
|
|
63110
|
+
if (batcher?.shaderKey !== void 0) {
|
|
63111
|
+
this._warmMeshDraw(renderable, batcher.shaderKey, scene);
|
|
63112
|
+
}
|
|
63113
|
+
return;
|
|
63114
|
+
}
|
|
63115
|
+
if (renderable.instanceLayout !== void 0) {
|
|
63116
|
+
return;
|
|
63117
|
+
}
|
|
63118
|
+
const blendMode = renderable.blendMode ?? "normal";
|
|
63119
|
+
for (const [format, sampleCount] of scene) {
|
|
63120
|
+
void this.pipelineCache.warm(
|
|
63121
|
+
"quad",
|
|
63122
|
+
"triangle-list",
|
|
63123
|
+
blendMode,
|
|
63124
|
+
true,
|
|
63125
|
+
"none",
|
|
63126
|
+
void 0,
|
|
63127
|
+
format,
|
|
63128
|
+
sampleCount
|
|
63129
|
+
);
|
|
63130
|
+
}
|
|
63131
|
+
}
|
|
63132
|
+
/**
|
|
63133
|
+
* Build the advanced blend shader and its offscreen target before the
|
|
63134
|
+
* first draw in one of those modes, and warm the two pipelines a bracket
|
|
63135
|
+
* uses: the draw itself into the 8-bit offscreen, and the composite back
|
|
63136
|
+
* onto the scene.
|
|
63137
|
+
* @override
|
|
63138
|
+
* @ignore
|
|
63139
|
+
* @internal
|
|
63140
|
+
*/
|
|
63141
|
+
_warmAdvancedBlend() {
|
|
63142
|
+
if (!this._canWarm()) {
|
|
63143
|
+
return;
|
|
63144
|
+
}
|
|
63145
|
+
if (typeof this._advancedBlendEffect === "undefined") {
|
|
63146
|
+
this._advancedBlendEffect = new BlendEffect(this);
|
|
63147
|
+
}
|
|
63148
|
+
const effect = this._advancedBlendEffect;
|
|
63149
|
+
if (effect.enabled !== true || effect.wgslRealization?.valid !== true) {
|
|
63150
|
+
return;
|
|
63151
|
+
}
|
|
63152
|
+
const { width, height } = this.getCanvas();
|
|
63153
|
+
if (typeof this._advancedBlendTarget === "undefined") {
|
|
63154
|
+
this._advancedBlendTarget = new WebGPURenderTarget(this, width, height, {
|
|
63155
|
+
sampleCount: 1,
|
|
63156
|
+
format: this.preferredFormat
|
|
63157
|
+
});
|
|
63158
|
+
}
|
|
63159
|
+
void this.pipelineCache.warm(
|
|
63160
|
+
"quad",
|
|
63161
|
+
"triangle-list",
|
|
63162
|
+
"normal",
|
|
63163
|
+
true,
|
|
63164
|
+
"none",
|
|
63165
|
+
void 0,
|
|
63166
|
+
this.preferredFormat,
|
|
63167
|
+
1
|
|
63168
|
+
);
|
|
63169
|
+
const family = this._effectFamily(effect);
|
|
63170
|
+
for (const [format, sampleCount] of this._sceneTargets()) {
|
|
63171
|
+
void this.pipelineCache.warm(
|
|
63172
|
+
family,
|
|
63173
|
+
"triangle-list",
|
|
63174
|
+
"normal",
|
|
63175
|
+
true,
|
|
63176
|
+
"none",
|
|
63177
|
+
void 0,
|
|
63178
|
+
format,
|
|
63179
|
+
sampleCount
|
|
63180
|
+
);
|
|
63181
|
+
}
|
|
63182
|
+
}
|
|
63183
|
+
/**
|
|
63184
|
+
* Warm the tone curve's pipeline: always the last step of a camera chain,
|
|
63185
|
+
* so a full-screen blit onto the canvas with blending off.
|
|
63186
|
+
* @override
|
|
63187
|
+
* @ignore
|
|
63188
|
+
* @internal
|
|
63189
|
+
*/
|
|
63190
|
+
_warmToneMap() {
|
|
63191
|
+
const toneMap = this._toneMapEffect;
|
|
63192
|
+
if (this.settings?.prewarm !== true || toneMap?.wgslRealization?.valid !== true || !this._canWarm()) {
|
|
63193
|
+
return;
|
|
63194
|
+
}
|
|
63195
|
+
void this.pipelineCache.warm(
|
|
63196
|
+
this._effectFamily(toneMap),
|
|
63197
|
+
"triangle-list",
|
|
63198
|
+
"none",
|
|
63199
|
+
true,
|
|
63200
|
+
"none",
|
|
63201
|
+
void 0,
|
|
63202
|
+
this.canvasFormat,
|
|
63203
|
+
this.canvasSampleCount
|
|
63204
|
+
);
|
|
63205
|
+
}
|
|
61938
63206
|
/**
|
|
61939
63207
|
* Begin a post-effect pass for the given renderable — the WebGPU
|
|
61940
63208
|
* realization of the WebGL FBO path: the renderable's whole content
|
|
@@ -61967,17 +63235,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
61967
63235
|
}
|
|
61968
63236
|
savedProjection.copy(this.projectionMatrix);
|
|
61969
63237
|
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(
|
|
63238
|
+
const rt = this._postEffectPool().begin(
|
|
61979
63239
|
isCamera,
|
|
61980
|
-
|
|
63240
|
+
this._postEffectCapacity(renderable, effects),
|
|
61981
63241
|
canvas.width,
|
|
61982
63242
|
canvas.height
|
|
61983
63243
|
);
|
|
@@ -62222,23 +63482,24 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62222
63482
|
* WebGPU splits what WebGL fuses into one program. The **modules** for
|
|
62223
63483
|
* the engine's own families — quad, blit, primitive, clear, and the mesh
|
|
62224
63484
|
* 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
|
-
*
|
|
63485
|
+
* are constructed. A **pipeline** is where the real compile happens, and
|
|
63486
|
+
* it is the expensive part: tens of milliseconds each, paid in the frame
|
|
63487
|
+
* that first draws with it. But it is keyed by draw state (blend, cull,
|
|
63488
|
+
* depth write) and by the format of the target being drawn into, as well
|
|
63489
|
+
* as by shader, so the set a scene will ask for cannot be known from
|
|
63490
|
+
* here. Those are warmed when an effect is attached to a renderable
|
|
63491
|
+
* ({@link Renderable#addPostEffect}), which is the first moment the
|
|
63492
|
+
* renderable that decides them is known.
|
|
63493
|
+
*
|
|
63494
|
+
* What this does warm is what needs no renderable. A `ShaderEffect`'s
|
|
63495
|
+
* module: `prepareEffectBinding` builds it, and only ever from inside the
|
|
63496
|
+
* draw path. Every effect the loader compiled from a `{type: "shader"}`
|
|
63497
|
+
* asset is already in `shaderList`, so those can be brought forward —
|
|
63498
|
+
* which is what makes declaring a level's effects alongside that level's
|
|
63499
|
+
* assets worth doing. And the mip-chain blitter every mipmapped texture
|
|
63500
|
+
* goes through on upload, when textures are filtered (`textureFilter`
|
|
63501
|
+
* resolving to `"linear"`; nearest filtering never builds a chain).
|
|
63502
|
+
* @returns {Promise<void>} settles once those are built
|
|
62242
63503
|
* @see {@link Renderer#prewarm}
|
|
62243
63504
|
*/
|
|
62244
63505
|
prewarm() {
|
|
@@ -62252,6 +63513,23 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62252
63513
|
buildEffectGPU(this, effect, realization);
|
|
62253
63514
|
}
|
|
62254
63515
|
}
|
|
63516
|
+
if (this.getDefaultTextureFilter() === "linear") {
|
|
63517
|
+
this.textureStore.warmMipmaps();
|
|
63518
|
+
}
|
|
63519
|
+
if (this._canWarm()) {
|
|
63520
|
+
for (const [format, sampleCount] of this._sceneTargets()) {
|
|
63521
|
+
void this.pipelineCache.warm(
|
|
63522
|
+
"clear",
|
|
63523
|
+
"triangle-list",
|
|
63524
|
+
"none",
|
|
63525
|
+
true,
|
|
63526
|
+
"none",
|
|
63527
|
+
void 0,
|
|
63528
|
+
format,
|
|
63529
|
+
sampleCount
|
|
63530
|
+
);
|
|
63531
|
+
}
|
|
63532
|
+
}
|
|
62255
63533
|
});
|
|
62256
63534
|
}
|
|
62257
63535
|
/**
|
|
@@ -62273,6 +63551,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62273
63551
|
this.frameTextureView = null;
|
|
62274
63552
|
this.frameTexture = null;
|
|
62275
63553
|
}
|
|
63554
|
+
if (this.pipelineCache != null) {
|
|
63555
|
+
this.pipelineCache.inFrame = false;
|
|
63556
|
+
}
|
|
62276
63557
|
this.destroyRetiredTextures();
|
|
62277
63558
|
}
|
|
62278
63559
|
/**
|
|
@@ -62292,6 +63573,9 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
62292
63573
|
}
|
|
62293
63574
|
this.frameTextureView = null;
|
|
62294
63575
|
this.frameTexture = null;
|
|
63576
|
+
if (this.pipelineCache != null) {
|
|
63577
|
+
this.pipelineCache.inFrame = false;
|
|
63578
|
+
}
|
|
62295
63579
|
this.currentRenderTarget = null;
|
|
62296
63580
|
this.pendingColorClear = false;
|
|
62297
63581
|
this.pendingDepthClear = false;
|
|
@@ -64050,6 +65334,10 @@ var WebGPURenderer = class _WebGPURenderer extends Renderer {
|
|
|
64050
65334
|
batcher.init(this);
|
|
64051
65335
|
}
|
|
64052
65336
|
emit(ONCONTEXT_RESTORED, this);
|
|
65337
|
+
if (this.settings.prewarm === true) {
|
|
65338
|
+
this.prewarm().catch(() => {
|
|
65339
|
+
});
|
|
65340
|
+
}
|
|
64053
65341
|
}
|
|
64054
65342
|
/**
|
|
64055
65343
|
* Returns the WebGPU canvas context
|
|
@@ -69963,8 +71251,110 @@ var ShineEffect = class extends ShaderEffect {
|
|
|
69963
71251
|
}
|
|
69964
71252
|
};
|
|
69965
71253
|
|
|
69966
|
-
// src/video/effects/
|
|
71254
|
+
// src/video/effects/squish.js
|
|
69967
71255
|
var wgslFragment15 = `
|
|
71256
|
+
struct SquishUniforms {
|
|
71257
|
+
uSquishFrame : vec4f,
|
|
71258
|
+
uSquishXFactor : f32,
|
|
71259
|
+
uSquishBulge : f32,
|
|
71260
|
+
};
|
|
71261
|
+
@group(3) @binding(0) var<uniform> fx : SquishUniforms;
|
|
71262
|
+
|
|
71263
|
+
fn apply(color : vec4f, uv : vec2f) -> vec4f {
|
|
71264
|
+
// -1..1 across the quad: the sprite's frame, stretched by the vertex scale
|
|
71265
|
+
var p = (uv - fx.uSquishFrame.xy) / fx.uSquishFrame.zw * 2.0 - 1.0;
|
|
71266
|
+
// undo the stretch: the sprite keeps its width, in the middle of the
|
|
71267
|
+
// wider quad, with room on both sides
|
|
71268
|
+
p.x = p.x * fx.uSquishXFactor;
|
|
71269
|
+
// each row widens by its share of the bulge: all of it across the
|
|
71270
|
+
// middle, none at the top and bottom
|
|
71271
|
+
p.x = p.x / max(1.0 + fx.uSquishBulge * sqrt(max(1.0 - p.y * p.y, 0.0)), 0.0001);
|
|
71272
|
+
let local = (p + 1.0) * 0.5;
|
|
71273
|
+
// sampled before the test, since textureSample needs uniform control flow
|
|
71274
|
+
let texel = textureSample(uTexture, uSampler, fx.uSquishFrame.xy + clamp(local, vec2f(0.0), vec2f(1.0)) * fx.uSquishFrame.zw) * vColor;
|
|
71275
|
+
let inside = all(local >= vec2f(0.0)) && all(local <= vec2f(1.0));
|
|
71276
|
+
return select(vec4f(0.0), texel, inside);
|
|
71277
|
+
}
|
|
71278
|
+
`;
|
|
71279
|
+
var SquishEffect = class _SquishEffect extends ShaderEffect {
|
|
71280
|
+
/**
|
|
71281
|
+
* @param {Renderer} renderer - the current renderer instance
|
|
71282
|
+
* @param {object} [options] - effect options
|
|
71283
|
+
* @param {number} [options.bulge=0] - how far the middle bulges out (positive) or pinches in (negative), from -1 to 1
|
|
71284
|
+
*/
|
|
71285
|
+
constructor(renderer, options = {}) {
|
|
71286
|
+
super(renderer, {
|
|
71287
|
+
glsl: `
|
|
71288
|
+
uniform vec4 uSquishFrame;
|
|
71289
|
+
uniform float uSquishXFactor;
|
|
71290
|
+
uniform float uSquishBulge;
|
|
71291
|
+
vec4 apply(vec4 color, vec2 uv) {
|
|
71292
|
+
// -1..1 across the quad: the sprite's frame, stretched by the vertex scale
|
|
71293
|
+
vec2 p = (uv - uSquishFrame.xy) / uSquishFrame.zw * 2.0 - 1.0;
|
|
71294
|
+
// undo the stretch: the sprite keeps its width, in the middle
|
|
71295
|
+
// of the wider quad, with room on both sides
|
|
71296
|
+
p.x *= uSquishXFactor;
|
|
71297
|
+
// each row widens by its share of the bulge: all of it across
|
|
71298
|
+
// the middle, none at the top and bottom
|
|
71299
|
+
p.x /= max(1.0 + uSquishBulge * sqrt(max(1.0 - p.y * p.y, 0.0)), 0.0001);
|
|
71300
|
+
vec2 local = (p + 1.0) * 0.5;
|
|
71301
|
+
// transparent past the sprite: the room around it, or the
|
|
71302
|
+
// indent of a pinch
|
|
71303
|
+
if (local.x < 0.0 || local.x > 1.0 || local.y < 0.0 || local.y > 1.0) {
|
|
71304
|
+
return vec4(0.0);
|
|
71305
|
+
}
|
|
71306
|
+
return texture2D(uSampler, uSquishFrame.xy + local * uSquishFrame.zw) * vColor;
|
|
71307
|
+
}
|
|
71308
|
+
`,
|
|
71309
|
+
wgsl: wgslFragment15
|
|
71310
|
+
});
|
|
71311
|
+
this._bulge = 0;
|
|
71312
|
+
this._frame = new Float32Array([0, 0, 1, 1]);
|
|
71313
|
+
this.setUniform("uSquishFrame", this._frame);
|
|
71314
|
+
this.setBulge(options.bulge ?? 0);
|
|
71315
|
+
}
|
|
71316
|
+
/**
|
|
71317
|
+
* set how far the middle of the sprite bulges out, or pinches in
|
|
71318
|
+
* @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
|
|
71319
|
+
*/
|
|
71320
|
+
setBulge(bulge) {
|
|
71321
|
+
const value = Math.min(Math.max(bulge, -1), 1);
|
|
71322
|
+
this._bulge = value;
|
|
71323
|
+
const xFactor = 1 + Math.max(value, 0);
|
|
71324
|
+
this._vertexScale.x = xFactor;
|
|
71325
|
+
this.setUniform("uSquishXFactor", xFactor);
|
|
71326
|
+
this.setUniform("uSquishBulge", value);
|
|
71327
|
+
}
|
|
71328
|
+
/**
|
|
71329
|
+
* The batchers hand every effect the frame it draws through here (it is
|
|
71330
|
+
* what `noise_uv` runs across), and the bulge is measured against the
|
|
71331
|
+
* same frame: where the sprite sits in its atlas, in UV units. A render
|
|
71332
|
+
* target blit hands over the whole target.
|
|
71333
|
+
* @ignore
|
|
71334
|
+
* @internal
|
|
71335
|
+
*/
|
|
71336
|
+
_setNoiseUVRect(sourceWidth, sourceHeight, objectWidth, objectHeight, u0, v0) {
|
|
71337
|
+
const frame = this._frame;
|
|
71338
|
+
frame[0] = u0;
|
|
71339
|
+
frame[1] = v0;
|
|
71340
|
+
frame[2] = Math.max(Math.abs(objectWidth), 1) / Math.max(sourceWidth, 1);
|
|
71341
|
+
frame[3] = Math.max(Math.abs(objectHeight), 1) / Math.max(sourceHeight, 1);
|
|
71342
|
+
this.setUniform("uSquishFrame", frame);
|
|
71343
|
+
}
|
|
71344
|
+
/**
|
|
71345
|
+
* Create an independent copy of this effect, with the same bulge.
|
|
71346
|
+
* @returns {SquishEffect} a new effect
|
|
71347
|
+
*/
|
|
71348
|
+
clone() {
|
|
71349
|
+
if (this.destroyed) {
|
|
71350
|
+
throw new Error("SquishEffect.clone: effect has been destroyed");
|
|
71351
|
+
}
|
|
71352
|
+
return new _SquishEffect(this._renderer, { bulge: this._bulge });
|
|
71353
|
+
}
|
|
71354
|
+
};
|
|
71355
|
+
|
|
71356
|
+
// src/video/effects/tintPulse.js
|
|
71357
|
+
var wgslFragment16 = `
|
|
69968
71358
|
struct TintPulseUniforms {
|
|
69969
71359
|
uPulseColor : vec3f,
|
|
69970
71360
|
uPulseSpeed : f32,
|
|
@@ -69998,7 +71388,7 @@ var TintPulseEffect = class extends ShaderEffect {
|
|
|
69998
71388
|
return vec4(mix(color.rgb, uPulseColor * color.a, pulse), color.a);
|
|
69999
71389
|
}
|
|
70000
71390
|
`,
|
|
70001
|
-
wgsl:
|
|
71391
|
+
wgsl: wgslFragment16
|
|
70002
71392
|
});
|
|
70003
71393
|
const color = options.color ?? [1, 0, 0];
|
|
70004
71394
|
this.setUniform("uPulseColor", new Float32Array(color));
|
|
@@ -70025,7 +71415,7 @@ var fragment = `
|
|
|
70025
71415
|
return vec4(color.rgb * v, color.a);
|
|
70026
71416
|
}
|
|
70027
71417
|
`;
|
|
70028
|
-
var
|
|
71418
|
+
var wgslFragment17 = `
|
|
70029
71419
|
struct VignetteUniforms {
|
|
70030
71420
|
uStrength : f32,
|
|
70031
71421
|
uSize : f32,
|
|
@@ -70046,7 +71436,7 @@ var VignetteEffect = class extends ShaderEffect {
|
|
|
70046
71436
|
* @param {number} [options.size=25.0] - vignette spread multiplier (higher = smaller dark area)
|
|
70047
71437
|
*/
|
|
70048
71438
|
constructor(renderer, options = {}) {
|
|
70049
|
-
super(renderer, { glsl: fragment, wgsl:
|
|
71439
|
+
super(renderer, { glsl: fragment, wgsl: wgslFragment17 });
|
|
70050
71440
|
this.strength = options.strength ?? 0.15;
|
|
70051
71441
|
this.size = options.size ?? 25;
|
|
70052
71442
|
this.setUniform("uStrength", this.strength);
|
|
@@ -70071,7 +71461,7 @@ var VignetteEffect = class extends ShaderEffect {
|
|
|
70071
71461
|
};
|
|
70072
71462
|
|
|
70073
71463
|
// src/video/effects/wave.js
|
|
70074
|
-
var
|
|
71464
|
+
var wgslFragment18 = `
|
|
70075
71465
|
struct WaveUniforms {
|
|
70076
71466
|
uAmplitude : f32,
|
|
70077
71467
|
uFrequency : f32,
|
|
@@ -70107,7 +71497,7 @@ var WaveEffect = class extends ShaderEffect {
|
|
|
70107
71497
|
return texture2D(uSampler, distorted) * vColor;
|
|
70108
71498
|
}
|
|
70109
71499
|
`,
|
|
70110
|
-
wgsl:
|
|
71500
|
+
wgsl: wgslFragment18
|
|
70111
71501
|
});
|
|
70112
71502
|
this.setUniform("uAmplitude", options.amplitude ?? 0.01);
|
|
70113
71503
|
this.setUniform("uFrequency", options.frequency ?? 10);
|
|
@@ -71704,6 +73094,7 @@ export {
|
|
|
71704
73094
|
Sphere,
|
|
71705
73095
|
Sprite,
|
|
71706
73096
|
Sprite3d,
|
|
73097
|
+
SquishEffect,
|
|
71707
73098
|
Stage,
|
|
71708
73099
|
TMXHexagonalRenderer,
|
|
71709
73100
|
TMXIsometricRenderer,
|