canvasframework 0.6.3 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,6 +89,12 @@ class WebGLCanvasAdapter {
89
89
  // ── Stats ─────────────────────────────────────────────────────────
90
90
  this.stats = { drawCalls: 0, glyphsCached: 0, culled: 0, instancesDrawn: 0 };
91
91
 
92
+ // ── Cache couleurs (initialisé ici pour éviter la création lazy dans _parseColor) ──
93
+ this._colorCache = new Map();
94
+
95
+ // ── Flag anti-récursion pour _rebuildAtlas ─────────────────────────
96
+ this._rebuilding = false;
97
+
92
98
  // Nettoyage périodique de l'atlas si saturation
93
99
  this._cleanupTimer = setInterval(() => this._maybeRebuildAtlas(), 30000);
94
100
  }
@@ -108,14 +114,17 @@ class WebGLCanvasAdapter {
108
114
  alpha: true,
109
115
  premultipliedAlpha: false,
110
116
  antialias: false,
111
- preserveDrawingBuffer: true, // nécessaire pour drawImage final
117
+ // preserveDrawingBuffer: false (défaut) — le résultat est copié via drawImage()
118
+ // immédiatement après chaque draw call, donc pas besoin de préserver le buffer.
119
+ // Laisser à false permet au driver GPU d'utiliser le double-buffering natif.
120
+ preserveDrawingBuffer: false,
112
121
  powerPreference: 'high-performance'
113
122
  });
114
123
 
115
124
  // Fallback WebGL1 si WebGL2 indispo
116
125
  this.gl = gl || this._glCanvas.getContext('webgl', {
117
126
  alpha: true, premultipliedAlpha: false,
118
- antialias: false, preserveDrawingBuffer: true
127
+ antialias: false, preserveDrawingBuffer: false
119
128
  });
120
129
 
121
130
  if (!this.gl) throw new Error('WebGL non disponible');
@@ -265,6 +274,54 @@ class WebGLCanvasAdapter {
265
274
  this._instanceData.byteLength,
266
275
  gl.DYNAMIC_DRAW
267
276
  );
277
+
278
+ // VAO WebGL2 : enregistre le layout des attributs une seule fois.
279
+ // Évite de rebinder chaque attribut à chaque draw call (gain CPU mesurable).
280
+ if (this._isWebGL2) {
281
+ this._vao = gl.createVertexArray();
282
+ gl.bindVertexArray(this._vao);
283
+ this._setupAttribPointers();
284
+ gl.bindVertexArray(null);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Configure les pointeurs d'attributs vertex (appelé une fois dans le VAO,
290
+ * ou à chaque draw call en WebGL1).
291
+ * @private
292
+ */
293
+ _setupAttribPointers() {
294
+ const gl = this.gl;
295
+ const loc = this._loc;
296
+ const ext = this._ext;
297
+ const stride = this._FLOATS_PER_INSTANCE * 4;
298
+
299
+ // Quad (diviseur 0 = même valeur pour toutes les instances)
300
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._quadBuf);
301
+ gl.enableVertexAttribArray(loc.quad);
302
+ gl.vertexAttribPointer(loc.quad, 2, gl.FLOAT, false, 0, 0);
303
+ if (ext) ext.vertexAttribDivisorANGLE(loc.quad, 0);
304
+ else gl.vertexAttribDivisor(loc.quad, 0);
305
+
306
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._instanceBuf);
307
+
308
+ // a_dst : vec4 @ offset 0
309
+ gl.enableVertexAttribArray(loc.dst);
310
+ gl.vertexAttribPointer(loc.dst, 4, gl.FLOAT, false, stride, 0);
311
+ if (ext) ext.vertexAttribDivisorANGLE(loc.dst, 1);
312
+ else gl.vertexAttribDivisor(loc.dst, 1);
313
+
314
+ // a_uv : vec4 @ offset 16
315
+ gl.enableVertexAttribArray(loc.uv);
316
+ gl.vertexAttribPointer(loc.uv, 4, gl.FLOAT, false, stride, 16);
317
+ if (ext) ext.vertexAttribDivisorANGLE(loc.uv, 1);
318
+ else gl.vertexAttribDivisor(loc.uv, 1);
319
+
320
+ // a_color : vec4 @ offset 32
321
+ gl.enableVertexAttribArray(loc.color);
322
+ gl.vertexAttribPointer(loc.color, 4, gl.FLOAT, false, stride, 32);
323
+ if (ext) ext.vertexAttribDivisorANGLE(loc.color, 1);
324
+ else gl.vertexAttribDivisor(loc.color, 1);
268
325
  }
