mapspinner 0.1.34 → 0.1.35

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/gl-render.js +38 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapspinner",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "WebGL2 Earth-scale terrain rendering SDK for interactive globe applications",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/gl-render.js CHANGED
@@ -569,6 +569,13 @@ export async function initMapspinnerRender(gl, opts = {}) {
569
569
  const vbo=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,vbo); gl.bufferData(gl.ARRAY_BUFFER,verts,gl.STATIC_DRAW);
570
570
  const ibo=gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,ibo); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);
571
571
  const instBuf=gl.createBuffer(); // per-instance [ox,oy,l,level,face] (filled per frame in render())
572
+ // DATA-CONTINUITY CACHE (2026-06-14): terrain + water get their OWN persistent instance buffers so
573
+ // neither clobbers the other (the shared-buffer clobber forced a re-upload every frame and was the
574
+ // root of the prior 'water drawn as terrain' regression). On a STATIC frame (same quads array object)
575
+ // the instance data is identical -> skip the Float32Array build + bufferData + water dedup Set-loop
576
+ // and just rebind+draw. Pure CPU/GC win (GPU is vertex-bound, the upload is off the critical path).
577
+ const instBufWater=gl.createBuffer();
578
+ let _instQuadsRef=null, _instWaterRef=null, _instWaterN=0;
572
579
 
573
580
  // per-face local->world (cube face -> sphere local frame). Column-major mat3 packed
574
581
  // into a Float32Array(9). Matches localToWorld3 convention:
@@ -892,14 +899,19 @@ export async function initMapspinnerRender(gl, opts = {}) {
892
899
  // h = composeHeight(dir) for each vertex.
893
900
  const FLOATS = 5; // [ox,oy,l,level,face]
894
901
  if (n > 0) {
895
- const inst = new Float32Array(n * FLOATS);
896
- for (let i = 0; i < n; i++) {
897
- const q = quads[i].quad;
898
- inst[i*FLOATS+0] = q.ox; inst[i*FLOATS+1] = q.oy; inst[i*FLOATS+2] = q.l; inst[i*FLOATS+3] = q.level;
899
- inst[i*FLOATS+4] = quads[i].face;
900
- }
902
+ // STATIC-FRAME SKIP: only rebuild + re-upload the instance data when the quad SET changed (the
903
+ // orchestrator passes the SAME quads array object on a static camera, a fresh array on rebuild).
904
+ const _dirty = (quads !== _instQuadsRef);
901
905
  gl.bindBuffer(gl.ARRAY_BUFFER, instBuf);
902
- gl.bufferData(gl.ARRAY_BUFFER, inst, gl.DYNAMIC_DRAW);
906
+ if (_dirty) {
907
+ const inst = new Float32Array(n * FLOATS);
908
+ for (let i = 0; i < n; i++) {
909
+ const q = quads[i].quad;
910
+ inst[i*FLOATS+0] = q.ox; inst[i*FLOATS+1] = q.oy; inst[i*FLOATS+2] = q.l; inst[i*FLOATS+3] = q.level;
911
+ inst[i*FLOATS+4] = quads[i].face;
912
+ }
913
+ gl.bufferData(gl.ARRAY_BUFFER, inst, gl.DYNAMIC_DRAW);
914
+ }
903
915
  const STRIDE = FLOATS * 4;
904
916
  gl.enableVertexAttribArray(1); gl.vertexAttribPointer(1, 4, gl.FLOAT, false, STRIDE, 0); gl.vertexAttribDivisor(1, 1); // iOffset
905
917
  gl.enableVertexAttribArray(2); gl.vertexAttribPointer(2, 1, gl.FLOAT, false, STRIDE, 4 * 4); gl.vertexAttribDivisor(2, 1); // iFace
@@ -926,17 +938,24 @@ export async function initMapspinnerRender(gl, opts = {}) {
926
938
  // (~1.6km) sag ~5cm, far under any visible bathymetry, still ~16-64x fewer water verts
927
939
  // than the deep terrain leaves.
928
940
  const WCAP = 9;
929
- const seen = new Set(); const wl = [];
930
- for (let i = 0; i < n; i++) {
931
- const q = quads[i].quad; let ox = q.ox, oy = q.oy, l = q.l, lv = q.level;
932
- if (lv > WCAP) { const A = l * Math.pow(2, lv - WCAP); ox = Math.floor(ox / A) * A; oy = Math.floor(oy / A) * A; l = A; lv = WCAP; }
933
- const key = quads[i].face + ':' + ox + ':' + oy + ':' + l;
934
- if (seen.has(key)) continue; seen.add(key);
935
- wl.push(ox, oy, l, lv, quads[i].face);
941
+ // OWN persistent buffer (instBufWater) + static-frame skip: on an unchanged quad set, reuse the
942
+ // cached water instances (skip the dedup Set-loop + Float32Array + bufferData). Separate buffer
943
+ // means the terrain pass never clobbers it (the prior water-as-terrain regression root).
944
+ gl.bindBuffer(gl.ARRAY_BUFFER, instBufWater);
945
+ if (_dirty || quads !== _instWaterRef) {
946
+ const seen = new Set(); const wl = [];
947
+ for (let i = 0; i < n; i++) {
948
+ const q = quads[i].quad; let ox = q.ox, oy = q.oy, l = q.l, lv = q.level;
949
+ if (lv > WCAP) { const A = l * Math.pow(2, lv - WCAP); ox = Math.floor(ox / A) * A; oy = Math.floor(oy / A) * A; l = A; lv = WCAP; }
950
+ const key = quads[i].face + ':' + ox + ':' + oy + ':' + l;
951
+ if (seen.has(key)) continue; seen.add(key);
952
+ wl.push(ox, oy, l, lv, quads[i].face);
953
+ }
954
+ _instWaterN = wl.length / FLOATS;
955
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(wl), gl.DYNAMIC_DRAW);
956
+ _instWaterRef = quads;
936
957
  }
937
- const wn = wl.length / FLOATS;
938
- gl.bindBuffer(gl.ARRAY_BUFFER, instBuf);
939
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(wl), gl.DYNAMIC_DRAW);
958
+ const wn = _instWaterN;
940
959
  gl.enableVertexAttribArray(1); gl.vertexAttribPointer(1, 4, gl.FLOAT, false, STRIDE, 0); gl.vertexAttribDivisor(1, 1);
941
960
  gl.enableVertexAttribArray(2); gl.vertexAttribPointer(2, 1, gl.FLOAT, false, STRIDE, 4 * 4); gl.vertexAttribDivisor(2, 1);
942
961
  if (_uw) {
@@ -954,6 +973,8 @@ export async function initMapspinnerRender(gl, opts = {}) {
954
973
  gl.disable(gl.BLEND);
955
974
  if (typeof window !== 'undefined') window.__lastWaterQuads = wn;
956
975
  }
976
+ _instQuadsRef = quads; // mark this quad set uploaded; next frame with the same array skips the rebuild
977
+ if (typeof window !== 'undefined') window.__instUploads = (window.__instUploads | 0) + (_dirty ? 1 : 0);
957
978
  }
958
979
  if (typeof window !== 'undefined') window.__lastDrawCalls = (n > 0) ? 2 : 0;
959
980
  return 0; // glError is checked via checkGlError() once per frame after quadtree (CPU/GPU pipelining)