@flighthq/render-gl 0.5.1-next.1059.c56d3cd → 0.5.1-next.1066.36fe89b

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/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/render-gl`. The `@fl
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/render-gl@0.5.1-next.1059.c56d3cd](https://github.com/flighthq/flight/tree/c56d3cda9aa3602334632ba5110c8ff1279391d5/packages/render-gl)
17
- - [License](https://github.com/flighthq/flight/blob/c56d3cda9aa3602334632ba5110c8ff1279391d5/LICENSE.md)
16
+ - [Source for @flighthq/render-gl@0.5.1-next.1066.36fe89b](https://github.com/flighthq/flight/tree/36fe89ba6c64ce8afd3d789f17c5d9730a46f50f/packages/render-gl)
17
+ - [License](https://github.com/flighthq/flight/blob/36fe89ba6c64ce8afd3d789f17c5d9730a46f50f/LICENSE.md)
@@ -1 +1 @@
1
- {"version":3,"file":"glSkinPaletteTexture.d.ts","sourceRoot":"","sources":["../src/glSkinPaletteTexture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AASpG,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,SAAS,GAAG,oBAAoB,CAI9E;AAID,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAExG;AAKD,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAGjH;AAQD,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,oBAAoB,EAC7B,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,EACrC,UAAU,EAAE,MAAM,EAClB,cAAc,SAAI,GACjB,IAAI,CAoBN"}
1
+ {"version":3,"file":"glSkinPaletteTexture.d.ts","sourceRoot":"","sources":["../src/glSkinPaletteTexture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AASpG,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,SAAS,GAAG,oBAAoB,CAI9E;AAID,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAExG;AAKD,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAGjH;AAQD,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,oBAAoB,EAC7B,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,EACrC,UAAU,EAAE,MAAM,EAClB,cAAc,SAAI,GACjB,IAAI,CAoCN"}
@@ -1,10 +1,10 @@
1
1
  import { allocateEntity, finishEntity } from '@flighthq/entity/contract';
2
2
  // The GPU skinning bone-palette data texture: the per-mesh joint-matrix palette in an RGBA32F texture
3
3
  // the vertex shader reads with texelFetch (GLSL ES 3.0 core), one mat4 packed as four consecutive
4
- // RGBA32F texels. The palette is a single row (height 1), so joint j's column c is at texel x = j*4 + c.
5
- // This replaces the `uniform mat4 u_jointMatrices[MAX_JOINTS]` array: the joint count is bounded by
6
- // MAX_TEXTURE_SIZE (thousands of joints) rather than the vertex-uniform budget, so there is no
7
- // per-context capacity cap and no CPU fallback above capacity.
4
+ // RGBA32F texels. When the total texel count fits within MAX_TEXTURE_SIZE, the palette is a single row
5
+ // (height 1); beyond that it wraps into multiple rows so the joint/instance count is bounded by
6
+ // MAX_TEXTURE_SIZE² rather than MAX_TEXTURE_SIZE. The vertex shader discovers the row width via
7
+ // textureSize() and computes 2D coordinates as ivec2(x % w, x / w).
8
8
  export function createGlSkinPaletteTexture(gl) {
9
9
  const out = allocateEntity();
10
10
  initializeGlSkinPaletteTexture(out, gl);
@@ -33,12 +33,21 @@ export function uploadGlSkinPaletteTexture(gl, palette, jointMatrices, jointCoun
33
33
  // rather than duplicated so both palettes share one upload path and cannot drift in how they grow,
34
34
  // filter or clamp — a normal palette that wrapped or filtered would corrupt lighting exactly as a
35
35
  // pose palette would, and the reasons not to are identical.
36
- const width = jointCount * texelsPerJoint;
36
+ const totalTexels = jointCount * texelsPerJoint;
37
37
  gl.bindTexture(gl.TEXTURE_2D, palette.texture);
38
38
  // Raw float data — premultiply must be off. The image upload path leaves it on (sticky state).
39
39
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
40
40
  if (jointCount > palette.jointCapacity) {
41
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, 1, 0, gl.RGBA, gl.FLOAT, jointMatrices);
41
+ const maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE);
42
+ const width = Math.min(totalTexels, maxWidth);
43
+ const height = Math.ceil(totalTexels / width);
44
+ if (height === 1) {
45
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, 1, 0, gl.RGBA, gl.FLOAT, jointMatrices);
46
+ }
47
+ else {
48
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, null);
49
+ uploadGlPaletteTextureRows(gl, jointMatrices, totalTexels, width, height);
50
+ }
42
51
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
43
52
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
44
53
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
@@ -46,7 +55,24 @@ export function uploadGlSkinPaletteTexture(gl, palette, jointMatrices, jointCoun
46
55
  palette.jointCapacity = jointCount;
47
56
  }
48
57
  else {
49
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, 1, gl.RGBA, gl.FLOAT, jointMatrices);
58
+ const maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE);
59
+ const allocatedTexels = palette.jointCapacity * texelsPerJoint;
60
+ const width = Math.min(allocatedTexels, maxWidth);
61
+ if (totalTexels <= width) {
62
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, totalTexels, 1, gl.RGBA, gl.FLOAT, jointMatrices);
63
+ }
64
+ else {
65
+ const height = Math.ceil(totalTexels / width);
66
+ uploadGlPaletteTextureRows(gl, jointMatrices, totalTexels, width, height);
67
+ }
68
+ }
69
+ }
70
+ function uploadGlPaletteTextureRows(gl, data, totalTexels, width, height) {
71
+ for (let y = 0; y < height; y++) {
72
+ const rowStart = y * width;
73
+ const rowTexels = Math.min(width, totalTexels - rowStart);
74
+ const floatStart = rowStart * 4;
75
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, y, rowTexels, 1, gl.RGBA, gl.FLOAT, data.subarray(floatStart, floatStart + rowTexels * 4));
50
76
  }
51
77
  }
