littlejsengine 1.25.0 → 1.26.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/CHANGELOG.md +87 -0
- package/FAQ.md +49 -11
- package/README.md +6 -2
- package/REFERENCE.md +120 -18
- package/dist/littlejs.d.ts +464 -99
- package/dist/littlejs.esm.js +2853 -704
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2844 -701
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2658 -512
- package/package.json +4 -4
- package/plugins/box2d.js +110 -34
- package/plugins/gltf.js +74 -22
- package/plugins/lightSystem.js +410 -28
- package/plugins/math3d.js +4 -2
- package/plugins/medalSystem.js +19 -7
- package/plugins/newgrounds.js +4 -2
- package/plugins/parallax.js +1 -0
- package/plugins/particleEffects.js +44 -10
- package/plugins/pathFinder.js +9 -11
- package/plugins/pluginExport.js +9 -0
- package/plugins/postProcess.js +4 -2
- package/plugins/render3d.js +485 -2385
- package/plugins/render3dDebug.js +5 -8
- package/plugins/render3dEditor.js +81 -6
- package/plugins/render3dExtras.js +103 -31
- package/plugins/render3dLevel.js +53 -5
- package/plugins/render3dMesh.js +1158 -0
- package/plugins/render3dObject.js +1451 -0
- package/plugins/render3dVoxels.js +15 -3
- package/plugins/textureSheet.js +9 -8
- package/plugins/threejs.js +1 -1
- package/plugins/tweenSystem.js +76 -51
- package/plugins/uiSystem.js +37 -8
- package/plugins/wavedash.js +229 -0
- package/src/engine.js +184 -36
- package/src/engineAudio.js +37 -21
- package/src/engineBuild.mjs +6 -1
- package/src/engineDebug.js +22 -162
- package/src/engineDraw.js +139 -34
- package/src/engineEditor.js +79 -18
- package/src/engineExport.js +0 -3
- package/src/engineInput.js +27 -18
- package/src/engineMath.js +35 -14
- package/src/engineObject.js +60 -41
- package/src/engineParticles.js +58 -11
- package/src/engineRelease.js +1 -5
- package/src/engineSettings.js +48 -9
- package/src/engineTileLayer.js +11 -4
- package/src/engineUtilities.js +41 -9
- package/src/engineWebGL.js +37 -13
package/src/engineSettings.js
CHANGED
|
@@ -458,7 +458,11 @@ let soundPauseWhenHidden = true;
|
|
|
458
458
|
/** Set position of camera in world space
|
|
459
459
|
* @param {Vector2} pos
|
|
460
460
|
* @memberof Settings */
|
|
461
|
-
function setCameraPos(pos)
|
|
461
|
+
function setCameraPos(pos)
|
|
462
|
+
{
|
|
463
|
+
ASSERT(isVector2(pos), 'setCameraPos: pos is a vec2, like vec2(5, 2)', pos);
|
|
464
|
+
cameraPos = pos.copy();
|
|
465
|
+
}
|
|
462
466
|
|
|
463
467
|
/** Set angle of camera in world space
|
|
464
468
|
* @param {number} angle
|
|
@@ -472,6 +476,7 @@ function setCameraScale(scale) { cameraScale = scale; }
|
|
|
472
476
|
|
|
473
477
|
/** Set scale applied to engine time
|
|
474
478
|
* - 0 freezes the game like a pause, gameUpdatePost and input still run so the game can set it back
|
|
479
|
+
* - Above 1 the fixed step runs that many updates each frame, so a large scale costs as many updates
|
|
475
480
|
* @param {number} scale - 0 or more
|
|
476
481
|
* @memberof Settings */
|
|
477
482
|
function setTimeScale(scale)
|
|
@@ -491,12 +496,20 @@ function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
|
|
|
491
496
|
/** Set color to clear the canvas to before render, does not clear if alpha is 0
|
|
492
497
|
* @param {Color} color
|
|
493
498
|
* @memberof Settings */
|
|
494
|
-
function setCanvasClearColor(color)
|
|
499
|
+
function setCanvasClearColor(color)
|
|
500
|
+
{
|
|
501
|
+
ASSERT(isColor(color), 'setCanvasClearColor: color is a Color, like hsl(.6, .5, .2)', color);
|
|
502
|
+
canvasClearColor = color.copy();
|
|
503
|
+
}
|
|
495
504
|
|
|
496
505
|
/** Set max size of the canvas
|
|
497
506
|
* @param {Vector2} size
|
|
498
507
|
* @memberof Settings */
|
|
499
|
-
function setCanvasMaxSize(size)
|
|
508
|
+
function setCanvasMaxSize(size)
|
|
509
|
+
{
|
|
510
|
+
ASSERT(isVector2(size), 'setCanvasMaxSize: size is a vec2, like vec2(1920, 1080)', size);
|
|
511
|
+
canvasMaxSize = size.copy();
|
|
512
|
+
}
|
|
500
513
|
|
|
501
514
|
/** Set minimum aspect ratio of the canvas (width/height), unused if 0
|
|
502
515
|
* @param {number} aspect
|
|
@@ -511,7 +524,11 @@ function setCanvasMaxAspect(aspect) { canvasMaxAspect = aspect; }
|
|
|
511
524
|
/** Set fixed size of the canvas
|
|
512
525
|
* @param {Vector2} size
|
|
513
526
|
* @memberof Settings */
|
|
514
|
-
function setCanvasFixedSize(size)
|
|
527
|
+
function setCanvasFixedSize(size)
|
|
528
|
+
{
|
|
529
|
+
ASSERT(isVector2(size), 'setCanvasFixedSize: size is a vec2, like vec2(1280, 720)', size);
|
|
530
|
+
canvasFixedSize = size.copy();
|
|
531
|
+
}
|
|
515
532
|
|
|
516
533
|
/** Use nearest scaling algorithm for canvas for more pixelated look
|
|
517
534
|
* @param {boolean} pixelated
|
|
@@ -553,6 +570,7 @@ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
|
|
|
553
570
|
function getCanvasPixelRatio() { return canvasPixelRatio ?? (devicePixelRatio || 1); }
|
|
554
571
|
|
|
555
572
|
/** Set default font used for text rendering
|
|
573
|
+
* - A family name with spaces or digits goes in quotes, as "'Press Start 2P'", or the canvas does not take it
|
|
556
574
|
* @param {string} font
|
|
557
575
|
* @memberof Settings */
|
|
558
576
|
function setFontDefault(font) { fontDefault = font; }
|
|
@@ -641,7 +659,11 @@ function setGLCircleSides(sides)
|
|
|
641
659
|
/** Set default size of tiles in pixels
|
|
642
660
|
* @param {Vector2} size
|
|
643
661
|
* @memberof Settings */
|
|
644
|
-
function setTileDefaultSize(size)
|
|
662
|
+
function setTileDefaultSize(size)
|
|
663
|
+
{
|
|
664
|
+
ASSERT(isVector2(size), 'setTileDefaultSize: size is a vec2, like vec2(16)', size);
|
|
665
|
+
tileDefaultSize = size.copy();
|
|
666
|
+
}
|
|
645
667
|
|
|
646
668
|
/** Default padding pixels around tiles
|
|
647
669
|
* @param {number} padding
|
|
@@ -666,12 +688,23 @@ function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
|
|
|
666
688
|
/** Set the fraction of velocity objects keep each frame, 1 keeps all of it, 0 stops at once
|
|
667
689
|
* @param {number} damp
|
|
668
690
|
* @memberof Settings */
|
|
669
|
-
function setObjectDefaultDamping(damp)
|
|
691
|
+
function setObjectDefaultDamping(damp)
|
|
692
|
+
{
|
|
693
|
+
ASSERT(damp >= 0 && damp <= 1,
|
|
694
|
+
'setObjectDefaultDamping: damping is 0 to 1, the fraction of velocity kept each frame', damp);
|
|
695
|
+
objectDefaultDamping = damp;
|
|
696
|
+
}
|
|
670
697
|
|
|
671
698
|
/** Set the fraction of angular velocity objects keep each frame, 1 keeps all of it, 0 stops at once
|
|
672
699
|
* @param {number} damp
|
|
673
700
|
* @memberof Settings */
|
|
674
|
-
function setObjectDefaultAngleDamping(damp)
|
|
701
|
+
function setObjectDefaultAngleDamping(damp)
|
|
702
|
+
{
|
|
703
|
+
ASSERT(damp >= 0 && damp <= 1,
|
|
704
|
+
'setObjectDefaultAngleDamping: angle damping is 0 to 1, the fraction of angular velocity kept each frame',
|
|
705
|
+
damp);
|
|
706
|
+
objectDefaultAngleDamping = damp;
|
|
707
|
+
}
|
|
675
708
|
|
|
676
709
|
/** Set how much to bounce when a collision occurs
|
|
677
710
|
* @param {number} restitution
|
|
@@ -691,7 +724,11 @@ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
|
|
|
691
724
|
/** Set how much gravity to apply to objects
|
|
692
725
|
* @param {Vector2} newGravity
|
|
693
726
|
* @memberof Settings */
|
|
694
|
-
function setGravity(newGravity)
|
|
727
|
+
function setGravity(newGravity)
|
|
728
|
+
{
|
|
729
|
+
ASSERT(isVector2(newGravity), 'setGravity: newGravity is a vec2, like vec2(0, -.01)', newGravity);
|
|
730
|
+
gravity = newGravity.copy();
|
|
731
|
+
}
|
|
695
732
|
|
|
696
733
|
/** Set the scale for the emit rate of particles, 0 disables particle emitters
|
|
697
734
|
* @param {number} scale
|
|
@@ -821,7 +858,8 @@ function setTouchGamepadVibration(ms) { touchGamepadVibration = ms; }
|
|
|
821
858
|
* @memberof Settings */
|
|
822
859
|
function setVibrateEnable(enable) { vibrateEnable = enable; }
|
|
823
860
|
|
|
824
|
-
/** Set if audio is enabled, false turns all sound off
|
|
861
|
+
/** Set if audio is enabled, false turns all sound off; a setting for before sounds are made, since a sound made
|
|
862
|
+
* while it is off has nothing to play and one already playing goes on; to mute while the game runs, setSoundVolume(0)
|
|
825
863
|
* @param {boolean} enable
|
|
826
864
|
* @memberof Settings */
|
|
827
865
|
function setSoundEnable(enable) { soundEnable = enable; }
|
|
@@ -840,6 +878,7 @@ function setSoundIgnoreSilentSwitch(ignore)
|
|
|
840
878
|
* @memberof Settings */
|
|
841
879
|
function setSoundVolume(volume)
|
|
842
880
|
{
|
|
881
|
+
ASSERT(isNumber(volume) && abs(volume) < Infinity, 'setSoundVolume: volume must be a finite number, 1 is full', volume);
|
|
843
882
|
soundVolume = volume;
|
|
844
883
|
audioUpdateVolume(); // update gain immediately, sound off or not
|
|
845
884
|
}
|
package/src/engineTileLayer.js
CHANGED
|
@@ -844,7 +844,7 @@ class TileLayer extends CanvasLayer
|
|
|
844
844
|
redrawStart(clear=false)
|
|
845
845
|
{
|
|
846
846
|
if (!this.context) return;
|
|
847
|
-
ASSERT(drawContext !== this.context);
|
|
847
|
+
ASSERT(drawContext !== this.context, 'redrawStart: already started, call redrawEnd() first');
|
|
848
848
|
clear || this.redrawIfSwitched(); // a partial redraw goes on top of the whole layer on the side in use
|
|
849
849
|
|
|
850
850
|
// save current render settings
|
|
@@ -889,7 +889,7 @@ class TileLayer extends CanvasLayer
|
|
|
889
889
|
redrawEnd()
|
|
890
890
|
{
|
|
891
891
|
if (!this.context) return;
|
|
892
|
-
ASSERT(drawContext === this.context);
|
|
892
|
+
ASSERT(drawContext === this.context, 'redrawEnd: call redrawStart() first');
|
|
893
893
|
|
|
894
894
|
// set stuff back to normal, the camera first, so a target that was drawing before gets its own transform back
|
|
895
895
|
[drawContext, mainCanvasSize, cameraPos, cameraScale, cameraAngle, canvasClearColor, glCustomShader] = this.savedRenderSettings;
|
|
@@ -1085,9 +1085,9 @@ class TileCollisionLayer extends TileLayer
|
|
|
1085
1085
|
for (let y = y0; y < y1; ++y)
|
|
1086
1086
|
for (let x = x0; x < x1; ++x)
|
|
1087
1087
|
{
|
|
1088
|
-
if (!this.collisionData[y*size.x + x]) continue;
|
|
1088
|
+
if (!(this.collisionData[y*size.x + x] > 0)) continue; // solid is positive, a negative is a marker
|
|
1089
1089
|
let end = x + 1; // a run of solid cells along the row
|
|
1090
|
-
while (end < x1 && this.collisionData[y*size.x + end]) ++end;
|
|
1090
|
+
while (end < x1 && this.collisionData[y*size.x + end] > 0) ++end;
|
|
1091
1091
|
const count = end - x;
|
|
1092
1092
|
const tileInfo = new TileInfo(vec2(x*cellPixels.x, textureHeight - (y+1)*cellPixels.y),
|
|
1093
1093
|
vec2(count*cellPixels.x, cellPixels.y), this.textureInfo, 0, 0);
|
|
@@ -1217,7 +1217,14 @@ class TileCollisionLayer extends TileLayer
|
|
|
1217
1217
|
const hitPos = lineTest(t0 ? a.add(d.scale(t0)) : a, t1 < 1 ? a.add(d.scale(t1)) : posEnd.subtract(offset),
|
|
1218
1218
|
testFunction, normal);
|
|
1219
1219
|
if (hitPos)
|
|
1220
|
+
{
|
|
1221
|
+
// into the world, kept inside its tile there too, as adding the layer's place can round it onto the edge
|
|
1222
|
+
const cell = hitPos.floor();
|
|
1220
1223
|
hitPos.x += offset.x, hitPos.y += offset.y;
|
|
1224
|
+
const inside = (v, low)=> v < low ? low : v < low + 1 ? v : low + 1 - max(1e-9, abs(low) * 1e-15);
|
|
1225
|
+
hitPos.x = inside(hitPos.x, cell.x + offset.x);
|
|
1226
|
+
hitPos.y = inside(hitPos.y, cell.y + offset.y);
|
|
1227
|
+
}
|
|
1221
1228
|
if (debugRaycast && hitPos)
|
|
1222
1229
|
{
|
|
1223
1230
|
const tilePos = hitPos.floor().add(vec2(.5));
|
package/src/engineUtilities.js
CHANGED
|
@@ -29,7 +29,7 @@ class Timer
|
|
|
29
29
|
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
30
30
|
constructor(timeLeft, useRealTime=false)
|
|
31
31
|
{
|
|
32
|
-
ASSERT(timeLeft === undefined || isNumber(timeLeft), '
|
|
32
|
+
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Timer: time must be a number of seconds', timeLeft);
|
|
33
33
|
this.useRealTime = useRealTime;
|
|
34
34
|
const globalTime = this.getGlobalTime();
|
|
35
35
|
/** @type {number|undefined} */
|
|
@@ -42,7 +42,7 @@ class Timer
|
|
|
42
42
|
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
43
43
|
set(timeLeft=0)
|
|
44
44
|
{
|
|
45
|
-
ASSERT(isNumber(timeLeft), 'Timer
|
|
45
|
+
ASSERT(isNumber(timeLeft), 'Timer.set: time must be a number of seconds', timeLeft);
|
|
46
46
|
const globalTime = this.getGlobalTime();
|
|
47
47
|
this.time = globalTime + timeLeft;
|
|
48
48
|
this.setTime = timeLeft;
|
|
@@ -121,9 +121,9 @@ function formatTime(t)
|
|
|
121
121
|
* @memberof Utilities */
|
|
122
122
|
async function fetchJSON(url)
|
|
123
123
|
{
|
|
124
|
-
const response = await
|
|
124
|
+
const response = await loadFetch(url, 'fetchJSON');
|
|
125
125
|
if (!response.ok)
|
|
126
|
-
throw new Error(`
|
|
126
|
+
throw new Error(`fetchJSON: could not load ${url}, HTTP ${response.status} ${response.statusText}`);
|
|
127
127
|
const text = await response.text();
|
|
128
128
|
let json;
|
|
129
129
|
try { json = JSON.parse(text); }
|
|
@@ -140,6 +140,21 @@ async function fetchJSON(url)
|
|
|
140
140
|
// is a file's text a web page, as a dev server sends for a path it does not have
|
|
141
141
|
const loadIsWebPage = (text)=> /^\s*</.test(text);
|
|
142
142
|
|
|
143
|
+
// fetch a file for a loader, a failure to reach it named with the file and who asked, and with the usual cause when
|
|
144
|
+
// the page was opened from a file on disk, where the browser lets it load nothing
|
|
145
|
+
async function loadFetch(url, who)
|
|
146
|
+
{
|
|
147
|
+
try { return await fetch(url); }
|
|
148
|
+
catch (error)
|
|
149
|
+
{
|
|
150
|
+
const fromDisk = globalThis.location?.protocol === 'file:';
|
|
151
|
+
const failed = new Error(`${who}: could not load ${url}, ${error.message}` + (fromDisk ?
|
|
152
|
+
', the page was opened from a file, serve it from a local web server' : ''));
|
|
153
|
+
/** @type {any} */ (failed).cause = error; // what the browser said, as the error option newer libraries know
|
|
154
|
+
throw failed;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
143
158
|
///////////////////////////////////////////////////////////////////////////////
|
|
144
159
|
|
|
145
160
|
/** Save a text file to disk
|
|
@@ -249,7 +264,7 @@ function readSaveData(saveName, defaultSaveData)
|
|
|
249
264
|
if (data)
|
|
250
265
|
{
|
|
251
266
|
try { loadedData = JSON.parse(data); }
|
|
252
|
-
catch {
|
|
267
|
+
catch { console.warn('readSaveData: the save ' + saveName + ' can not be read, using the defaults'); }
|
|
253
268
|
if (!loadedData || typeof loadedData !== 'object' || isArray(loadedData))
|
|
254
269
|
loadedData = {}; // only an object of saved values, never a string spread into its letters
|
|
255
270
|
}
|
|
@@ -268,9 +283,26 @@ function writeSaveData(saveName, saveData)
|
|
|
268
283
|
ASSERT(isStringLike(saveName), 'writeSaveData requires saveName string');
|
|
269
284
|
ASSERT(typeof saveData === 'object' && saveData !== null && !isArray(saveData),
|
|
270
285
|
'writeSaveData: save data must be an object, readSaveData reads it back into one');
|
|
271
|
-
// tolerate localStorage being unavailable or quota exceeded
|
|
272
|
-
|
|
273
|
-
|
|
286
|
+
// tolerate localStorage being unavailable or quota exceeded; in release too, a player's save going nowhere is
|
|
287
|
+
// not silent, and the warning says which it was
|
|
288
|
+
let text;
|
|
289
|
+
try { text = JSON.stringify(saveData); }
|
|
290
|
+
catch (error)
|
|
291
|
+
{
|
|
292
|
+
console.warn('writeSaveData: the save ' + saveName + ' can not be written as JSON, ' + error.message);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (text === undefined) // a toJSON that returns nothing, which would be stored as the text undefined
|
|
296
|
+
{
|
|
297
|
+
console.warn('writeSaveData: the save ' + saveName + ' is nothing as JSON, it is not written');
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
try { localStorage.setItem(saveName, text); return true; }
|
|
301
|
+
catch
|
|
302
|
+
{
|
|
303
|
+
console.warn('writeSaveData: the save ' + saveName + ' could not be written, storage is full or off');
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
274
306
|
}
|
|
275
307
|
|
|
276
308
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -298,7 +330,7 @@ function noise1D(x)
|
|
|
298
330
|
return lerp(noiseHash(i), noiseHash(i + 1), smoothStep(x - i));
|
|
299
331
|
}
|
|
300
332
|
|
|
301
|
-
/** 2D value noise — returns a smooth value in [0, 1] for any real (x, y)
|
|
333
|
+
/** 2D value noise — returns a smooth value in [0, 1] for any real (x, y)
|
|
302
334
|
* @param {number} x
|
|
303
335
|
* @param {number} y
|
|
304
336
|
* @return {number}
|
package/src/engineWebGL.js
CHANGED
|
@@ -45,7 +45,7 @@ function glClampTextureSize(size) { return glMaxTextureSize ? min(size, glMaxTex
|
|
|
45
45
|
let glAntialias = true;
|
|
46
46
|
|
|
47
47
|
// WebGL internal variables not exposed to documentation
|
|
48
|
-
let glMipmappedTextures = new WeakSet, glMipmapsUntilTarget = new WeakSet, glMipmapsStale = new Set, glPremultipliedTextures = new WeakSet, glShaderPremultiplied, glEnableBeforeLoss = true, glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos = new Set, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glShaderObjects = [], glCustomShader, glBatchShader, glProgramCustom, glTransform, glRenderTargetSaved, glUniformLocations = new WeakMap, glCanBeEnabled = true;
|
|
48
|
+
let glMipmappedTextures = new WeakSet, glMipmapsUntilTarget = new WeakSet, glMipmapsStale = new Set, glPremultipliedTextures = new WeakSet, glShaderPremultiplied, glEnableBeforeLoss = true, glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos = new Set, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glShaderObjects = [], glCustomShader, glBatchShader, glProgramCustom, glTransform, glRenderTargetSaved, glUniformLocations = new WeakMap, glCanBeEnabled = true, glDeviceFailed = false;
|
|
49
49
|
let glFailedPrograms = new WeakSet; // programs that did not build in a release build, which nothing draws with
|
|
50
50
|
// ANDed onto every packed color as a draw is queued; the light system's shadow pass sets 0xff000000
|
|
51
51
|
// to draw everything black with its alpha kept (rgbaInt packs alpha in the top byte)
|
|
@@ -120,6 +120,7 @@ function glInit(rootElement)
|
|
|
120
120
|
glCanvas = glContext = undefined;
|
|
121
121
|
glEnable = false;
|
|
122
122
|
glCanBeEnabled = false;
|
|
123
|
+
glDeviceFailed = true; // the device, not the game, turned it off
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -224,6 +225,7 @@ function glInit(rootElement)
|
|
|
224
225
|
{
|
|
225
226
|
console.error('LittleJS: WebGL can not draw on this device, using Canvas2D');
|
|
226
227
|
glEnable = glCanBeEnabled = false;
|
|
228
|
+
glDeviceFailed = true;
|
|
227
229
|
glCanvas.style.display = 'none';
|
|
228
230
|
}
|
|
229
231
|
|
|
@@ -505,6 +507,7 @@ function glUniformLocation(program, name)
|
|
|
505
507
|
// color, then the sprite's color and additive color apply as the engine's own fragment shader does
|
|
506
508
|
function glShaderProgram(shader)
|
|
507
509
|
{
|
|
510
|
+
shader.program || glShaderTrack(shader);
|
|
508
511
|
const program = shader.program ||= glCreateProgram(gl_VERTEX_SOURCE,
|
|
509
512
|
'#version 300 es\n' +
|
|
510
513
|
'precision highp float;' +
|
|
@@ -512,13 +515,33 @@ function glShaderProgram(shader)
|
|
|
512
515
|
'uniform vec3 iResolution;' + // canvas size in pixels
|
|
513
516
|
'uniform float iTime;' + // engine time
|
|
514
517
|
'uniform bool premultipliedTexture;' + // is the texture premultiplied, a render target or a smooth image
|
|
515
|
-
'in vec2 v,l;in vec4 d,e;out vec4 c
|
|
518
|
+
'in vec2 v,l;in vec4 d,e;out vec4 c;' +
|
|
519
|
+
// main first, the snippet's mainImage declared for it, so a define in the snippet can not reach main's names
|
|
520
|
+
'void mainImage(out vec4,vec2);' +
|
|
521
|
+
'void main(){vec4 t;mainImage(t,v);' + gl_FRAGMENT_TINT_SOURCE + '}\n' + // a define needs its own line
|
|
516
522
|
'#define localUV l\n' +
|
|
517
|
-
shader.fragmentCode + '\n'
|
|
518
|
-
'void main(){vec4 t;mainImage(t,v);' + gl_FRAGMENT_TINT_SOURCE + '}');
|
|
523
|
+
shader.fragmentCode + '\n');
|
|
519
524
|
return glFailedPrograms.has(program) ? glShader : program;
|
|
520
525
|
}
|
|
521
526
|
|
|
527
|
+
// a Shader compiled again after dispose is kept for a lost context again
|
|
528
|
+
function glShaderTrack(shader) { glShaderObjects.includes(shader) || glShaderObjects.push(shader); }
|
|
529
|
+
|
|
530
|
+
// let go of a Shader's programs, 2D and 3D, and of its place in the list; a batch drawing with it is drawn first
|
|
531
|
+
function glShaderDispose(shader)
|
|
532
|
+
{
|
|
533
|
+
const i = glShaderObjects.indexOf(shader);
|
|
534
|
+
i < 0 || glShaderObjects.splice(i, 1);
|
|
535
|
+
if (glContext && !headlessMode)
|
|
536
|
+
{
|
|
537
|
+
if (glBatchShader === shader) // a batch drawing with it is drawn first, the batch holds the Shader
|
|
538
|
+
glFlush();
|
|
539
|
+
for (const program of [shader.program, shader.program3D])
|
|
540
|
+
program && glContext.deleteProgram(program);
|
|
541
|
+
}
|
|
542
|
+
shader.program = shader.program3D = undefined;
|
|
543
|
+
}
|
|
544
|
+
|
|
522
545
|
/** Create WebGL texture from an image and init the texture settings
|
|
523
546
|
* Restores the active texture when done
|
|
524
547
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} [image]
|
|
@@ -574,8 +597,16 @@ function glCreateTexture(image, wrap=false, pixelated=tilesPixelated)
|
|
|
574
597
|
* @memberof WebGL */
|
|
575
598
|
function glDeleteTexture(texture)
|
|
576
599
|
{
|
|
577
|
-
if (!glContext) return;
|
|
578
|
-
|
|
600
|
+
if (!glContext || !texture) return;
|
|
601
|
+
|
|
602
|
+
// what is batched with it is drawn first, since deleting unbinds it, and nothing keeps it after
|
|
603
|
+
if (texture === glActiveTexture)
|
|
604
|
+
{
|
|
605
|
+
glFlush();
|
|
606
|
+
glActiveTexture = undefined; // so nothing binds the deleted texture again
|
|
607
|
+
}
|
|
608
|
+
glMipmapsStale.delete(texture);
|
|
609
|
+
glPremultipliedTextures.delete(texture);
|
|
579
610
|
glContext.deleteTexture(texture);
|
|
580
611
|
}
|
|
581
612
|
|
|
@@ -651,13 +682,6 @@ function glUnregisterTextureInfo(textureInfo)
|
|
|
651
682
|
// unset and destroy the texture, drawing what is batched with it first, since deleting unbinds it
|
|
652
683
|
const glTexture = textureInfo.glTexture;
|
|
653
684
|
textureInfo.glTexture = undefined;
|
|
654
|
-
if (glTexture && glTexture === glActiveTexture)
|
|
655
|
-
{
|
|
656
|
-
glFlush();
|
|
657
|
-
glActiveTexture = undefined; // so nothing binds the deleted texture again
|
|
658
|
-
}
|
|
659
|
-
glMipmapsStale.delete(glTexture);
|
|
660
|
-
glPremultipliedTextures.delete(glTexture);
|
|
661
685
|
glDeleteTexture(glTexture);
|
|
662
686
|
}
|
|
663
687
|
|