@tinypoly/gpu-renderer 0.2.5 → 0.3.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.
Files changed (101) hide show
  1. package/ARCHITECTURE.md +48 -10
  2. package/README.md +2 -0
  3. package/dist/accumulator.d.ts.map +1 -1
  4. package/dist/accumulator.js +1 -0
  5. package/dist/accumulator.js.map +1 -1
  6. package/dist/denoise.js +2 -2
  7. package/dist/denoise.js.map +1 -1
  8. package/dist/device.d.ts.map +1 -1
  9. package/dist/device.js +9 -0
  10. package/dist/device.js.map +1 -1
  11. package/dist/frameLayout.d.ts.map +1 -1
  12. package/dist/frameLayout.js +5 -0
  13. package/dist/frameLayout.js.map +1 -1
  14. package/dist/geometry.d.ts +48 -8
  15. package/dist/geometry.d.ts.map +1 -1
  16. package/dist/geometry.js +409 -134
  17. package/dist/geometry.js.map +1 -1
  18. package/dist/grass.d.ts.map +1 -1
  19. package/dist/grass.js +2 -1
  20. package/dist/grass.js.map +1 -1
  21. package/dist/imageResolve.js +2 -2
  22. package/dist/imageResolve.js.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/instancing.d.ts +100 -0
  28. package/dist/instancing.d.ts.map +1 -0
  29. package/dist/instancing.js +286 -0
  30. package/dist/instancing.js.map +1 -0
  31. package/dist/lights.d.ts.map +1 -1
  32. package/dist/lights.js +8 -5
  33. package/dist/lights.js.map +1 -1
  34. package/dist/memoryProbe.d.ts +76 -0
  35. package/dist/memoryProbe.d.ts.map +1 -0
  36. package/dist/memoryProbe.js +197 -0
  37. package/dist/memoryProbe.js.map +1 -0
  38. package/dist/oidnPass.d.ts +6 -0
  39. package/dist/oidnPass.d.ts.map +1 -1
  40. package/dist/oidnPass.js +18 -1
  41. package/dist/oidnPass.js.map +1 -1
  42. package/dist/pathTracePass.d.ts +15 -2
  43. package/dist/pathTracePass.d.ts.map +1 -1
  44. package/dist/pathTracePass.js +38 -16
  45. package/dist/pathTracePass.js.map +1 -1
  46. package/dist/renderer.d.ts +13 -0
  47. package/dist/renderer.d.ts.map +1 -1
  48. package/dist/renderer.js +248 -41
  49. package/dist/renderer.js.map +1 -1
  50. package/dist/sceneUpload.d.ts +26 -8
  51. package/dist/sceneUpload.d.ts.map +1 -1
  52. package/dist/sceneUpload.js +97 -27
  53. package/dist/sceneUpload.js.map +1 -1
  54. package/dist/shaderModule.d.ts.map +1 -1
  55. package/dist/shaderModule.js +2 -1
  56. package/dist/shaderModule.js.map +1 -1
  57. package/dist/skyBake.d.ts.map +1 -1
  58. package/dist/skyBake.js +5 -3
  59. package/dist/skyBake.js.map +1 -1
  60. package/dist/texturePack.d.ts +43 -3
  61. package/dist/texturePack.d.ts.map +1 -1
  62. package/dist/texturePack.js +209 -37
  63. package/dist/texturePack.js.map +1 -1
  64. package/dist/wgsl/bvh.wgsl.d.ts +5 -1
  65. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -1
  66. package/dist/wgsl/bvh.wgsl.js +105 -15
  67. package/dist/wgsl/bvh.wgsl.js.map +1 -1
  68. package/dist/wgsl/common.wgsl.d.ts.map +1 -1
  69. package/dist/wgsl/common.wgsl.js +92 -11
  70. package/dist/wgsl/common.wgsl.js.map +1 -1
  71. package/dist/wgsl/surface.wgsl.d.ts +1 -1
  72. package/dist/wgsl/surface.wgsl.d.ts.map +1 -1
  73. package/dist/wgsl/surface.wgsl.js +24 -14
  74. package/dist/wgsl/surface.wgsl.js.map +1 -1
  75. package/dist/wgsl/texture.wgsl.d.ts +1 -1
  76. package/dist/wgsl/texture.wgsl.d.ts.map +1 -1
  77. package/dist/wgsl/texture.wgsl.js +11 -0
  78. package/dist/wgsl/texture.wgsl.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/accumulator.ts +1 -0
  81. package/src/denoise.ts +2 -2
  82. package/src/device.ts +12 -0
  83. package/src/frameLayout.ts +5 -0
  84. package/src/geometry.ts +527 -133
  85. package/src/grass.ts +2 -1
  86. package/src/imageResolve.ts +2 -2
  87. package/src/index.ts +1 -0
  88. package/src/instancing.ts +415 -0
  89. package/src/lights.ts +9 -5
  90. package/src/memoryProbe.ts +275 -0
  91. package/src/oidnPass.ts +21 -1
  92. package/src/pathTracePass.ts +42 -18
  93. package/src/renderer.ts +308 -47
  94. package/src/sceneUpload.ts +139 -36
  95. package/src/shaderModule.ts +2 -1
  96. package/src/skyBake.ts +5 -3
  97. package/src/texturePack.ts +274 -43
  98. package/src/wgsl/bvh.wgsl.ts +105 -15
  99. package/src/wgsl/common.wgsl.ts +92 -11
  100. package/src/wgsl/surface.wgsl.ts +24 -14
  101. package/src/wgsl/texture.wgsl.ts +11 -0