52
78
  //# sourceMappingURL=glSkinPaletteTexture.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glSkinPaletteTexture.js","sourceRoot":"","sources":["../src/glSkinPaletteTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzE,sGAAsG;AACtG,kGAAkG;AAClG,yGAAyG;AACzG,oGAAoG;AACpG,+FAA+F;AAC/F,+DAA+D;AAE/D,MAAM,UAAU,0BAA0B,CAAC,EAAa;IACtD,MAAM,GAAG,GAAG,cAAc,EAAwB,CAAC;IACnD,8BAA8B,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,kGAAkG;AAClG,wFAAwF;AACxF,MAAM,UAAU,2BAA2B,CAAC,EAAa,EAAE,OAAuC;IAChG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,gGAAgG;AAChG,kGAAkG;AAClG,wDAAwD;AACxD,MAAM,UAAU,8BAA8B,CAAC,GAA6C,EAAE,EAAa;IACzG,GAAG,CAAC,aAAa,GAAG,CAAC,CAAC;IACtB,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,aAAa,EAAG,CAAC;AACpC,CAAC;AAED,iGAAiG;AACjG,+FAA+F;AAC/F,mGAAmG;AACnG,gGAAgG;AAChG,mGAAmG;AACnG,2DAA2D;AAC3D,MAAM,UAAU,0BAA0B,CACxC,EAAa,EACb,OAA6B,EAC7B,aAAqC,EACrC,UAAkB,EAClB,cAAc,GAAG,CAAC;IAElB,kGAAkG;IAClG,mGAAmG;IACnG,kGAAkG;IAClG,4DAA4D;IAC5D,MAAM,KAAK,GAAG,UAAU,GAAG,cAAc,CAAC;IAC1C,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,+FAA+F;IAC/F,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;IAEzD,IAAI,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;QACvC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,aAA6B,CAAC,CAAC;QAC3G,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;QACrE,OAAO,CAAC,aAAa,GAAG,UAAU,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,aAA6B,CAAC,CAAC;IACvG,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"glSkinPaletteTexture.js","sourceRoot":"","sources":["../src/glSkinPaletteTexture.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/render-gl",
3
- "version": "0.5.1-next.1059.c56d3cd",
3
+ "version": "0.5.1-next.1066.36fe89b",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,16 +38,16 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/color": "0.5.1-next.1059.c56d3cd",
42
- "@flighthq/entity": "0.5.1-next.1059.c56d3cd",
43
- "@flighthq/log": "0.5.1-next.1059.c56d3cd",
44
- "@flighthq/scene2d": "0.5.1-next.1059.c56d3cd",
45
- "@flighthq/geometry": "0.5.1-next.1059.c56d3cd",
46
- "@flighthq/render": "0.5.1-next.1059.c56d3cd",
47
- "@flighthq/registry": "0.5.1-next.1059.c56d3cd",
48
- "@flighthq/signals": "0.5.1-next.1059.c56d3cd",
49
- "@flighthq/texture": "0.5.1-next.1059.c56d3cd",
50
- "@flighthq/types": "0.5.1-next.1059.c56d3cd"
41
+ "@flighthq/color": "0.5.1-next.1066.36fe89b",
42
+ "@flighthq/entity": "0.5.1-next.1066.36fe89b",
43
+ "@flighthq/log": "0.5.1-next.1066.36fe89b",
44
+ "@flighthq/scene2d": "0.5.1-next.1066.36fe89b",
45
+ "@flighthq/geometry": "0.5.1-next.1066.36fe89b",
46
+ "@flighthq/render": "0.5.1-next.1066.36fe89b",
47
+ "@flighthq/registry": "0.5.1-next.1066.36fe89b",
48
+ "@flighthq/signals": "0.5.1-next.1066.36fe89b",
49
+ "@flighthq/texture": "0.5.1-next.1066.36fe89b",
50
+ "@flighthq/types": "0.5.1-next.1066.36fe89b"
51
51
  },
