@kurot/core 1.0.20 → 1.0.22

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.
Files changed (109) hide show
  1. package/dist/kurot/display/DisplayObject.d.ts.map +1 -1
  2. package/dist/kurot/display/DisplayObject.js +6 -0
  3. package/dist/kurot/display/DisplayObject.js.map +1 -1
  4. package/dist/kurot/display/Mesh.d.ts +5 -2
  5. package/dist/kurot/display/Mesh.d.ts.map +1 -1
  6. package/dist/kurot/display/Mesh.js +5 -2
  7. package/dist/kurot/display/Mesh.js.map +1 -1
  8. package/dist/kurot/filters/BloomFilter.d.ts +40 -0
  9. package/dist/kurot/filters/BloomFilter.d.ts.map +1 -0
  10. package/dist/kurot/filters/BloomFilter.js +51 -0
  11. package/dist/kurot/filters/BloomFilter.js.map +1 -0
  12. package/dist/kurot/filters/BloomShaders.d.ts +10 -0
  13. package/dist/kurot/filters/BloomShaders.d.ts.map +1 -0
  14. package/dist/kurot/filters/BloomShaders.js +54 -0
  15. package/dist/kurot/filters/BloomShaders.js.map +1 -0
  16. package/dist/kurot/filters/BlurFilter.d.ts +4 -0
  17. package/dist/kurot/filters/BlurFilter.d.ts.map +1 -1
  18. package/dist/kurot/filters/BlurFilter.js +23 -6
  19. package/dist/kurot/filters/BlurFilter.js.map +1 -1
  20. package/dist/kurot/filters/CustomFilter.d.ts +44 -1
  21. package/dist/kurot/filters/CustomFilter.d.ts.map +1 -1
  22. package/dist/kurot/filters/CustomFilter.js +56 -17
  23. package/dist/kurot/filters/CustomFilter.js.map +1 -1
  24. package/dist/kurot/filters/DropShadowFilter.d.ts.map +1 -1
  25. package/dist/kurot/filters/DropShadowFilter.js +1 -0
  26. package/dist/kurot/filters/DropShadowFilter.js.map +1 -1
  27. package/dist/kurot/filters/Filter.d.ts +15 -0
  28. package/dist/kurot/filters/Filter.d.ts.map +1 -1
  29. package/dist/kurot/filters/Filter.js +58 -0
  30. package/dist/kurot/filters/Filter.js.map +1 -1
  31. package/dist/kurot/filters/FilterTexture.d.ts +21 -0
  32. package/dist/kurot/filters/FilterTexture.d.ts.map +1 -0
  33. package/dist/kurot/filters/FilterTexture.js +2 -0
  34. package/dist/kurot/filters/FilterTexture.js.map +1 -0
  35. package/dist/kurot/filters/GlowFilter.d.ts.map +1 -1
  36. package/dist/kurot/filters/GlowFilter.js +6 -0
  37. package/dist/kurot/filters/GlowFilter.js.map +1 -1
  38. package/dist/kurot/filters/MultiPassFilter.d.ts +41 -0
  39. package/dist/kurot/filters/MultiPassFilter.d.ts.map +1 -0
  40. package/dist/kurot/filters/MultiPassFilter.js +53 -0
  41. package/dist/kurot/filters/MultiPassFilter.js.map +1 -0
  42. package/dist/kurot/filters/index.d.ts +6 -0
  43. package/dist/kurot/filters/index.d.ts.map +1 -1
  44. package/dist/kurot/filters/index.js +2 -0
  45. package/dist/kurot/filters/index.js.map +1 -1
  46. package/dist/kurot/player/Player.d.ts.map +1 -1
  47. package/dist/kurot/player/Player.js +1 -0
  48. package/dist/kurot/player/Player.js.map +1 -1
  49. package/dist/kurot/player/canvas/CanvasRenderer.d.ts.map +1 -1
  50. package/dist/kurot/player/canvas/CanvasRenderer.js +26 -16
  51. package/dist/kurot/player/canvas/CanvasRenderer.js.map +1 -1
  52. package/dist/kurot/player/pipes/BitmapPipe.d.ts.map +1 -1
  53. package/dist/kurot/player/pipes/BitmapPipe.js +20 -10
  54. package/dist/kurot/player/pipes/BitmapPipe.js.map +1 -1
  55. package/dist/kurot/player/pipes/FilterPipe.d.ts.map +1 -1
  56. package/dist/kurot/player/pipes/FilterPipe.js +23 -18
  57. package/dist/kurot/player/pipes/FilterPipe.js.map +1 -1
  58. package/dist/kurot/player/pipes/MaskPipe.d.ts.map +1 -1
  59. package/dist/kurot/player/pipes/MaskPipe.js +6 -0
  60. package/dist/kurot/player/pipes/MaskPipe.js.map +1 -1
  61. package/dist/kurot/player/pipes/filter-bounds.d.ts +8 -0
  62. package/dist/kurot/player/pipes/filter-bounds.d.ts.map +1 -0
  63. package/dist/kurot/player/pipes/filter-bounds.js +25 -0
  64. package/dist/kurot/player/pipes/filter-bounds.js.map +1 -0
  65. package/dist/kurot/player/webgl/WebGLFilterSystem.d.ts +27 -0
  66. package/dist/kurot/player/webgl/WebGLFilterSystem.d.ts.map +1 -0
  67. package/dist/kurot/player/webgl/WebGLFilterSystem.js +119 -0
  68. package/dist/kurot/player/webgl/WebGLFilterSystem.js.map +1 -0
  69. package/dist/kurot/player/webgl/WebGLFilterTargetPool.d.ts +18 -0
  70. package/dist/kurot/player/webgl/WebGLFilterTargetPool.d.ts.map +1 -0
  71. package/dist/kurot/player/webgl/WebGLFilterTargetPool.js +80 -0
  72. package/dist/kurot/player/webgl/WebGLFilterTargetPool.js.map +1 -0
  73. package/dist/kurot/player/webgl/WebGLFilterTextures.d.ts +17 -0
  74. package/dist/kurot/player/webgl/WebGLFilterTextures.d.ts.map +1 -0
  75. package/dist/kurot/player/webgl/WebGLFilterTextures.js +124 -0
  76. package/dist/kurot/player/webgl/WebGLFilterTextures.js.map +1 -0
  77. package/dist/kurot/player/webgl/WebGLProgram.d.ts +6 -2
  78. package/dist/kurot/player/webgl/WebGLProgram.d.ts.map +1 -1
  79. package/dist/kurot/player/webgl/WebGLProgram.js +29 -8
  80. package/dist/kurot/player/webgl/WebGLProgram.js.map +1 -1
  81. package/dist/kurot/player/webgl/WebGLRenderBuffer.d.ts +2 -0
  82. package/dist/kurot/player/webgl/WebGLRenderBuffer.d.ts.map +1 -1
  83. package/dist/kurot/player/webgl/WebGLRenderBuffer.js +3 -0
  84. package/dist/kurot/player/webgl/WebGLRenderBuffer.js.map +1 -1
  85. package/dist/kurot/player/webgl/WebGLRenderContext.d.ts +15 -6
  86. package/dist/kurot/player/webgl/WebGLRenderContext.d.ts.map +1 -1
  87. package/dist/kurot/player/webgl/WebGLRenderContext.js +200 -145
  88. package/dist/kurot/player/webgl/WebGLRenderContext.js.map +1 -1
  89. package/dist/kurot/player/webgl/WebGLRenderer.d.ts.map +1 -1
  90. package/dist/kurot/player/webgl/WebGLRenderer.js +143 -110
  91. package/dist/kurot/player/webgl/WebGLRenderer.js.map +1 -1
  92. package/dist/kurot/player/webgl/WebGLUtils.d.ts +2 -2
  93. package/dist/kurot/player/webgl/WebGLUtils.d.ts.map +1 -1
  94. package/dist/kurot/player/webgl/WebGLUtils.js +35 -12
  95. package/dist/kurot/player/webgl/WebGLUtils.js.map +1 -1
  96. package/dist/kurot/player/webgl/WebGLVertexArrayObject.d.ts +2 -0
  97. package/dist/kurot/player/webgl/WebGLVertexArrayObject.d.ts.map +1 -1
  98. package/dist/kurot/player/webgl/WebGLVertexArrayObject.js +3 -1
  99. package/dist/kurot/player/webgl/WebGLVertexArrayObject.js.map +1 -1
  100. package/dist/kurot/player/webgl/custom-filter-uniforms.d.ts +6 -0
  101. package/dist/kurot/player/webgl/custom-filter-uniforms.d.ts.map +1 -0
  102. package/dist/kurot/player/webgl/custom-filter-uniforms.js +116 -0
  103. package/dist/kurot/player/webgl/custom-filter-uniforms.js.map +1 -0
  104. package/dist/kurot/player/webgl/split-mesh.d.ts +9 -0
  105. package/dist/kurot/player/webgl/split-mesh.d.ts.map +1 -0
  106. package/dist/kurot/player/webgl/split-mesh.js +29 -0
  107. package/dist/kurot/player/webgl/split-mesh.js.map +1 -0
  108. package/dist/kurot/system/Capabilities.js +1 -1
  109. package/package.json +1 -1