@@ -7,5 +7,5 @@
7
7
  * renderer already draws many jittered samples through every pixel, which averages that detail properly instead of
8
8
  * approximating it. Levels would buy memory locality, not correctness, so they can wait.
9
9
  */
10
- export declare const TEXTURE_WGSL = "\nconst TEXTURE_FLOAT: u32 = 1u;\nconst TEXTURE_SRGB: u32 = 2u;\nconst TEXTURE_NEAREST: u32 = 4u;\nconst TEXTURE_FLIP_Y: u32 = 8u;\n\n// Three's own wrap constants. Anything else clamps, as it does in the CPU renderer.\nconst WRAP_REPEAT: u32 = 1000u;\nconst WRAP_MIRROR: u32 = 1002u;\n\n/** Brings a texel index inside the texture the way the wrap mode says to. */\nfn wrapIndex(index: i32, size: i32, mode: u32) -> i32 {\n if (mode == WRAP_REPEAT) {\n return ((index % size) + size) % size;\n }\n\n if (mode == WRAP_MIRROR) {\n let period = size * 2;\n let folded = ((index % period) + period) % period;\n\n return select(period - 1 - folded, folded, folded < size);\n }\n\n return clamp(index, 0, size - 1);\n}\n\n/** One texel, in linear light. Alpha is never encoded, as in Three. */\nfn readTexel(info: TextureInfo, x: i32, y: i32) -> vec4<f32> {\n let index = u32(y) * info.size.x + u32(x);\n\n if ((info.flags & TEXTURE_FLOAT) != 0u) {\n let at = info.offset + index * 4u;\n\n return vec4<f32>(\n bitcast<f32>(textures[at]),\n bitcast<f32>(textures[at + 1u]),\n bitcast<f32>(textures[at + 2u]),\n bitcast<f32>(textures[at + 3u]));\n }\n\n let word = textures[info.offset + index];\n\n // The decode is a table of the 256 codes rather than a power per channel and per texel: the same values, read.\n if ((info.flags & TEXTURE_SRGB) != 0u) {\n return vec4<f32>(\n bitcast<f32>(textures[SRGB_TABLE + (word & 0xffu)]),\n bitcast<f32>(textures[SRGB_TABLE + ((word >> 8u) & 0xffu)]),\n bitcast<f32>(textures[SRGB_TABLE + ((word >> 16u) & 0xffu)]),\n f32((word >> 24u) & 0xffu) / 255.0);\n }\n\n return vec4<f32>(\n f32(word & 0xffu),\n f32((word >> 8u) & 0xffu),\n f32((word >> 16u) & 0xffu),\n f32((word >> 24u) & 0xffu)) / 255.0;\n}\n\n/** One texture of the table, sampled with its own transform, wrap modes and filter. */\nfn sampleTextureAt(index: u32, uv: vec2<f32>) -> vec4<f32> {\n let info = textureInfo(index);\n // The affine transform Three keeps on the texture, one row at a time.\n let transformed = vec2<f32>(\n dot(info.rowU.xyz, vec3<f32>(uv, 1.0)),\n dot(info.rowV.xyz, vec3<f32>(uv, 1.0)));\n\n return sampleTextureInfo(info, transformed);\n}\n\n/**\n * A texture read the way GLSL's texture2D reads it: at the coordinates given, with no transform. Three applies a\n * texture's matrix in its own materials' shaders, so a shader of the scene's, an environment lookup and a light's\n * projected map do not get it.\n */\nfn sampleTextureRaw(index: u32, uv: vec2<f32>) -> vec4<f32> {\n return sampleTextureInfo(textureInfo(index), uv);\n}\n\n/** Three's equirectUv: longitude across, and latitude up from the bottom of the image, as GL reads it. */\nfn equirectUv(direction: vec3<f32>) -> vec2<f32> {\n let d = normalize(direction);\n\n return vec2<f32>(atan2(d.z, d.x) / (2.0 * PI) + 0.5, asin(clamp(d.y, -1.0, 1.0)) / PI + 0.5);\n}\n\n/** An environment map of the scene's: an equirectangular image, or a cube map the snapshot already unwrapped into one. */\nfn sampleEquirect(index: u32, direction: vec3<f32>) -> vec4<f32> {\n return sampleTextureRaw(index, equirectUv(direction));\n}\n\nfn sampleTextureInfo(info: TextureInfo, transformed: vec2<f32>) -> vec4<f32> {\n if (!all(transformed == transformed) || info.size.x == 0u || info.size.y == 0u) {\n return vec4<f32>(0.0);\n }\n\n let flipped = select(transformed.y, 1.0 - transformed.y, (info.flags & TEXTURE_FLIP_Y) != 0u);\n let size = vec2<i32>(info.size);\n // The half texel offset that puts a sample in the middle of a texel, as on a graphics pipeline.\n let centred = vec2<f32>(transformed.x, flipped) * vec2<f32>(info.size) - 0.5;\n\n if ((info.flags & TEXTURE_NEAREST) != 0u) {\n let at = vec2<i32>(floor(centred + 0.5));\n\n return readTexel(info, wrapIndex(at.x, size.x, info.wrapS), wrapIndex(at.y, size.y, info.wrapT));\n }\n\n let low = floor(centred);\n let fraction = centred - low;\n let base = vec2<i32>(low);\n let x0 = wrapIndex(base.x, size.x, info.wrapS);\n let x1 = wrapIndex(base.x + 1, size.x, info.wrapS);\n let y0 = wrapIndex(base.y, size.y, info.wrapT);\n let y1 = wrapIndex(base.y + 1, size.y, info.wrapT);\n\n // The four corners are blended in linear light, which is why the decode happens per texel above.\n return mix(\n mix(readTexel(info, x0, y0), readTexel(info, x1, y0), fraction.x),\n mix(readTexel(info, x0, y1), readTexel(info, x1, y1), fraction.x),\n fraction.y);\n}\n";
10
+ export declare const TEXTURE_WGSL = "\nconst TEXTURE_FLOAT: u32 = 1u;\nconst TEXTURE_SRGB: u32 = 2u;\nconst TEXTURE_NEAREST: u32 = 4u;\nconst TEXTURE_FLIP_Y: u32 = 8u;\nconst TEXTURE_SINGLE: u32 = 32u;\n\n// Three's own wrap constants. Anything else clamps, as it does in the CPU renderer.\nconst WRAP_REPEAT: u32 = 1000u;\nconst WRAP_MIRROR: u32 = 1002u;\n\n/** Brings a texel index inside the texture the way the wrap mode says to. */\nfn wrapIndex(index: i32, size: i32, mode: u32) -> i32 {\n if (mode == WRAP_REPEAT) {\n return ((index % size) + size) % size;\n }\n\n if (mode == WRAP_MIRROR) {\n let period = size * 2;\n let folded = ((index % period) + period) % period;\n\n return select(period - 1 - folded, folded, folded < size);\n }\n\n return clamp(index, 0, size - 1);\n}\n\n/** One texel, in linear light. Alpha is never encoded, as in Three. */\nfn readTexel(info: TextureInfo, x: i32, y: i32) -> vec4<f32> {\n let index = u32(y) * info.size.x + u32(x);\n\n if ((info.flags & TEXTURE_FLOAT) != 0u) {\n let at = info.offset + index * 4u;\n\n return vec4<f32>(\n bitcast<f32>(textures[at]),\n bitcast<f32>(textures[at + 1u]),\n bitcast<f32>(textures[at + 2u]),\n bitcast<f32>(textures[at + 3u]));\n }\n\n // The only channel the scene reads, four texels to a word, handed back in every channel: alpha as it is, the colour\n // decoded where the texture is sRGB, as a full texel's would be.\n if ((info.flags & TEXTURE_SINGLE) != 0u) {\n let code = (textures[info.offset + (index >> 2u)] >> ((index & 3u) * 8u)) & 0xffu;\n let raw = f32(code) / 255.0;\n let colour = select(raw, bitcast<f32>(textures[SRGB_TABLE + code]), (info.flags & TEXTURE_SRGB) != 0u);\n\n return vec4<f32>(colour, colour, colour, raw);\n }\n\n let word = textures[info.offset + index];\n\n // The decode is a table of the 256 codes rather than a power per channel and per texel: the same values, read.\n if ((info.flags & TEXTURE_SRGB) != 0u) {\n return vec4<f32>(\n bitcast<f32>(textures[SRGB_TABLE + (word & 0xffu)]),\n bitcast<f32>(textures[SRGB_TABLE + ((word >> 8u) & 0xffu)]),\n bitcast<f32>(textures[SRGB_TABLE + ((word >> 16u) & 0xffu)]),\n f32((word >> 24u) & 0xffu) / 255.0);\n }\n\n return vec4<f32>(\n f32(word & 0xffu),\n f32((word >> 8u) & 0xffu),\n f32((word >> 16u) & 0xffu),\n f32((word >> 24u) & 0xffu)) / 255.0;\n}\n\n/** One texture of the table, sampled with its own transform, wrap modes and filter. */\nfn sampleTextureAt(index: u32, uv: vec2<f32>) -> vec4<f32> {\n let info = textureInfo(index);\n // The affine transform Three keeps on the texture, one row at a time.\n let transformed = vec2<f32>(\n dot(info.rowU.xyz, vec3<f32>(uv, 1.0)),\n dot(info.rowV.xyz, vec3<f32>(uv, 1.0)));\n\n return sampleTextureInfo(info, transformed);\n}\n\n/**\n * A texture read the way GLSL's texture2D reads it: at the coordinates given, with no transform. Three applies a\n * texture's matrix in its own materials' shaders, so a shader of the scene's, an environment lookup and a light's\n * projected map do not get it.\n */\nfn sampleTextureRaw(index: u32, uv: vec2<f32>) -> vec4<f32> {\n return sampleTextureInfo(textureInfo(index), uv);\n}\n\n/** Three's equirectUv: longitude across, and latitude up from the bottom of the image, as GL reads it. */\nfn equirectUv(direction: vec3<f32>) -> vec2<f32> {\n let d = normalize(direction);\n\n return vec2<f32>(atan2(d.z, d.x) / (2.0 * PI) + 0.5, asin(clamp(d.y, -1.0, 1.0)) / PI + 0.5);\n}\n\n/** An environment map of the scene's: an equirectangular image, or a cube map the snapshot already unwrapped into one. */\nfn sampleEquirect(index: u32, direction: vec3<f32>) -> vec4<f32> {\n return sampleTextureRaw(index, equirectUv(direction));\n}\n\nfn sampleTextureInfo(info: TextureInfo, transformed: vec2<f32>) -> vec4<f32> {\n if (!all(transformed == transformed) || info.size.x == 0u || info.size.y == 0u) {\n return vec4<f32>(0.0);\n }\n\n let flipped = select(transformed.y, 1.0 - transformed.y, (info.flags & TEXTURE_FLIP_Y) != 0u);\n let size = vec2<i32>(info.size);\n // The half texel offset that puts a sample in the middle of a texel, as on a graphics pipeline.\n let centred = vec2<f32>(transformed.x, flipped) * vec2<f32>(info.size) - 0.5;\n\n if ((info.flags & TEXTURE_NEAREST) != 0u) {\n let at = vec2<i32>(floor(centred + 0.5));\n\n return readTexel(info, wrapIndex(at.x, size.x, info.wrapS), wrapIndex(at.y, size.y, info.wrapT));\n }\n\n let low = floor(centred);\n let fraction = centred - low;\n let base = vec2<i32>(low);\n let x0 = wrapIndex(base.x, size.x, info.wrapS);\n let x1 = wrapIndex(base.x + 1, size.x, info.wrapS);\n let y0 = wrapIndex(base.y, size.y, info.wrapT);\n let y1 = wrapIndex(base.y + 1, size.y, info.wrapT);\n\n // The four corners are blended in linear light, which is why the decode happens per texel above.\n return mix(\n mix(readTexel(info, x0, y0), readTexel(info, x1, y0), fraction.x),\n mix(readTexel(info, x0, y1), readTexel(info, x1, y1), fraction.x),\n fraction.y);\n}\n";
11
11
  //# sourceMappingURL=texture.wgsl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"texture.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/texture.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,66IAwHxB,CAAC"}