52
52
  "devDependencies": {
53
53
  "typescript": "^5.3.0"
@@ -12,7 +12,7 @@ interface Call {
12
12
  args: unknown[];
13
13
  }
14
14
 
15
- function makeGl(): { gl: WebGL2RenderingContext; calls: Call[] } {
15
+ function makeGl(maxTextureSize = 4096): { gl: WebGL2RenderingContext; calls: Call[] } {
16
16
  const calls: Call[] = [];
17
17
  const record =
18
18
  (name: string, result?: unknown) =>
@@ -21,6 +21,7 @@ function makeGl(): { gl: WebGL2RenderingContext; calls: Call[] } {
21
21
  return result;
22
22
  };
23
23
  const gl = {
24
+ MAX_TEXTURE_SIZE: 0x0d33,
24
25
  TEXTURE_2D: 0x0de1,
25
26
  RGBA: 0x1908,
26
27
  RGBA32F: 0x8814,
@@ -35,6 +36,11 @@ function makeGl(): { gl: WebGL2RenderingContext; calls: Call[] } {
35
36
  createTexture: record('createTexture', { id: 'tex' }),
36
37
  deleteTexture: record('deleteTexture'),
37
38
  bindTexture: record('bindTexture'),
39
+ getParameter: (pname: number) => {
40
+ calls.push({ name: 'getParameter', args: [pname] });
41
+ if (pname === 0x0d33) return maxTextureSize;
42
+ return 0;
43
+ },
38
44
  texImage2D: record('texImage2D'),
39
45
  texSubImage2D: record('texSubImage2D'),
40
46
  pixelStorei: record('pixelStorei'),
@@ -152,4 +158,42 @@ describe('uploadGlSkinPaletteTexture', () => {
152
158
  uploadGlSkinPaletteTexture(gl, palette, makePalette(6), 6);
153
159
  expect(palette.jointCapacity).toBe(6);
154
160
  });
161
+
162
+ it('wraps into multiple rows when totalTexels exceeds MAX_TEXTURE_SIZE', () => {
163
+ const { gl, calls } = makeGl(16);
164
+ const palette = createGlSkinPaletteTexture(gl);
165
+ // 5 joints × 4 texels = 20 texels, max width 16 → 16 × 2 texture
166
+ uploadGlSkinPaletteTexture(gl, palette, makePalette(5), 5);
167
+
168
+ const alloc = calls.find((c) => c.name === 'texImage2D');
169
+ expect(alloc).toBeDefined();
170
+ expect(alloc?.args[3]).toBe(16); // width = MAX_TEXTURE_SIZE
171
+ expect(alloc?.args[4]).toBe(2); // height = ceil(20 / 16)
172
+ expect(alloc?.args[8]).toBeNull(); // allocated with null, then filled row by row
173
+ expect(palette.jointCapacity).toBe(5);
174
+
175
+ const subs = calls.filter((c) => c.name === 'texSubImage2D');
176
+ expect(subs.length).toBe(2);
177
+ expect(subs[0].args[3]).toBe(0); // row 0 yoffset
178
+ expect(subs[0].args[4]).toBe(16); // row 0 width = full row
179
+ expect(subs[1].args[3]).toBe(1); // row 1 yoffset
180
+ expect(subs[1].args[4]).toBe(4); // row 1 width = 20 - 16 remaining texels
181
+ });
182
+
183
+ it('uploads multi-row sub-image when data fits existing multi-row capacity', () => {
184
+ const { gl, calls } = makeGl(16);
185
+ const palette = createGlSkinPaletteTexture(gl);
186
+ // Allocate for 6 joints (24 texels → 16 × 2)
187
+ uploadGlSkinPaletteTexture(gl, palette, makePalette(6), 6);
188
+ calls.length = 0;
189
+
190
+ // Upload 5 joints (20 texels) into the existing 16 × 2 texture
191
+ uploadGlSkinPaletteTexture(gl, palette, makePalette(5), 5);
192
+
193
+ expect(calls.some((c) => c.name === 'texImage2D')).toBe(false);
194
+ const subs = calls.filter((c) => c.name === 'texSubImage2D');
195
+ expect(subs.length).toBe(2);
196
+ expect(subs[0].args[4]).toBe(16); // row 0 full width
197
+ expect(subs[1].args[4]).toBe(4); // row 1 partial (20 - 16)
198
+ });
155
199
  });