@@ -1,3 +1,9 @@
1
+ import { splitMesh } from './split-mesh.js';
2
+ import { MultiPassFilter } from '../../filters/MultiPassFilter.js';
3
+ import { WebGLFilterSystem } from './WebGLFilterSystem.js';
4
+ import { CustomFilter } from '../../filters/CustomFilter.js';
5
+ import { uploadCustomUniforms } from './custom-filter-uniforms.js';
6
+ import { WebGLFilterTextures } from './WebGLFilterTextures.js';
1
7
  import { ColorMatrixFilter } from '../../filters/ColorMatrixFilter.js';
2
8
  import { BlurFilter } from '../../filters/BlurFilter.js';
3
9
  import { GlowFilter } from '../../filters/GlowFilter.js';
@@ -10,8 +16,6 @@ import { ShaderLib2, getBlurTier2, makeBlurHFrag2, makeBlurVFrag2 } from './shad
10
16
  import { SYM_GL_CONTEXT, SYM_PREMULTIPLIED, SYM_DEFAULT_EMPTY, SYM_SMOOTHING } from './WebGLUtils.js';
11
17
  import { WebGLRenderBuffer } from './WebGLRenderBuffer.js';
12
18
  import { MultiTextureBatcher, makeMultiCmd } from './MultiTextureBatcher.js';