1
+ {"version":3,"file":"texture.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/texture.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,89JAmIxB,CAAC"}
@@ -12,6 +12,7 @@ const TEXTURE_FLOAT: u32 = 1u;
12
12
  const TEXTURE_SRGB: u32 = 2u;
13
13
  const TEXTURE_NEAREST: u32 = 4u;
14
14
  const TEXTURE_FLIP_Y: u32 = 8u;
15
+ const TEXTURE_SINGLE: u32 = 32u;
15
16
 
16
17
  // Three's own wrap constants. Anything else clamps, as it does in the CPU renderer.
17
18
  const WRAP_REPEAT: u32 = 1000u;
@@ -47,6 +48,16 @@ fn readTexel(info: TextureInfo, x: i32, y: i32) -> vec4<f32> {
47
48
  bitcast<f32>(textures[at + 3u]));
48
49
  }
49
50
 
51
+ // The only channel the scene reads, four texels to a word, handed back in every channel: alpha as it is, the colour
52
+ // decoded where the texture is sRGB, as a full texel's would be.
53
+ if ((info.flags & TEXTURE_SINGLE) != 0u) {
54
+ let code = (textures[info.offset + (index >> 2u)] >> ((index & 3u) * 8u)) & 0xffu;
55
+ let raw = f32(code) / 255.0;
56
+ let colour = select(raw, bitcast<f32>(textures[SRGB_TABLE + code]), (info.flags & TEXTURE_SRGB) != 0u);
57
+
58
+ return vec4<f32>(colour, colour, colour, raw);
59
+ }
60
+
50
61
  let word = textures[info.offset + index];