269
326
 
270
327
  _setupAtlasTexture() {
@@ -373,8 +430,12 @@ class WebGLCanvasAdapter {
373
430
  /**
374
431
  * Reconstruit l'atlas depuis zéro avec uniquement les glyphes actuels.
375
432
  * Appelé quand l'atlas est saturé.
433
+ * Protégé contre la réentrance par _rebuilding.
376
434
  */
377
435
  _rebuildAtlas() {
436
+ if (this._rebuilding) return; // évite la récursion infinie
437
+ this._rebuilding = true;
438
+
378
439
  const ctx = this._atlasCtx;
379
440
  ctx.clearRect(0, 0, this.atlasSize, this.atlasSize);
380
441
 
@@ -391,7 +452,8 @@ class WebGLCanvasAdapter {
391
452
  if (char && font) this._getGlyph(char, font);
392
453
  }
393
454
 
394
- this._atlasDirty = true;
455
+ this._atlasDirty = true;
456
+ this._rebuilding = false;
395
457
  }
396
458
 
397
459
  _maybeRebuildAtlas() {
@@ -403,14 +465,18 @@ class WebGLCanvasAdapter {
403
465
 
404
466
  /**
405
467
  * Upload la texture atlas vers le GPU si elle a changé.
468
+ * Utilise texSubImage2D (mise à jour partielle) plutôt que texImage2D (ré-allocation
469
+ * complète) pour éviter un round-trip mémoire GPU inutile à chaque modification.
406
470
  * Appelé UNE seule fois par frame, juste avant le draw call.
407
471
  */
408
472
  _uploadAtlasIfDirty() {
409
473
  if (!this._atlasDirty) return;
410
474
  const gl = this.gl;
411
475
  gl.bindTexture(gl.TEXTURE_2D, this._atlasTex);
412
- gl.texImage2D(
413
- gl.TEXTURE_2D, 0, gl.RGBA,
476
+ // texSubImage2D met à jour les données sans réallouer le stockage GPU
477
+ gl.texSubImage2D(
478
+ gl.TEXTURE_2D, 0,
479
+ 0, 0, // offset x, y dans la texture
414
480
  gl.RGBA, gl.UNSIGNED_BYTE,
415
481
  this._atlasCanvas
416
482
  );
@@ -558,43 +624,29 @@ class WebGLCanvasAdapter {
558
624
  gl.bindTexture(gl.TEXTURE_2D, this._atlasTex);
559
625
  gl.uniform1i(loc.atlas, 0);
560
626
 
561
- // 6. Buffer quad (commun à toutes les instances)
562
- gl.bindBuffer(gl.ARRAY_BUFFER, this._quadBuf);
563
- gl.enableVertexAttribArray(loc.quad);
564
- gl.vertexAttribPointer(loc.quad, 2, gl.FLOAT, false, 0, 0);
565
- if (ext) ext.vertexAttribDivisorANGLE(loc.quad, 0);
566
- else gl.vertexAttribDivisor(loc.quad, 0);
567
-
568
- // 7. Buffer instances — upload uniquement les instances actives
569
- const stride = this._FLOATS_PER_INSTANCE * 4; // bytes
627
+ // 6. Upload uniquement les instances actives dans le buffer GPU
570
628
  gl.bindBuffer(gl.ARRAY_BUFFER, this._instanceBuf);
571
629
  gl.bufferSubData(
572
630
  gl.ARRAY_BUFFER, 0,
573
631
  this._instanceData.subarray(0, this._instanceCount * this._FLOATS_PER_INSTANCE)
574
632
  );
575
633
 
576
- // a_dst : vec4 @ offset 0
577
- gl.enableVertexAttribArray(loc.dst);
578
- gl.vertexAttribPointer(loc.dst, 4, gl.FLOAT, false, stride, 0);
579
- if (ext) ext.vertexAttribDivisorANGLE(loc.dst, 1);
580
- else gl.vertexAttribDivisor(loc.dst, 1);
581
-
582
- // a_uv : vec4 @ offset 16
583
- gl.enableVertexAttribArray(loc.uv);
584
- gl.vertexAttribPointer(loc.uv, 4, gl.FLOAT, false, stride, 16);
585
- if (ext) ext.vertexAttribDivisorANGLE(loc.uv, 1);
586
- else gl.vertexAttribDivisor(loc.uv, 1);
587
-
588
- // a_color : vec4 @ offset 32
589
- gl.enableVertexAttribArray(loc.color);
590
- gl.vertexAttribPointer(loc.color, 4, gl.FLOAT, false, stride, 32);
591
- if (ext) ext.vertexAttribDivisorANGLE(loc.color, 1);
592
- else gl.vertexAttribDivisor(loc.color, 1);
634
+ // 7. Lier les attributs vertex
635
+ if (this._vao) {
636
+ // WebGL2 : le VAO a déjà mémorisé le layout — un seul bind suffit
637
+ gl.bindVertexArray(this._vao);
638
+ } else {
639
+ // WebGL1 : rebinder les attributs manuellement à chaque draw call
640
+ this._setupAttribPointers();
641
+ }
593
642
 
594
643
  // 8. LE draw call unique — dessine N instances du quad unitaire
595
644
  if (ext) ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, this._instanceCount);
596
645
  else gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this._instanceCount);
597
646
 
647
+ // Libérer le VAO après usage
648
+ if (this._vao) gl.bindVertexArray(null);
649
+
598
650
  // 9. Copier le résultat WebGL sur le canvas 2D principal
599
651
  this.ctx.drawImage(this._glCanvas, 0, 0);
600
652
 
@@ -632,18 +684,14 @@ class WebGLCanvasAdapter {
632
684
 
633
685
  /**
634
686
  * Parse une couleur CSS → [r, g, b, a] normalisés [0..1]
635
- * Supporte : #rgb #rrggbb rgba() rgb() et les couleurs nommées communes
687
+ * Supporte : #rgb #rrggbb #rrggbbaa rgba() rgb() et les couleurs nommées communes.
688
+ * Le cache (_colorCache) est initialisé dans le constructeur.
636
689
  */
637
690
  _parseColor(color) {
638
691
  if (!color) return [0, 0, 0, 1];
639
692
 
640
- // Cache
641
- if (this._colorCache) {
642
- const cached = this._colorCache.get(color);
643
- if (cached) return cached;
644
- } else {
645
- this._colorCache = new Map();
646
- }
693
+ const cached = this._colorCache.get(color);
694
+ if (cached) return cached;
647
695
 
648
696
  let r = 0, g = 0, b = 0, a = 1;
649
697
 
@@ -769,6 +817,7 @@ class WebGLCanvasAdapter {
769
817
  gl.deleteTexture(this._atlasTex);
770
818
  gl.deleteBuffer(this._quadBuf);
771
819
  gl.deleteBuffer(this._instanceBuf);
820
+ if (this._vao) gl.deleteVertexArray(this._vao);
772
821
  gl.deleteProgram(this._program);
773
822
  }
774
823
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"