13
- const BLUR_FRAMEBUFFER_POOL_LIMIT = 16;
14
- const BLUR_FRAMEBUFFER_POOL_BYTE_LIMIT = 64 * 1024 * 1024;
15
19
  const _gcRegistry = new FinalizationRegistry(({ gl, texture }) => {
16
20
  gl.deleteTexture(texture);
17
21
  });
@@ -37,6 +41,11 @@ export class WebGLRenderContext {
37
41
  resolution = 1;
38
42
  // ── Private fields ────────────────────────────────────────────────────────
39
43
  _vao;
44
+ _filterTextures;
45
+ _filterSystem;
46
+ _filterPassInputs = {};
47
+ _filterOutputWidth = 0;
48
+ _filterOutputHeight = 0;
40
49
  _batcher = new MultiTextureBatcher();
41
50
  _bufferStack = [];
42
51
  _currentBuffer;
@@ -50,10 +59,6 @@ export class WebGLRenderContext {
50
59
  _contextRestoredCallbacks = [];
51
60
  _trackedBitmapDatas = new Set();
52
61
  _uploadedVersions = new WeakMap();
53
- // ── Blur FBO pool ─────────────────────────────────────────────────────────
54
- _blurFboPool = new Map();
55
- _blurFboPoolSize = 0;
56
- _blurFboPoolBytes = 0;
57
62
  constructor(canvas) {
58
63
  this.surface = canvas;
59
64
  const gl2 = canvas.getContext('webgl2');
@@ -77,6 +82,8 @@ export class WebGLRenderContext {
77
82
  this.makeBlurV = makeBlurVFrag;
78
83
  }
79
84
  const gl = this.gl;
85
+ this._filterTextures = new WebGLFilterTextures(this);
86
+ this._filterSystem = new WebGLFilterSystem(this);
80
87
  gl.disable(gl.DEPTH_TEST);
81
88
  gl.disable(gl.CULL_FACE);
82
89
  gl.enable(gl.BLEND);
@@ -105,11 +112,11 @@ export class WebGLRenderContext {
105
112
  // ── Getters ───────────────────────────────────────────────────────────────
106
113
  /** Number of reusable framebuffers currently retained by the blur pipeline. */
107
114
  get blurFramebufferPoolSize() {
108
- return this._blurFboPoolSize;
115
+ return this._filterSystem.pool.size;
109
116
  }
110
117
  /** Estimated GPU bytes retained by the reusable blur framebuffer pool. */
111
118
  get blurFramebufferPoolBytes() {
112
- return this._blurFboPoolBytes;
119
+ return this._filterSystem.pool.bytes;
113
120
  }
114
121
  get activatedBuffer() {
115
122
  return this._currentBuffer;
@@ -184,10 +191,18 @@ export class WebGLRenderContext {
184
191
  this.gl.disable(this.gl.SCISSOR_TEST);
185
192
  }
186
193
  enableScissor(x, y, width, height) {
187
- this.drawCmdManager.pushEnableScissor(x, y, width, height);
194
+ this.flush();
195
+ if (this._currentBuffer) {
196
+ this._currentBuffer.enableScissor(x, y, width, height);
197
+ this._currentBuffer.hasScissor = true;
198
+ }
188
199
  }
189
200
  disableScissor() {
190
- this.drawCmdManager.pushDisableScissor();
201
+ this.flush();
202
+ if (this._currentBuffer) {
203
+ this._currentBuffer.disableScissor();
204
+ this._currentBuffer.hasScissor = false;
205
+ }
191
206
  }
192
207
  // ── Mask (stencil-based) ──────────────────────────────────────────────────
193
208
  pushMask(x, y, width, height) {
@@ -288,8 +303,13 @@ export class WebGLRenderContext {
288
303
  return;
289
304
  const buf = this._currentBuffer;
290
305
  if (meshVertices && meshIndices) {
291
- const meshNum = meshIndices.length / 3;
292
- if (this._vao.reachMaxSize(meshNum * 4, meshNum * 6))
306
+ if (meshVertices.length / 2 > WebGLVertexArrayObject.MAX_VERTICES || meshIndices.length > WebGLVertexArrayObject.MAX_INDICES) {
307
+ for (const chunk of splitMesh(meshVertices, meshUVs, meshIndices)) {
308
+ this.drawTexture(texture, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight, textureWidth, textureHeight, chunk.uvs, chunk.vertices, chunk.indices, _bounds, rotated, smoothing, flipY);
309
+ }
310
+ return;
311
+ }
312
+ if (this._vao.reachMaxSize(meshVertices.length / 2, meshIndices.length))
293
313
  this.flush();
294
314
  }
295
315
  else {
@@ -299,8 +319,6 @@ export class WebGLRenderContext {
299
319
  if (smoothing !== undefined && texture[SYM_SMOOTHING] !== smoothing) {
300
320
  this.drawCmdManager.pushChangeSmoothing(texture, smoothing);
301
321
  }
302
- if (meshUVs)
303
- this._vao.changeToMeshIndices();
304
322
  // ── Multi-texture path (plain quads without filter) ───────────────────
305
323
  const useMulti = !this.activeFilter && !meshVertices && this._maxTextureUnits > 1;
306
324
  if (useMulti) {
@@ -322,6 +340,8 @@ export class WebGLRenderContext {
322
340
  // ── Single-texture path (filter, mesh, or single-unit device) ─────────
323
341
  if (this._vao.isMultiTexture())
324
342
  this.flush();
343
+ if (meshUVs)
344
+ this._vao.changeToMeshIndices();
325
345
  this._vao.cacheArrays(buf, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight, textureWidth, textureHeight, meshUVs, meshVertices, meshIndices, rotated, 0, flipY);
326
346
  const count = meshIndices ? meshIndices.length / 3 : 2;
327
347
  this.drawCmdManager.pushDrawTexture(texture, count, this.activeFilter, textureWidth, textureHeight);
@@ -339,129 +359,115 @@ export class WebGLRenderContext {
339
359
  * framebuffer feedback and state leakage.
340
360
  */
341
361
  compositeFilterResult(filters, offscreen) {
342
- const target = offscreen.rootRenderTarget;
343
- if (!target?.texture) {
344
- return;
345
- }
346
- const w = target.width;
347
- const h = target.height;
348
- this.flush();
349
- for (const filter of filters) {
350
- if (filter instanceof BlurFilter && (filter.blurX > 0 || filter.blurY > 0)) {
351
- this._drawBlurPingPong(target.texture, w, h, filter, offscreen, offscreen.resolution);
352
- }
353
- }
354
- if (this._currentBuffer) {
355
- this._currentBuffer.rootRenderTarget.activate();
356
- this.onResize(this._currentBuffer.width, this._currentBuffer.height);
357
- }
358
- const nonBlurFilter = filters.find(f => !(f instanceof BlurFilter));
359
- this._filterResolution = offscreen.resolution;
360
- this.activeFilter = nonBlurFilter;
361
- this.drawTexture(target.texture, 0, 0, w, h, 0, 0, w / offscreen.resolution, h / offscreen.resolution, w, h);
362
- this.activeFilter = undefined;
363
362
  this.flush();
364
- this._filterResolution = 1;
365
- }
366
- _drawBlurPingPong(texture, w, h, filter, buffer, resolution) {
363
+ const parent = this._currentBuffer;
364
+ if (!parent || !offscreen.rootRenderTarget.texture)
365
+ return;
367
366
  const gl = this.gl;
368
- // ── Acquire a temporary FBO from the pool (or create a new one) ───────
369
- const poolKey = `${w}x${h}`;
370
- let tmpEntry = this._takeBlurFramebuffer(poolKey);
371
- if (!tmpEntry) {
372
- const tmpTex = gl.createTexture();
373
- gl.bindTexture(gl.TEXTURE_2D, tmpTex);
374
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
375
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
376
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
377
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
378
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
379
- const tmpFbo = gl.createFramebuffer();
380
- gl.bindFramebuffer(gl.FRAMEBUFFER, tmpFbo);
381
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tmpTex, 0);
382
- tmpEntry = { texture: tmpTex, fbo: tmpFbo, byteSize: w * h * 4 };
383
- }
384
- gl.bindFramebuffer(gl.FRAMEBUFFER, tmpEntry.fbo);
385
- gl.viewport(0, 0, w, h);
386
- gl.clearColor(0, 0, 0, 0);
387
- gl.clear(gl.COLOR_BUFFER_BIT);
388
- // ── Select shader tier based on actual blur radius ────────────────────
389
- const blurX = filter.blurX * resolution;
390
- const blurY = filter.blurY * resolution;
391
- const hTier = this.blurTierFn(blurX);
392
- const vTier = this.blurTierFn(blurY);
393
- const hKey = `blur_h_${hTier}`;
394
- const vKey = `blur_v_${vTier}`;
395
- // ── Pass 1: horizontal blur → tmpFbo ──────────────────────────────────
396
- const hProg = WebGLProgram.get(gl, this.shaders.fullscreen_vert, this.makeBlurH(hTier), hKey);
397
- this._drawFullscreenQuad(hProg, texture, w, h, prog => {
398
- const uBlurX = prog.uniforms['blurX'];
399
- const uSize = prog.uniforms['uTextureSize'];
400
- if (uBlurX)
401
- gl.uniform1f(uBlurX, blurX);
402
- if (uSize)
403
- gl.uniform2f(uSize, w, h);
404
- });
405
- // ── Pass 2: vertical blur → offscreen FBO ────────────────────────────
406
- buffer.rootRenderTarget.activate();
407
- this.onResize(w, h);
408
- const vProg = WebGLProgram.get(gl, this.shaders.fullscreen_vert, this.makeBlurV(vTier), vKey);
409
- this._drawFullscreenQuad(vProg, tmpEntry.texture, w, h, prog => {
410
- const uBlurY = prog.uniforms['blurY'];
411
- const uSize = prog.uniforms['uTextureSize'];
412
- if (uBlurY)
413
- gl.uniform1f(uBlurY, blurY);
414
- if (uSize)
415
- gl.uniform2f(uSize, w, h);
416
- });
417
- // ── Return the temporary FBO to the pool ──────────────────────────────
418
- this._returnBlurFramebuffer(poolKey, tmpEntry);
419
- }
420
- _takeBlurFramebuffer(key) {
421
- const entries = this._blurFboPool.get(key);
422
- if (!entries)
423
- return undefined;
424
- const entry = entries.pop();
425
- if (!entry)
426
- return undefined;
427
- this._blurFboPoolSize--;
428
- this._blurFboPoolBytes -= entry.byteSize;
429
- if (entries.length === 0) {
430
- this._blurFboPool.delete(key);
367
+ const blend = gl.isEnabled(gl.BLEND);
368
+ const stencil = gl.isEnabled(gl.STENCIL_TEST);
369
+ const scissor = gl.isEnabled(gl.SCISSOR_TEST);
370
+ const alpha = parent.globalAlpha;
371
+ const tint = parent.globalTintColor;
372
+ const last = filters[filters.length - 1];
373
+ const finalFilter = last instanceof BlurFilter || last instanceof MultiPassFilter || last instanceof CustomFilter ? undefined : last;
374
+ try {
375
+ gl.disable(gl.BLEND);
376
+ gl.disable(gl.STENCIL_TEST);
377
+ gl.disable(gl.SCISSOR_TEST);
378
+ this._filterSystem.render(finalFilter ? filters.slice(0, -1) : filters, offscreen.rootRenderTarget, offscreen.resolution, output => {
379
+ parent.rootRenderTarget.activate();
380
+ this.onResize(parent.width, parent.height);
381
+ if (blend) {
382
+ gl.enable(gl.BLEND);
383
+ }
384
+ if (stencil) {
385
+ gl.enable(gl.STENCIL_TEST);
386
+ }
387
+ if (scissor) {
388
+ gl.enable(gl.SCISSOR_TEST);
389
+ }
390
+ // Subtree vertices already contain world alpha and tint.
391
+ parent.globalAlpha = 1;
392
+ parent.globalTintColor = 0xffffff;
393
+ this._filterResolution = output.width * offscreen.resolution / offscreen.width;
394
+ this.activeFilter = finalFilter;
395
+ this.drawFramebufferTexture(output.texture, output.width, output.height, 0, 0, offscreen.width / offscreen.resolution, offscreen.height / offscreen.resolution);
396
+ this.flush();
397
+ });
398
+ }
399
+ finally {
400
+ this.activeFilter = undefined;
401
+ this._filterResolution = 1;
402
+ this._filterPassInputs = {};
403
+ parent.globalAlpha = alpha;
404
+ parent.globalTintColor = tint;
405
+ parent.rootRenderTarget.activate();
406
+ this.onResize(parent.width, parent.height);
407
+ if (blend) {
408
+ gl.enable(gl.BLEND);
409
+ }
410
+ else {
411
+ gl.disable(gl.BLEND);
412
+ }
413
+ if (stencil) {
414
+ gl.enable(gl.STENCIL_TEST);
415
+ }
416
+ else {
417
+ gl.disable(gl.STENCIL_TEST);
418
+ }
419
+ if (scissor) {
420
+ gl.enable(gl.SCISSOR_TEST);
421
+ }
422
+ else {
423
+ gl.disable(gl.SCISSOR_TEST);
424
+ }
431
425
  }
432
- return entry;
433
426
  }
434
- _returnBlurFramebuffer(key, entry) {
435
- if (entry.byteSize > BLUR_FRAMEBUFFER_POOL_BYTE_LIMIT) {
436
- this.gl.deleteTexture(entry.texture);
437
- this.gl.deleteFramebuffer(entry.fbo);
438
- return;
439
- }
440
- while (this._blurFboPoolSize >= BLUR_FRAMEBUFFER_POOL_LIMIT ||
441
- this._blurFboPoolBytes + entry.byteSize > BLUR_FRAMEBUFFER_POOL_BYTE_LIMIT) {
442
- const oldest = this._blurFboPool.entries().next().value;
443
- if (!oldest)
444
- break;
445
- const [oldestKey, entries] = oldest;
446
- const evicted = entries.pop();
447
- if (evicted) {
448
- this.gl.deleteTexture(evicted.texture);
449
- this.gl.deleteFramebuffer(evicted.fbo);
450
- this._blurFboPoolSize--;
451
- this._blurFboPoolBytes -= evicted.byteSize;
427
+ $drawFilterPass(filter, input, output, resolution, textures = {}) {
428
+ if (input === output || Object.values(textures).some(value => value.texture === output.texture)) {
429
+ throw new Error('A filter cannot read from its output framebuffer.');
430
+ }
431
+ output.activate();
432
+ this.gl.viewport(0, 0, output.width, output.height);
433
+ // Custom vertex shaders may cover only part of the output or discard fragments.
434
+ output.clear();
435
+ this._filterResolution = filter instanceof CustomFilter ? resolution : resolution * input.width / output.width;
436
+ this._filterOutputWidth = output.width;
437
+ this._filterOutputHeight = output.height;
438
+ this._filterPassInputs = textures;
439
+ try {
440
+ this._drawFullscreenQuad(this._getTextureProgram(filter), input.texture, output.width, output.height, program => this._uploadFilterUniforms(program, filter, input.width, input.height));
441
+ }
442
+ finally {
443
+ this._filterPassInputs = {};
444
+ }
445
+ }
446
+ $drawBlurPass(horizontal, input, output, radius) {
447
+ if (input === output)
448
+ throw new Error('A blur cannot read from its output framebuffer.');
449
+ output.activate();
450
+ this.gl.viewport(0, 0, output.width, output.height);
451
+ const tier = this.blurTierFn(radius);
452
+ const source = horizontal ? this.makeBlurH(tier) : this.makeBlurV(tier);
453
+ const program = WebGLProgram.get(this.gl, this.shaders.fullscreen_vert, source, `blur_${horizontal ? 'h' : 'v'}_${tier}`);
454
+ this._drawFullscreenQuad(program, input.texture, output.width, output.height, prog => {
455
+ const radiusLocation = prog.uniforms[horizontal ? 'blurX' : 'blurY'];
456
+ if (radiusLocation) {
457
+ this.gl.uniform1f(radiusLocation, radius);
452
458
  }
453
- if (entries.length === 0) {
454
- this._blurFboPool.delete(oldestKey);
459
+ if (prog.uniforms.uTextureSize) {
460
+ this.gl.uniform2f(prog.uniforms.uTextureSize, input.width, input.height);
455
461
  }
456
- }
457
- let entries = this._blurFboPool.get(key);
458
- if (!entries) {
459
- entries = [];
460
- this._blurFboPool.set(key, entries);
461
- }
462
- entries.push(entry);
463
- this._blurFboPoolSize++;
464
- this._blurFboPoolBytes += entry.byteSize;
462
+ });
463
+ }
464
+ /**
465
+ * Releases idle effect targets and auxiliary image uploads. Images remain caller-owned.
466
+ */
467
+ releaseFilterResources() {
468
+ this.flush();
469
+ this._filterSystem.pool.clear();
470
+ this._filterTextures.clear();
465
471
  }
466
472
  _drawFullscreenQuad(prog, texture, w, h, setUniforms) {
467
473
  const gl = this.gl;
@@ -482,40 +488,44 @@ export class WebGLRenderContext {
482
488
  gl.enableVertexAttribArray(aColor);
483
489
  gl.vertexAttribPointer(aColor, 4, gl.UNSIGNED_BYTE, true, stride, 16);
484
490
  }
491
+ setUniforms(prog);
485
492
  const uProj = prog.uniforms['projectionVector'];
486
493
  if (uProj)
487
494
  gl.uniform2f(uProj, w / 2, -h / 2);
488
495
  const uSampler = prog.uniforms['uSampler'];
489
496
  if (uSampler)
490
497
  gl.uniform1i(uSampler, 0);
498
+ gl.activeTexture(gl.TEXTURE0);
491
499
  gl.bindTexture(gl.TEXTURE_2D, texture);
492
- setUniforms(prog);
493
500
  const f32 = new Float32Array(20);
494
501
  const u32 = new Uint32Array(f32.buffer);
495
502
  const packed = 0xffffffff;
496
503
  f32[0] = 0;
497
504
  f32[1] = 0;
498
505
  f32[2] = 0;
499
- f32[3] = 0;
506
+ f32[3] = 1;
500
507
  u32[4] = packed;
501
508
  f32[5] = w;
502
509
  f32[6] = 0;
503
510
  f32[7] = 1;
504
- f32[8] = 0;
511
+ f32[8] = 1;
505
512
  u32[9] = packed;
506
513
  f32[10] = w;
507
514
  f32[11] = h;
508
515
  f32[12] = 1;
509
- f32[13] = 1;
516
+ f32[13] = 0;
510
517
  u32[14] = packed;
511
518
  f32[15] = 0;
512
519
  f32[16] = h;
513
520
  f32[17] = 0;
514
- f32[18] = 1;
521
+ f32[18] = 0;
515
522
  u32[19] = packed;
516
523
  gl.bufferData(gl.ARRAY_BUFFER, f32, gl.STREAM_DRAW);
517
524
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
518
525
  gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
526
+ if (this._currentBuffer) {
527
+ this._currentBuffer.drawCalls++;
528
+ }
519
529
  gl.bufferData(gl.ARRAY_BUFFER, this._gpuVertexBufferSize, gl.DYNAMIC_DRAW);
520
530
  this._bindIndices = false;
521
531
  }
@@ -527,7 +537,40 @@ export class WebGLRenderContext {
527
537
  }
528
538
  // ── Execute ───────────────────────────────────────────────────────────────
529
539
  flush() {
530
- this._flush();
540
+ try {
541
+ this._flush();
542
+ }
543
+ catch (error) {
544
+ this.drawCmdManager.clear();
545
+ this._vao.clear();
546
+ this._batcher.reset();
547
+ this._bindIndices = false;
548
+ throw error;
549
+ }
550
+ }
551
+ $abortFrame(buffer) {
552
+ this.drawCmdManager.clear();
553
+ this._vao.clear();
554
+ this._batcher.reset();
555
+ this._bindIndices = false;
556
+ this.activeFilter = undefined;
557
+ this._bufferStack.length = 0;
558
+ this._bufferStack.push(buffer);
559
+ this._currentBuffer = buffer;
560
+ buffer.stencilList.length = 0;
561
+ buffer.stencilHandleCount = 0;
562
+ buffer.disableStencil();
563
+ buffer.disableScissor();
564
+ buffer.hasScissor = false;
565
+ buffer.rootRenderTarget.activate();
566
+ this.gl.activeTexture(this.gl.TEXTURE0);
567
+ this.gl.enable(this.gl.BLEND);
568
+ this.gl.disable(this.gl.SCISSOR_TEST);
569
+ this.gl.disable(this.gl.STENCIL_TEST);
570
+ this.gl.colorMask(true, true, true, true);
571
+ this.currentBlendMode = 'source-over';
572
+ this._applyBlend('source-over');
573
+ this.onResize(buffer.width, buffer.height);
531
574
  }
532
575
  // ── Private — flush & dispatch ────────────────────────────────────────────
533
576
  _onContextRestored() {
@@ -543,15 +586,14 @@ export class WebGLRenderContext {
543
586
  gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
544
587
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
545
588
  gl.bufferData(gl.ARRAY_BUFFER, this._gpuVertexBufferSize, gl.DYNAMIC_DRAW);
546
- WebGLProgram.clearCache();
589
+ WebGLProgram.clearCache(gl, true);
547
590
  this._bindIndices = false;
548
591
  this._batcher.reset();
549
592
  this.drawCmdManager.clear();
550
593
  this._vao.clear();
551
594
  this._defaultEmptyTexture = undefined;
552
- this._blurFboPool.clear();
553
- this._blurFboPoolSize = 0;
554
- this._blurFboPoolBytes = 0;
595
+ this._filterTextures.clear(true);
596
+ this._filterSystem.pool.clear(true);
555
597
  WebGLRenderBuffer.handleContextRestored(this);
556
598
  for (const ref of this._trackedBitmapDatas) {
557
599
  const bd = ref.deref();
@@ -730,10 +772,14 @@ export class WebGLRenderContext {
730
772
  gl.drawElements(gl.TRIANGLES, count * 3, gl.UNSIGNED_SHORT, indexOffset * 6);
731
773
  }
732
774
  _getTextureProgram(filter) {
775
+ if (filter instanceof CustomFilter) {
776
+ const source = filter.$getProgram(this.isWebGL2);
777
+ return WebGLProgram.get(this.gl, source.vertex ?? this.shaders.fullscreen_vert, source.fragment, 'CustomFilter');
778
+ }
733
779
  if (filter instanceof ColorMatrixFilter) {
734
780
  return WebGLProgram.get(this.gl, this.shaders.default_vert, this.shaders.colorTransform_frag, 'colorTransform');
735
781
  }
736
- if (filter instanceof BlurFilter || filter instanceof GlowFilter || filter instanceof DropShadowFilter) {
782
+ if (filter instanceof GlowFilter || filter instanceof DropShadowFilter) {
737
783
  return WebGLProgram.get(this.gl, this.shaders.default_vert, this.shaders.glow_frag, 'glow');
738
784
  }
739
785
  return WebGLProgram.get(this.gl, this.shaders.default_vert, this.shaders.texture_frag, 'texture');
@@ -765,6 +811,16 @@ export class WebGLRenderContext {
765
811
  if (uSampler)
766
812
  gl.uniform1i(uSampler, 0);
767
813
  gl.bindTexture(gl.TEXTURE_2D, texture);
814
+ this._uploadFilterUniforms(prog, filter, texW, texH);
815
+ gl.drawElements(gl.TRIANGLES, count * 3, gl.UNSIGNED_SHORT, indexOffset * 6);
816
+ }
817
+ _uploadFilterUniforms(prog, filter, texW, texH) {
818
+ const gl = this.gl;
819
+ if (filter instanceof CustomFilter) {
820
+ uploadCustomUniforms(gl, prog, filter, texW, texH, this._filterResolution, this._filterOutputWidth || texW, this._filterOutputHeight || texH, this._filterPassInputs);
821
+ this._filterTextures.bind(prog, filter, this._filterPassInputs);
822
+ return;
823
+ }
768
824
  if (filter instanceof ColorMatrixFilter) {
769
825
  const uMatrix = prog.uniforms['matrix'];
770
826
  const uAdd = prog.uniforms['colorAdd'];
@@ -808,7 +864,7 @@ export class WebGLRenderContext {
808
864
  gl.uniform1f(uDist, filter.distance * this._filterResolution);
809
865
  const uAngle = prog.uniforms['angle'];
810
866
  if (uAngle)
811
- gl.uniform1f(uAngle, -(filter.angle / 180) * Math.PI);
867
+ gl.uniform1f(uAngle, (filter.angle / 180) * Math.PI);
812
868
  const uHide = prog.uniforms['hideObject'];
813
869
  if (uHide)
814
870
  gl.uniform1f(uHide, filter.hideObject ? 1 : 0);
@@ -829,9 +885,8 @@ export class WebGLRenderContext {
829
885
  gl.uniform1f(uInner, filter instanceof GlowFilter && filter.inner ? 1 : 0);
830
886
  const uKnockout = prog.uniforms['knockout'];
831
887
  if (uKnockout)
832
- gl.uniform1f(uKnockout, filter instanceof GlowFilter && filter.knockout ? 1 : 0);
888
+ gl.uniform1f(uKnockout, filter instanceof GlowFilter && filter.knockout ? 0 : 1);
833
889
  }
834
- gl.drawElements(gl.TRIANGLES, count * 3, gl.UNSIGNED_SHORT, indexOffset * 6);
835
890
  }
836
891
  _drawRectBatch(indexOffset, count) {
837
892
  const gl = this.gl;