51
62
 
52
63
  // The decode is a table of the 256 codes rather than a power per channel and per texel: the same values, read.
@@ -1 +1 @@
1
- {"version":3,"file":"texture.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/texture.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHtC,CAAC"}
1
+ {"version":3,"file":"texture.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/texture.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmItC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinypoly/gpu-renderer",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "WebGPU path tracer for Three.js scenes: BVH traversal, progressive accumulation and tone mapping in WGSL compute shaders.",
5
5
  "keywords": [
6
6
  "three",
@@ -56,6 +56,7 @@ export class Accumulator {
56
56
 
57
57
  private create(): GPUBuffer {
58
58
  return this.device.createBuffer({
59
+ label: "accumulation",
59
60
  size: this.byteLength,
60
61
  // clearBuffer is a copy into the buffer: without COPY_DST it fails validation and clears nothing.
61
62
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
package/src/denoise.ts CHANGED
@@ -503,9 +503,9 @@ export class DenoisePass {
503
503
  const size = Math.max(16, width * height * 16);
504
504
 
505
505
  this.buffers = Array.from({ length: 6 }, () =>
506
- this.device.createBuffer({ size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }));
506
+ this.device.createBuffer({ label: "svgf", size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }));
507
507
  this.buffers.push(
508
- this.device.createBuffer({ size: Math.max(16, width * height * 4), usage: GPUBufferUsage.STORAGE }));
508
+ this.device.createBuffer({ label: "svgf", size: Math.max(16, width * height * 4), usage: GPUBufferUsage.STORAGE }));
509
509
  this.output = this.buffers[4];
510
510
  this.groups.clear();
511
511
  this.width = width;
package/src/device.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** Acquiring a device, and the questions a host asks before choosing this renderer at all. */
2
2
 
3
+ import { logMemory, toMB, trackDevice } from "./memoryProbe.js";
4
+
3
5
  /** What the renderer needs from the platform, and what is missing when it cannot run. */
4
6
  export interface WebGpuSupport {
5
7
  /** True when an adapter answered. */
@@ -79,8 +81,18 @@ export async function requestRenderDevice(options: RenderDeviceOptions = {}): Pr
79
81
  },
80
82
  });
81
83
 
84
+ trackDevice(device);
82
85
  void device.lost.then(info => options.onLost?.(info.message || String(info.reason)));
83
86
 
87
+ const info = (adapter as GPUAdapter & { info?: GPUAdapterInfo }).info;
88
+
89
+ logMemory("device acquired", {
90
+ adapter: info ? `${info.vendor} ${info.architecture} ${info.device} ${info.description}`.trim() : "unknown",
91
+ maxBufferMB: toMB(device.limits.maxBufferSize),
92
+ maxStorageBindingMB: toMB(device.limits.maxStorageBufferBindingSize),
93
+ shaderF16: requiredFeatures.length > 0,
94
+ });
95
+
84
96
  return {
85
97
  adapter,
86
98
  device,
@@ -49,6 +49,11 @@ export const FRAME_LAYOUT = layoutStruct("Frame", [
49
49
  { name: "colorOffset", type: "u32", doc: "Where vertex colours start inside the attribute buffer." },
50
50
  { name: "hasColors", type: "u32" },
51
51
  { name: "varyingOffset", type: "u32", doc: "Where shader varyings start inside the attribute buffer." },
52
+ { name: "sceneTriangles", type: "u32", doc: "Triangles of the scene's own; the instances' are numbered after them." },
53
+ { name: "instanceOffset", type: "u32", doc: "Where the instance of every instanced triangle starts inside the index buffer." },
54
+ { name: "cornerOffset", type: "u32", doc: "Where the three vertices of every stored triangle start inside the index buffer." },
55
+ { name: "normalOffset", type: "u32", doc: "Where the vertices' folded normals start inside the index buffer." },
56
+ { name: "instanceData", type: "u32", doc: "Where the instances' transforms start in the texture buffer." },
52
57
  { name: "environmentTexture", type: "u32", doc: "The surrounding image, or NO_TEXTURE for the gradient." },
53
58
  { name: "environmentCdf", type: "u32" },
54
59
  { name: "environmentCdfSize", type: "vec2u" },