@plasius/gpu-renderer 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-renderer",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -49,6 +49,7 @@
49
49
  "author": "Plasius LTD <development@plasius.co.uk>",
50
50
  "license": "Apache-2.0",
51
51
  "dependencies": {
52
+ "@plasius/gpu-shared": "^0.1.11",
52
53
  "@plasius/gpu-xr": "^0.1.11"
53
54
  },
54
55
  "devDependencies": {
package/src/index.d.ts CHANGED
@@ -309,6 +309,7 @@ export interface WavefrontEnvironmentPortalRecord {
309
309
  }
310
310
 
311
311
  export interface WavefrontPathTracingComputeConfig {
312
+ readonly mode: typeof rendererWavefrontComputeMode;
312
313
  readonly width: number;
313
314
  readonly height: number;
314
315
  readonly maxDepth: number;
@@ -394,6 +395,7 @@ export interface WavefrontPathTracingMemoryEstimate {
394
395
  readonly environmentPortalBytes: number;
395
396
  readonly configBytes: number;
396
397
  readonly counterBytes: number;
398
+ readonly indirectDispatchBytes: number;
397
399
  readonly totalHotBufferBytes: number;
398
400
  }
399
401
 
@@ -441,32 +443,12 @@ export interface WavefrontPathTracingComputeRenderer {
441
443
  readonly device: GPUDevice;
442
444
  readonly format: GPUTextureFormat | string;
443
445
  readonly config: WavefrontPathTracingComputeConfig;
444
- renderOnce(): Readonly<{
445
- frame: number;
446
- width: number;
447
- height: number;
448
- maxDepth: number;
449
- tiles: number;
450
- tileSize: number;
451
- samplesPerPixel: number;
452
- screenRays: number;
453
- primaryRays: number;
454
- sceneObjectCount: number;
455
- triangleCount: number;
456
- emissiveTriangleCount: number;
457
- environmentPortalCount: number;
458
- environmentPortalMode: 0 | 1 | 2;
459
- bvhNodeCount: number;
460
- displayQuality: boolean;
461
- accelerationBuildMode: WavefrontAccelerationBuildMode;
462
- gpuAccelerationBuildRequired: boolean;
463
- accelerationBuildSubmitted: boolean;
464
- accelerationBuilt: boolean;
465
- accelerationBuildCount: number;
466
- commandSubmissions: number;
467
- frameConfigSlots: number;
468
- memory: WavefrontPathTracingMemoryEstimate;
469
- }>;
446
+ renderOnce(): WavefrontPathTracingComputeFrameStats;
447
+ renderFrame(options?: {
448
+ readStats?: boolean;
449
+ readOutputProbe?: boolean;
450
+ probe?: { x?: number; y?: number };
451
+ }): Promise<WavefrontPathTracingComputeFrameStats>;
470
452
  readOutputProbe(options?: { x?: number; y?: number }): Promise<
471
453
  Readonly<{
472
454
  x: number;
@@ -501,6 +483,50 @@ export interface WavefrontPathTracingComputeRenderer {
501
483
  destroy(): void;
502
484
  }
503
485
 
486
+ export interface WavefrontPathTracingComputeFrameStats {
487
+ readonly frame: number;
488
+ readonly width: number;
489
+ readonly height: number;
490
+ readonly maxDepth: number;
491
+ readonly tiles: number;
492
+ readonly tileSize: number;
493
+ readonly samplesPerPixel: number;
494
+ readonly screenRays: number;
495
+ readonly primaryRays: number;
496
+ readonly sceneObjectCount: number;
497
+ readonly triangleCount: number;
498
+ readonly emissiveTriangleCount: number;
499
+ readonly environmentPortalCount: number;
500
+ readonly environmentPortalMode: 0 | 1 | 2;
501
+ readonly bvhNodeCount: number;
502
+ readonly displayQuality: boolean;
503
+ readonly accelerationBuildMode: WavefrontAccelerationBuildMode;
504
+ readonly gpuAccelerationBuildRequired: boolean;
505
+ readonly accelerationBuildSubmitted: boolean;
506
+ readonly accelerationBuilt: boolean;
507
+ readonly accelerationBuildCount: number;
508
+ readonly commandSubmissions: number;
509
+ readonly frameConfigSlots: number;
510
+ readonly memory: WavefrontPathTracingMemoryEstimate;
511
+ readonly outputProbe?: Readonly<{
512
+ x: number;
513
+ y: number;
514
+ rgba: readonly number[];
515
+ luminance: number;
516
+ sampledPixels: number;
517
+ nonZeroSamples: number;
518
+ maxChannel: number;
519
+ }> | null;
520
+ readonly bounces?: readonly unknown[];
521
+ readonly termination?: Readonly<{
522
+ emissive: number;
523
+ environment: number;
524
+ ambientFallback: number;
525
+ maxDepth: number;
526
+ }>;
527
+ readonly queueOverflow?: number;
528
+ }
529
+
504
530
  export function normalizeWavefrontSceneObject(
505
531
  input?: WavefrontSceneObjectInput,
506
532
  index?: number
@@ -601,6 +627,20 @@ export function supportsWavefrontPathTracingCompute(options?: {
601
627
  export function createWavefrontPathTracingComputeRenderer(
602
628
  options?: CreateWavefrontPathTracingComputeRendererOptions
603
629
  ): Promise<WavefrontPathTracingComputeRenderer>;
630
+ export function renderWavefrontPathTracingComputeFrame(
631
+ options?: CreateWavefrontPathTracingComputeRendererOptions & {
632
+ readStats?: boolean;
633
+ readOutputProbe?: boolean;
634
+ }
635
+ ): Promise<WavefrontPathTracingComputeFrameStats>;
636
+ export function createWavefrontPathTracingComputeShaderSource(options?: {
637
+ workgroupSize?: number;
638
+ outputTextureFormat?: GPUTextureFormat | "rgba8unorm";
639
+ }): string;
640
+
641
+ export const rendererWavefrontComputeMode: "webgpu-compute";
642
+ export const rendererWavefrontComputeWorkgroupSize: 64;
643
+ export const rendererWavefrontComputeStatsStride: 8;
604
644
 
605
645
  export const wavefrontPathTracingComputeLimits: Readonly<{
606
646
  workgroupSize: 64;
@@ -616,6 +656,8 @@ export const wavefrontPathTracingComputeLimits: Readonly<{
616
656
  emissiveTriangleMetadataRecordBytes: 48;
617
657
  environmentPortalRecordBytes: 96;
618
658
  accumulationRecordBytes: 16;
659
+ counterRecordBytes: 32;
660
+ indirectDispatchRecordBytes: 12;
619
661
  }>;
620
662
  export const wavefrontSceneObjectKinds: Readonly<{
621
663
  sphere: 1;
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  createWavefrontMeshAcceleration,
10
10
  createWavefrontPathTracingComputeConfig,
11
11
  createWavefrontPathTracingComputeRenderer,
12
+ createWavefrontPathTracingComputeShaderSource,
12
13
  createWavefrontReferenceRay,
13
14
  estimateWavefrontPathTracingMemory,
14
15
  intersectWavefrontReferenceTriangle,
@@ -17,6 +18,10 @@ export {
17
18
  packWavefrontBvhNodes,
18
19
  packWavefrontSceneObjects,
19
20
  packWavefrontTriangles,
21
+ renderWavefrontPathTracingComputeFrame,
22
+ rendererWavefrontComputeMode,
23
+ rendererWavefrontComputeStatsStride,
24
+ rendererWavefrontComputeWorkgroupSize,
20
25
  supportsWavefrontPathTracingCompute,
21
26
  traceWavefrontReferenceTriangles,
22
27
  wavefrontMaterialKinds,
@@ -6,6 +6,9 @@ const DEFAULT_SAMPLES_PER_PIXEL = 1;
6
6
  const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
7
7
  const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
8
8
  const WORKGROUP_SIZE = 64;
9
+ export const rendererWavefrontComputeMode = "webgpu-compute";
10
+ export const rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
11
+ export const rendererWavefrontComputeStatsStride = 8;
9
12
  const RAY_RECORD_BYTES = 80;
10
13
  const HIT_RECORD_BYTES = 208;
11
14
  const SCENE_OBJECT_RECORD_BYTES = 96;
@@ -18,7 +21,9 @@ const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
18
21
  const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
19
22
  const ACCUMULATION_RECORD_BYTES = 16;
20
23
  const CONFIG_BUFFER_BYTES = 272;
21
- const COUNTER_BUFFER_BYTES = 16;
24
+ const COUNTER_DISPATCH_ARGS_OFFSET = 16;
25
+ const INDIRECT_DISPATCH_ARGS_BYTES = 12;
26
+ const COUNTER_BUFFER_BYTES = 32;
22
27
  const TRACE_STORAGE_BUFFER_BINDINGS = 9;
23
28
  const HIT_TYPE_SURFACE = 0;
24
29
  const HIT_TYPE_EMISSIVE = 1;
@@ -64,6 +69,8 @@ export const wavefrontPathTracingComputeLimits = Object.freeze({
64
69
  emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
65
70
  environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
66
71
  accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
72
+ counterRecordBytes: COUNTER_BUFFER_BYTES,
73
+ indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES,
67
74
  });
68
75
 
69
76
  export const wavefrontSceneObjectKinds = Object.freeze({
@@ -1156,6 +1163,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
1156
1163
  environmentPortalBytes,
1157
1164
  configBytes: CONFIG_BUFFER_BYTES,
1158
1165
  counterBytes: COUNTER_BUFFER_BYTES,
1166
+ indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
1159
1167
  totalHotBufferBytes:
1160
1168
  queueBytes * 2 +
1161
1169
  hitBytes +
@@ -1167,7 +1175,8 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
1167
1175
  emissiveTriangleMetadataBytes +
1168
1176
  environmentPortalBytes +
1169
1177
  CONFIG_BUFFER_BYTES +
1170
- COUNTER_BUFFER_BYTES,
1178
+ COUNTER_BUFFER_BYTES +
1179
+ INDIRECT_DISPATCH_ARGS_BYTES,
1171
1180
  });
1172
1181
  }
1173
1182
 
@@ -1269,6 +1278,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
1269
1278
  );
1270
1279
 
1271
1280
  return Object.freeze({
1281
+ mode: rendererWavefrontComputeMode,
1272
1282
  width,
1273
1283
  height,
1274
1284
  maxDepth,
@@ -1329,6 +1339,9 @@ function getGpuUsageConstants() {
1329
1339
  ) {
1330
1340
  throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
1331
1341
  }
1342
+ if (typeof GPUBufferUsage.INDIRECT !== "number") {
1343
+ throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
1344
+ }
1332
1345
 
1333
1346
  return {
1334
1347
  buffer: GPUBufferUsage,
@@ -2018,6 +2031,10 @@ struct Counters {
2018
2031
  nextCount: atomic<u32>,
2019
2032
  terminatedCount: atomic<u32>,
2020
2033
  hitCount: atomic<u32>,
2034
+ dispatchX: u32,
2035
+ dispatchY: u32,
2036
+ dispatchZ: u32,
2037
+ dispatchPad: u32,
2021
2038
  };
2022
2039
 
2023
2040
  struct Candidate {
@@ -2180,6 +2197,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
2180
2197
  return environment_radiance(origin, direction);
2181
2198
  }
2182
2199
 
2200
+ fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
2201
+ let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
2202
+ let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
2203
+ let normalEnvironment = gated_environment_radiance(
2204
+ hit.position.xyz + normal * 0.003,
2205
+ normal
2206
+ );
2207
+ let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
2208
+ let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
2209
+ return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
2210
+ }
2211
+
2183
2212
  fn default_mesh_range() -> MeshRange {
2184
2213
  return MeshRange(
2185
2214
  0u,
@@ -2751,6 +2780,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
2751
2780
  return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
2752
2781
  }
2753
2782
 
2783
+ fn ray_workgroups_for_count(rayCount: u32) -> u32 {
2784
+ return max(1u, (rayCount + 63u) / 64u);
2785
+ }
2786
+
2787
+ fn write_active_dispatch_args(activeCount: u32) {
2788
+ counters.dispatchX = ray_workgroups_for_count(activeCount);
2789
+ counters.dispatchY = 1u;
2790
+ counters.dispatchZ = 1u;
2791
+ counters.dispatchPad = 0u;
2792
+ }
2793
+
2754
2794
  fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
2755
2795
  return value / (vec3<f32>(1.0) + value);
2756
2796
  }
@@ -2763,6 +2803,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
2763
2803
  atomicStore(&counters.nextCount, 0u);
2764
2804
  atomicStore(&counters.terminatedCount, 0u);
2765
2805
  atomicStore(&counters.hitCount, 0u);
2806
+ write_active_dispatch_args(config.tilePixelCount);
2766
2807
  }
2767
2808
  if (index >= config.tilePixelCount) {
2768
2809
  return;
@@ -3038,9 +3079,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
3038
3079
  }
3039
3080
 
3040
3081
  if (ray.bounce + 1u >= config.maxDepth) {
3082
+ let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
3041
3083
  accumulation[ray.rayId] =
3042
- accumulation[ray.rayId] +
3043
- vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
3084
+ accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
3044
3085
  atomicAdd(&counters.terminatedCount, 1u);
3045
3086
  return;
3046
3087
  }
@@ -3049,6 +3090,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
3049
3090
  let scatter = scatter_direction(ray, hit, seed);
3050
3091
  let nextIndex = atomicAdd(&counters.nextCount, 1u);
3051
3092
  if (nextIndex >= config.tilePixelCount) {
3093
+ let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
3094
+ accumulation[ray.rayId] =
3095
+ accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
3096
+ atomicAdd(&counters.terminatedCount, 1u);
3052
3097
  return;
3053
3098
  }
3054
3099
  let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
@@ -3077,8 +3122,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
3077
3122
  return;
3078
3123
  }
3079
3124
  let nextCount = atomicLoad(&counters.nextCount);
3080
- atomicStore(&counters.activeCount, min(nextCount, config.tilePixelCount));
3125
+ let activeCount = min(nextCount, config.tilePixelCount);
3126
+ atomicStore(&counters.activeCount, activeCount);
3081
3127
  atomicStore(&counters.nextCount, 0u);
3128
+ write_active_dispatch_args(activeCount);
3082
3129
  }
3083
3130
 
3084
3131
  @compute @workgroup_size(64)
@@ -3373,10 +3420,16 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3373
3420
  );
3374
3421
  const counterBuffer = createBuffer(
3375
3422
  device,
3376
- constants.buffer.STORAGE | constants.buffer.COPY_DST,
3423
+ constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
3377
3424
  COUNTER_BUFFER_BYTES,
3378
3425
  "plasius.wavefront.counters"
3379
3426
  );
3427
+ const activeDispatchBuffer = createBuffer(
3428
+ device,
3429
+ constants.buffer.INDIRECT | constants.buffer.COPY_DST,
3430
+ INDIRECT_DISPATCH_ARGS_BYTES,
3431
+ "plasius.wavefront.activeDispatchArgs"
3432
+ );
3380
3433
 
3381
3434
  let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
3382
3435
  device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
@@ -3818,27 +3871,36 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3818
3871
  }
3819
3872
 
3820
3873
  function encodeTileSample(encoder, tile, configOffset) {
3821
- const passEncoder = encoder.beginComputePass({
3822
- label: "plasius.wavefront.computePass",
3874
+ const generatePass = encoder.beginComputePass({
3875
+ label: "plasius.wavefront.generatePrimaryRaysPass",
3823
3876
  });
3824
3877
  const tileWorkgroups = Math.ceil((tile.width * tile.height) / WORKGROUP_SIZE);
3825
- const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
3826
3878
 
3827
- passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
3828
- passEncoder.setPipeline(pipelines.generatePrimaryRays);
3829
- passEncoder.dispatchWorkgroups(tileWorkgroups);
3879
+ generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
3880
+ generatePass.setPipeline(pipelines.generatePrimaryRays);
3881
+ generatePass.dispatchWorkgroups(tileWorkgroups);
3882
+ generatePass.end();
3830
3883
 
3831
3884
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
3885
+ encoder.copyBufferToBuffer(
3886
+ counterBuffer,
3887
+ COUNTER_DISPATCH_ARGS_OFFSET,
3888
+ activeDispatchBuffer,
3889
+ 0,
3890
+ INDIRECT_DISPATCH_ARGS_BYTES
3891
+ );
3892
+ const passEncoder = encoder.beginComputePass({
3893
+ label: `plasius.wavefront.bounce.${bounceIndex}`,
3894
+ });
3832
3895
  passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
3833
3896
  passEncoder.setPipeline(pipelines.intersectActiveQueue);
3834
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3897
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3835
3898
  passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
3836
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3899
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3837
3900
  passEncoder.setPipeline(pipelines.compactAndSwapQueues);
3838
3901
  passEncoder.dispatchWorkgroups(1);
3902
+ passEncoder.end();
3839
3903
  }
3840
-
3841
- passEncoder.end();
3842
3904
  }
3843
3905
 
3844
3906
  function encodeTileOutput(encoder, tile, configOffset) {
@@ -3990,6 +4052,32 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3990
4052
  });
3991
4053
  }
3992
4054
 
4055
+ async function renderFrame(renderOptions = {}) {
4056
+ const frameStats = renderOnce();
4057
+ const probe =
4058
+ renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
4059
+ const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
4060
+ return Object.freeze({
4061
+ ...frameStats,
4062
+ outputProbe: probe
4063
+ ? Object.freeze({
4064
+ ...probe,
4065
+ sampledPixels: 1,
4066
+ nonZeroSamples: maxChannel > 0 ? 1 : 0,
4067
+ maxChannel,
4068
+ })
4069
+ : null,
4070
+ bounces: [],
4071
+ termination: Object.freeze({
4072
+ emissive: 0,
4073
+ environment: 0,
4074
+ ambientFallback: 0,
4075
+ maxDepth: 0,
4076
+ }),
4077
+ queueOverflow: 0,
4078
+ });
4079
+ }
4080
+
3993
4081
  function updateSceneObjects(sceneObjects) {
3994
4082
  const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
3995
4083
  packedScene = nextPackedScene;
@@ -4050,6 +4138,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
4050
4138
  configBuffer.destroy?.();
4051
4139
  bvhBuildConfigBuffer.destroy?.();
4052
4140
  counterBuffer.destroy?.();
4141
+ activeDispatchBuffer.destroy?.();
4053
4142
  radianceTexture.destroy?.();
4054
4143
  denoiseScratchTexture.destroy?.();
4055
4144
  outputTexture.destroy?.();
@@ -4063,9 +4152,31 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
4063
4152
  format,
4064
4153
  config,
4065
4154
  renderOnce,
4155
+ renderFrame,
4066
4156
  readOutputProbe,
4067
4157
  updateSceneObjects,
4068
4158
  getSnapshot,
4069
4159
  destroy,
4070
4160
  });
4071
4161
  }
4162
+
4163
+ export async function renderWavefrontPathTracingComputeFrame(options = {}) {
4164
+ const renderer = await createWavefrontPathTracingComputeRenderer(options);
4165
+ try {
4166
+ return await renderer.renderFrame(options);
4167
+ } finally {
4168
+ renderer.destroy();
4169
+ }
4170
+ }
4171
+
4172
+ export function createWavefrontPathTracingComputeShaderSource(options = {}) {
4173
+ const workgroupSize = readPositiveInteger(
4174
+ "workgroupSize",
4175
+ options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
4176
+ rendererWavefrontComputeWorkgroupSize
4177
+ );
4178
+ if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
4179
+ throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
4180
+ }
4181
+ return WAVEFRONT_COMPUTE_WGSL;
4182
+ }