@plasius/gpu-renderer 0.2.40 → 0.2.41

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.
@@ -0,0 +1,411 @@
1
+ import { GPU_READBACK_COMPLETION_TIMEOUT_MS } from "./wavefront-core.js";
2
+ import { createBuffer } from "./wavefront-gpu-resources.js";
3
+
4
+ const TIMESTAMP_QUERY_COUNT = 2;
5
+ const TIMESTAMP_BUFFER_BYTES = TIMESTAMP_QUERY_COUNT * BigUint64Array.BYTES_PER_ELEMENT;
6
+
7
+ function hasGpuFeature(features, name) {
8
+ if (typeof features?.has === "function") {
9
+ return features.has(name);
10
+ }
11
+ if (features && typeof features[Symbol.iterator] === "function") {
12
+ return Array.from(features).includes(name);
13
+ }
14
+ return false;
15
+ }
16
+
17
+ function freezeRayCountTelemetry(values = {}) {
18
+ return Object.freeze({
19
+ status: values.status ?? "not-requested",
20
+ source: values.source ?? null,
21
+ expectedPrimaryRays: values.expectedPrimaryRays ?? null,
22
+ observedPrimaryRays: values.observedPrimaryRays ?? null,
23
+ secondaryRays: values.secondaryRays ?? null,
24
+ totalPathSegments: values.totalPathSegments ?? null,
25
+ bounceHistogram: Object.freeze([...(values.bounceHistogram ?? [])]),
26
+ capturedRayCounts: values.capturedRayCounts ?? 0,
27
+ expectedRayCounts: values.expectedRayCounts ?? 0,
28
+ reason: values.reason ?? null,
29
+ });
30
+ }
31
+
32
+ export function createWavefrontRayCountTelemetry(values = {}) {
33
+ return freezeRayCountTelemetry(values);
34
+ }
35
+
36
+ export function createWavefrontFrameTimingTelemetry(values = {}) {
37
+ return Object.freeze({
38
+ status: values.status ?? "not-requested",
39
+ source: values.source ?? null,
40
+ timestampQueryStatus: values.timestampQueryStatus ?? "not-recorded",
41
+ totalGpuTimeMs: values.totalGpuTimeMs ?? null,
42
+ totalRenderJobTimeMs: Math.max(0, Number(values.totalRenderJobTimeMs ?? 0)),
43
+ classificationTimeMs: values.classificationTimeMs ?? null,
44
+ compactionTimeMs: values.compactionTimeMs ?? null,
45
+ samplingTimeMs: values.samplingTimeMs ?? null,
46
+ reason: values.reason ?? null,
47
+ });
48
+ }
49
+
50
+ function safeErrorReason(error) {
51
+ return error instanceof Error && error.message
52
+ ? error.message
53
+ : "unknown-telemetry-readback-failure";
54
+ }
55
+
56
+ function timestampDurationMs(values) {
57
+ if (values.length < TIMESTAMP_QUERY_COUNT || values[1] < values[0]) {
58
+ return null;
59
+ }
60
+ const durationNanoseconds = values[1] - values[0];
61
+ const durationMs = Number(durationNanoseconds) / 1_000_000;
62
+ return Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null;
63
+ }
64
+
65
+ export function createWavefrontFrameTelemetryResources({
66
+ device,
67
+ constants,
68
+ maxRayCountRecords,
69
+ }) {
70
+ const rayRecordCapacity = Math.max(1, Math.trunc(Number(maxRayCountRecords) || 1));
71
+ const rayBufferBytes = rayRecordCapacity * Uint32Array.BYTES_PER_ELEMENT;
72
+ const mapMode = constants.map;
73
+ if (!mapMode || typeof mapMode.READ !== "number") {
74
+ return Object.freeze({
75
+ available: false,
76
+ reason: "gpu-map-read-unavailable",
77
+ memoryBytes: 0,
78
+ beginFrame() {},
79
+ recordActiveRayCount() {},
80
+ decorateFirstPass(descriptor) {
81
+ return descriptor;
82
+ },
83
+ decorateFinalPass(descriptor) {
84
+ return descriptor;
85
+ },
86
+ async readFrame({ expectedPrimaryRays = 0, expectedRayCounts = 0 } = {}) {
87
+ return Object.freeze({
88
+ rayCounts: freezeRayCountTelemetry({
89
+ status: "unavailable",
90
+ expectedPrimaryRays,
91
+ expectedRayCounts,
92
+ reason: "gpu-map-read-unavailable",
93
+ }),
94
+ timestampQueryStatus: "unsupported",
95
+ totalGpuTimeMs: null,
96
+ reason: "gpu-map-read-unavailable",
97
+ });
98
+ },
99
+ destroy() {},
100
+ });
101
+ }
102
+
103
+ const rayCountBuffer = createBuffer(
104
+ device,
105
+ constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
106
+ rayBufferBytes,
107
+ "plasius.wavefront.rayCounts"
108
+ );
109
+ const rayCountReadbackBuffer = createBuffer(
110
+ device,
111
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
112
+ rayBufferBytes,
113
+ "plasius.wavefront.rayCounts.readback"
114
+ );
115
+
116
+ const timestampFeatureAvailable = hasGpuFeature(device?.features, "timestamp-query");
117
+ const timestampApiAvailable =
118
+ timestampFeatureAvailable &&
119
+ typeof device.createQuerySet === "function" &&
120
+ typeof constants.buffer.QUERY_RESOLVE === "number";
121
+ let timestampQuerySet = null;
122
+ let timestampResolveBuffer = null;
123
+ let timestampReadbackBuffer = null;
124
+ let timestampSetupReason = timestampFeatureAvailable
125
+ ? "timestamp-query-api-unavailable"
126
+ : "timestamp-query-unsupported";
127
+ if (timestampApiAvailable) {
128
+ try {
129
+ timestampQuerySet = device.createQuerySet({
130
+ label: "plasius.wavefront.timestamps",
131
+ type: "timestamp",
132
+ count: TIMESTAMP_QUERY_COUNT,
133
+ });
134
+ timestampResolveBuffer = createBuffer(
135
+ device,
136
+ constants.buffer.QUERY_RESOLVE | constants.buffer.COPY_SRC,
137
+ TIMESTAMP_BUFFER_BYTES,
138
+ "plasius.wavefront.timestamps.resolve"
139
+ );
140
+ timestampReadbackBuffer = createBuffer(
141
+ device,
142
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
143
+ TIMESTAMP_BUFFER_BYTES,
144
+ "plasius.wavefront.timestamps.readback"
145
+ );
146
+ timestampSetupReason = null;
147
+ } catch (error) {
148
+ timestampQuerySet?.destroy?.();
149
+ timestampQuerySet = null;
150
+ timestampResolveBuffer?.destroy?.();
151
+ timestampResolveBuffer = null;
152
+ timestampReadbackBuffer?.destroy?.();
153
+ timestampReadbackBuffer = null;
154
+ timestampSetupReason = `timestamp-query-setup-failed:${safeErrorReason(error)}`;
155
+ }
156
+ }
157
+ const timestampUnavailableStatus = timestampSetupReason?.startsWith(
158
+ "timestamp-query-setup-failed:"
159
+ )
160
+ ? "failed"
161
+ : "unsupported";
162
+
163
+ let active = false;
164
+ let recordCount = 0;
165
+ let recordOverflow = false;
166
+ let bounceIndices = [];
167
+ let timestampStarted = false;
168
+ let timestampEnded = false;
169
+ let rayReadbackMapped = false;
170
+ let timestampReadbackMapped = false;
171
+
172
+ const memoryBytes =
173
+ rayBufferBytes * 2 +
174
+ (timestampQuerySet ? TIMESTAMP_BUFFER_BYTES * 2 : 0);
175
+
176
+ function beginFrame() {
177
+ active = true;
178
+ recordCount = 0;
179
+ recordOverflow = false;
180
+ bounceIndices = [];
181
+ timestampStarted = false;
182
+ timestampEnded = false;
183
+ }
184
+
185
+ function recordActiveRayCount(encoder, counterBuffer, bounceIndex) {
186
+ if (!active) {
187
+ return;
188
+ }
189
+ if (recordCount >= rayRecordCapacity) {
190
+ recordOverflow = true;
191
+ return;
192
+ }
193
+ encoder.copyBufferToBuffer(
194
+ counterBuffer,
195
+ 0,
196
+ rayCountBuffer,
197
+ recordCount * Uint32Array.BYTES_PER_ELEMENT,
198
+ Uint32Array.BYTES_PER_ELEMENT
199
+ );
200
+ bounceIndices.push(Math.max(0, Math.trunc(Number(bounceIndex) || 0)));
201
+ recordCount += 1;
202
+ }
203
+
204
+ function decorateFirstPass(descriptor) {
205
+ if (!active || !timestampQuerySet || timestampStarted) {
206
+ return descriptor;
207
+ }
208
+ timestampStarted = true;
209
+ return {
210
+ ...descriptor,
211
+ timestampWrites: {
212
+ querySet: timestampQuerySet,
213
+ beginningOfPassWriteIndex: 0,
214
+ },
215
+ };
216
+ }
217
+
218
+ function decorateFinalPass(descriptor) {
219
+ if (!active || !timestampQuerySet || !timestampStarted || timestampEnded) {
220
+ return descriptor;
221
+ }
222
+ timestampEnded = true;
223
+ return {
224
+ ...descriptor,
225
+ timestampWrites: {
226
+ querySet: timestampQuerySet,
227
+ endOfPassWriteIndex: 1,
228
+ },
229
+ };
230
+ }
231
+
232
+ async function readFrame({
233
+ expectedPrimaryRays = 0,
234
+ expectedRayCounts = recordCount,
235
+ waitForSubmittedGpuWork,
236
+ } = {}) {
237
+ active = false;
238
+ if (recordOverflow || recordCount !== expectedRayCounts) {
239
+ return Object.freeze({
240
+ rayCounts: freezeRayCountTelemetry({
241
+ status: "failed",
242
+ expectedPrimaryRays,
243
+ capturedRayCounts: recordCount,
244
+ expectedRayCounts,
245
+ reason: recordOverflow
246
+ ? "ray-count-record-capacity-exceeded"
247
+ : `ray-count-record-mismatch:${recordCount}/${expectedRayCounts}`,
248
+ }),
249
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
250
+ totalGpuTimeMs: null,
251
+ reason: "ray-count-recording-incomplete",
252
+ });
253
+ }
254
+
255
+ const encoder = device.createCommandEncoder({
256
+ label: "plasius.wavefront.telemetry.readback",
257
+ });
258
+ const rayCopyBytes = Math.max(
259
+ Uint32Array.BYTES_PER_ELEMENT,
260
+ recordCount * Uint32Array.BYTES_PER_ELEMENT
261
+ );
262
+ encoder.copyBufferToBuffer(rayCountBuffer, 0, rayCountReadbackBuffer, 0, rayCopyBytes);
263
+ const canResolveTimestamps =
264
+ timestampQuerySet &&
265
+ timestampStarted &&
266
+ timestampEnded &&
267
+ typeof encoder.resolveQuerySet === "function";
268
+ if (canResolveTimestamps) {
269
+ encoder.resolveQuerySet(
270
+ timestampQuerySet,
271
+ 0,
272
+ TIMESTAMP_QUERY_COUNT,
273
+ timestampResolveBuffer,
274
+ 0
275
+ );
276
+ encoder.copyBufferToBuffer(
277
+ timestampResolveBuffer,
278
+ 0,
279
+ timestampReadbackBuffer,
280
+ 0,
281
+ TIMESTAMP_BUFFER_BYTES
282
+ );
283
+ }
284
+ device.queue.submit([encoder.finish()]);
285
+
286
+ try {
287
+ await waitForSubmittedGpuWork({
288
+ timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
289
+ allowTimeout: false,
290
+ });
291
+ await rayCountReadbackBuffer.mapAsync(mapMode.READ);
292
+ rayReadbackMapped = true;
293
+ const rayCopy = rayCountReadbackBuffer
294
+ .getMappedRange()
295
+ .slice(0, rayCopyBytes);
296
+ const rayCounts = new Uint32Array(rayCopy, 0, recordCount);
297
+ rayCountReadbackBuffer.unmap();
298
+ rayReadbackMapped = false;
299
+
300
+ const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
301
+ let observedPrimaryRays = 0;
302
+ let secondaryRays = 0;
303
+ let totalPathSegments = 0;
304
+ for (let index = 0; index < rayCounts.length; index += 1) {
305
+ const count = Number(rayCounts[index] ?? 0);
306
+ const bounceIndex = bounceIndices[index] ?? 0;
307
+ bounceHistogram[bounceIndex] = (bounceHistogram[bounceIndex] ?? 0) + count;
308
+ totalPathSegments += count;
309
+ if (bounceIndex === 0) {
310
+ observedPrimaryRays += count;
311
+ } else {
312
+ secondaryRays += count;
313
+ }
314
+ }
315
+ const primaryCountsMatch = observedPrimaryRays === expectedPrimaryRays;
316
+ const rayTelemetry = freezeRayCountTelemetry({
317
+ status: primaryCountsMatch ? "available" : "failed",
318
+ source: "gpu-active-queue-readback",
319
+ expectedPrimaryRays,
320
+ observedPrimaryRays,
321
+ secondaryRays: primaryCountsMatch ? secondaryRays : null,
322
+ totalPathSegments: primaryCountsMatch ? totalPathSegments : null,
323
+ bounceHistogram,
324
+ capturedRayCounts: recordCount,
325
+ expectedRayCounts,
326
+ reason: primaryCountsMatch
327
+ ? null
328
+ : `primary-ray-count-mismatch:${observedPrimaryRays}/${expectedPrimaryRays}`,
329
+ });
330
+
331
+ let timestampQueryStatus = timestampQuerySet ? "failed" : timestampUnavailableStatus;
332
+ let totalGpuTimeMs = null;
333
+ let timestampReason = timestampSetupReason;
334
+ if (canResolveTimestamps) {
335
+ try {
336
+ await timestampReadbackBuffer.mapAsync(mapMode.READ);
337
+ timestampReadbackMapped = true;
338
+ const timestampCopy = timestampReadbackBuffer
339
+ .getMappedRange()
340
+ .slice(0, TIMESTAMP_BUFFER_BYTES);
341
+ const timestamps = new BigUint64Array(timestampCopy);
342
+ timestampReadbackBuffer.unmap();
343
+ timestampReadbackMapped = false;
344
+ totalGpuTimeMs = timestampDurationMs(timestamps);
345
+ timestampQueryStatus = totalGpuTimeMs === null ? "failed" : "available";
346
+ timestampReason =
347
+ totalGpuTimeMs === null ? "timestamp-query-returned-invalid-range" : null;
348
+ } catch (error) {
349
+ if (timestampReadbackMapped) {
350
+ timestampReadbackBuffer.unmap();
351
+ timestampReadbackMapped = false;
352
+ }
353
+ timestampQueryStatus = "failed";
354
+ timestampReason = `timestamp-query-readback-failed:${safeErrorReason(error)}`;
355
+ }
356
+ } else if (timestampQuerySet) {
357
+ timestampReason = "timestamp-query-span-incomplete";
358
+ }
359
+
360
+ return Object.freeze({
361
+ rayCounts: rayTelemetry,
362
+ timestampQueryStatus,
363
+ totalGpuTimeMs,
364
+ reason: timestampReason,
365
+ });
366
+ } catch (error) {
367
+ if (rayReadbackMapped) {
368
+ rayCountReadbackBuffer.unmap();
369
+ rayReadbackMapped = false;
370
+ }
371
+ if (timestampReadbackMapped) {
372
+ timestampReadbackBuffer?.unmap();
373
+ timestampReadbackMapped = false;
374
+ }
375
+ const reason = safeErrorReason(error);
376
+ return Object.freeze({
377
+ rayCounts: freezeRayCountTelemetry({
378
+ status: "failed",
379
+ expectedPrimaryRays,
380
+ capturedRayCounts: recordCount,
381
+ expectedRayCounts,
382
+ reason: `ray-count-readback-failed:${reason}`,
383
+ }),
384
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
385
+ totalGpuTimeMs: null,
386
+ reason: `telemetry-readback-failed:${reason}`,
387
+ });
388
+ }
389
+ }
390
+
391
+ function destroy() {
392
+ active = false;
393
+ rayCountBuffer.destroy?.();
394
+ rayCountReadbackBuffer.destroy?.();
395
+ timestampQuerySet?.destroy?.();
396
+ timestampResolveBuffer?.destroy?.();
397
+ timestampReadbackBuffer?.destroy?.();
398
+ }
399
+
400
+ return Object.freeze({
401
+ available: true,
402
+ reason: null,
403
+ memoryBytes,
404
+ beginFrame,
405
+ recordActiveRayCount,
406
+ decorateFirstPass,
407
+ decorateFinalPass,
408
+ readFrame,
409
+ destroy,
410
+ });
411
+ }
@@ -47,6 +47,18 @@ export function createWavefrontBindGroupLayouts(device, constants) {
47
47
  { binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
48
48
  { binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
49
49
  { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
50
+ { binding: 33, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
51
+ { binding: 34, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
52
+ { binding: 35, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
53
+ { binding: 36, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
54
+ { binding: 37, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
55
+ { binding: 38, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
56
+ { binding: 39, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
57
+ { binding: 40, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
58
+ { binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
59
+ { binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
60
+ { binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
61
+ { binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
50
62
  ],
51
63
  });
52
64
  const acceleration = device.createBindGroupLayout({
@@ -3,6 +3,7 @@ import {
3
3
  GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
4
4
  GPU_READBACK_COMPLETION_TIMEOUT_MS,
5
5
  GPU_SUBMITTED_WORK_TIMEOUT_MS,
6
+ TRACE_SAMPLED_TEXTURE_BINDINGS,
6
7
  TRACE_STORAGE_BUFFER_BINDINGS,
7
8
  WORKGROUP_SIZE,
8
9
  readNonNegativeInteger,
@@ -50,11 +51,11 @@ export function resolveCanvas(canvasOrSelector, documentRef = globalThis.documen
50
51
  }
51
52
 
52
53
  async function getPipelineDiagnostics(shaderModule) {
53
- if (typeof shaderModule?.compilationInfo !== "function") {
54
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
54
55
  return "";
55
56
  }
56
57
  try {
57
- const info = await shaderModule.compilationInfo();
58
+ const info = await shaderModule.getCompilationInfo();
58
59
  const messages = info.messages ?? [];
59
60
  if (messages.length === 0) {
60
61
  return "";
@@ -96,10 +97,10 @@ export async function createComputePipeline(device, shaderModule, layout, entryP
96
97
  }
97
98
 
98
99
  export async function assertShaderModuleCompiles(shaderModule, label) {
99
- if (typeof shaderModule?.compilationInfo !== "function") {
100
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
100
101
  return;
101
102
  }
102
- const info = await shaderModule.compilationInfo();
103
+ const info = await shaderModule.getCompilationInfo();
103
104
  const messages = Array.isArray(info?.messages) ? info.messages : [];
104
105
  const errors = messages.filter((message) => message?.type === "error");
105
106
  if (errors.length <= 0) {
@@ -138,7 +139,39 @@ export function createWavefrontDeviceDescriptor(adapter, options = {}) {
138
139
  );
139
140
  }
140
141
 
142
+ const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
143
+ if (Number.isFinite(exposedSampledTextureLimit)) {
144
+ if (exposedSampledTextureLimit < TRACE_SAMPLED_TEXTURE_BINDINGS) {
145
+ throw new Error(
146
+ `Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${TRACE_SAMPLED_TEXTURE_BINDINGS}, ` +
147
+ `but this adapter exposes ${exposedSampledTextureLimit}.`
148
+ );
149
+ }
150
+ requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
151
+ Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
152
+ TRACE_SAMPLED_TEXTURE_BINDINGS
153
+ );
154
+ }
155
+
141
156
  const descriptor = { ...(options.deviceDescriptor ?? {}) };
157
+ const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
158
+ const adapterFeatures = adapter?.features;
159
+ const timestampQuerySupported =
160
+ typeof adapterFeatures?.has === "function"
161
+ ? adapterFeatures.has("timestamp-query")
162
+ : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function"
163
+ ? Array.from(adapterFeatures).includes("timestamp-query")
164
+ : false;
165
+ if (
166
+ options.gpuTimestamps !== false &&
167
+ timestampQuerySupported &&
168
+ !requiredFeatures.includes("timestamp-query")
169
+ ) {
170
+ requiredFeatures.push("timestamp-query");
171
+ }
172
+ if (requiredFeatures.length > 0) {
173
+ descriptor.requiredFeatures = requiredFeatures;
174
+ }
142
175
  if (Object.keys(requiredLimits).length > 0) {
143
176
  descriptor.requiredLimits = {
144
177
  ...(descriptor.requiredLimits ?? {}),
@@ -113,36 +113,49 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
113
113
  let uv1 = select(vec2<f32>(0.0), vertex1.uv.xy, vertex1.uv.z > 0.5);
114
114
  let uv2 = select(vec2<f32>(0.0), vertex2.uv.xy, vertex2.uv.z > 0.5);
115
115
 
116
- triangles[triangleIndex] = TriangleRecord(
117
- triangleIndex,
118
- mesh.meshId,
119
- mesh.materialKind,
120
- mesh.flags,
121
- mesh.materialRefId,
122
- mesh.mediumRefId,
123
- mesh.materialSlot,
124
- 0u,
125
- vec4<f32>(vertex0.position.xyz, 0.0),
126
- vec4<f32>(vertex1.position.xyz, 0.0),
127
- vec4<f32>(vertex2.position.xyz, 0.0),
128
- vec4<f32>(n0, 0.0),
129
- vec4<f32>(n1, 0.0),
130
- vec4<f32>(n2, 0.0),
131
- vec4<f32>(uv0, uv1),
132
- vec4<f32>(uv2, 0.0, 0.0),
133
- mesh.color,
134
- mesh.emission,
135
- mesh.material,
136
- mesh.materialResponse,
137
- mesh.materialExtension,
138
- mesh.specularColor,
139
- mesh.baseColorAtlas,
140
- mesh.metallicRoughnessAtlas,
141
- mesh.normalAtlas,
142
- mesh.occlusionAtlas,
143
- mesh.emissiveAtlas,
144
- mesh.textureSettings
145
- );
116
+ var preparedTriangle = TriangleRecord();
117
+ preparedTriangle.triangleId = triangleIndex;
118
+ preparedTriangle.meshId = mesh.meshId;
119
+ preparedTriangle.materialKind = mesh.materialKind;
120
+ preparedTriangle.flags = mesh.flags;
121
+ preparedTriangle.materialRefId = mesh.materialRefId;
122
+ preparedTriangle.mediumRefId = mesh.mediumRefId;
123
+ preparedTriangle.materialSlot = mesh.materialSlot;
124
+ preparedTriangle.v0 = vec4<f32>(vertex0.position.xyz, 0.0);
125
+ preparedTriangle.v1 = vec4<f32>(vertex1.position.xyz, 0.0);
126
+ preparedTriangle.v2 = vec4<f32>(vertex2.position.xyz, 0.0);
127
+ preparedTriangle.n0 = vec4<f32>(n0, 0.0);
128
+ preparedTriangle.n1 = vec4<f32>(n1, 0.0);
129
+ preparedTriangle.n2 = vec4<f32>(n2, 0.0);
130
+ preparedTriangle.uv0uv1 = vec4<f32>(uv0, uv1);
131
+ preparedTriangle.uv2Pad = vec4<f32>(uv2, 0.0, 0.0);
132
+ preparedTriangle.color = mesh.color;
133
+ preparedTriangle.emission = mesh.emission;
134
+ preparedTriangle.material = mesh.material;
135
+ preparedTriangle.materialResponse = mesh.materialResponse;
136
+ preparedTriangle.materialExtension = mesh.materialExtension;
137
+ preparedTriangle.specularColor = mesh.specularColor;
138
+ preparedTriangle.baseColorAtlas = mesh.baseColorAtlas;
139
+ preparedTriangle.metallicRoughnessAtlas = mesh.metallicRoughnessAtlas;
140
+ preparedTriangle.normalAtlas = mesh.normalAtlas;
141
+ preparedTriangle.occlusionAtlas = mesh.occlusionAtlas;
142
+ preparedTriangle.emissiveAtlas = mesh.emissiveAtlas;
143
+ preparedTriangle.textureSettings = mesh.textureSettings;
144
+ preparedTriangle.materialExtension2 = vec4<f32>(0.0, 0.0, 1.3, 0.0);
145
+ preparedTriangle.materialExtension3 = vec4<f32>(100.0, 400.0, 0.0, 0.0);
146
+ preparedTriangle.clearcoatAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
147
+ preparedTriangle.clearcoatRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
148
+ preparedTriangle.clearcoatNormalAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
149
+ preparedTriangle.transmissionAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
150
+ preparedTriangle.thicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
151
+ preparedTriangle.sheenColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
152
+ preparedTriangle.sheenRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
153
+ preparedTriangle.specularAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
154
+ preparedTriangle.specularColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
155
+ preparedTriangle.iridescenceAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
156
+ preparedTriangle.iridescenceThicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
157
+ preparedTriangle.anisotropyAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
158
+ triangles[triangleIndex] = preparedTriangle;
146
159
 
147
160
  let leafBase = config.triangleCount - 1u;
148
161
  let nodeIndex = leafBase + triangleIndex;
@@ -224,36 +224,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
224
224
  vec4<f32>(1.0, 1.0, 1.0, 1.0)
225
225
  );
226
226
  var candidate = no_candidate();
227
- var hitTriangle = TriangleRecord(
228
- 0u,
229
- 0u,
230
- 0u,
231
- 0u,
232
- 0u,
233
- 0u,
234
- 0u,
235
- 0u,
236
- vec4<f32>(0.0),
237
- vec4<f32>(0.0),
238
- vec4<f32>(0.0),
239
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
240
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
241
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
242
- vec4<f32>(0.0),
243
- vec4<f32>(0.0),
244
- vec4<f32>(0.0),
245
- vec4<f32>(0.0),
246
- vec4<f32>(1.0, 0.0, 1.0, 1.0),
247
- vec4<f32>(0.0, 0.0, 0.0, 0.08),
248
- vec4<f32>(0.08, 1.0, 0.0, 0.0),
249
- vec4<f32>(1.0, 1.0, 1.0, 1.0),
250
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
251
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
252
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
253
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
254
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
255
- vec4<f32>(1.0, 1.0, 1.0, 0.0)
256
- );
227
+ var hitTriangle = TriangleRecord();
257
228
 
258
229
  for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
259
230
  let object = sceneObjects[objectIndex];