@plasius/gpu-renderer 0.2.56 → 0.2.57
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/CHANGELOG.md +17 -0
- package/README.md +48 -0
- package/dist/index.cjs +2256 -1811
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2256 -1811
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/gpu-allocation-budget.js +92 -0
- package/src/index.d.ts +30 -0
- package/src/wavefront-bind-groups.js +2 -0
- package/src/wavefront-compute.js +41 -10
- package/src/wavefront-config.js +16 -2
- package/src/wavefront-instance-shader.js +86 -0
- package/src/wavefront-instances.js +107 -0
- package/src/wavefront-mesh-sources.js +2 -2
- package/src/wavefront-pipelines.js +6 -5
- package/src/wavefront-runtime-support.js +8 -6
- package/src/wavefront-shader-bvh.js +4 -2
- package/src/wavefront-shader-kernels.js +1 -1
- package/src/wavefront-shader-layout.js +1 -0
- package/src/wavefront-shaders.js +5 -1
- package/src/wavefront-upload.js +12 -0
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Accounts for requested WebGPU resource storage, not driver overhead or free VRAM.
|
|
2
|
+
const formats = Object.freeze({
|
|
3
|
+
r8unorm:1, r8snorm:1, r8uint:1, r8sint:1,
|
|
4
|
+
rg8unorm:2, rg8snorm:2, rg8uint:2, rg8sint:2, r16float:2, r16uint:2, r16sint:2,
|
|
5
|
+
rgba8unorm:4, 'rgba8unorm-srgb':4, bgra8unorm:4, 'bgra8unorm-srgb':4,
|
|
6
|
+
rgba8snorm:4, rgba8uint:4, rgba8sint:4, rg16float:4, rg16uint:4, rg16sint:4,
|
|
7
|
+
r32float:4, r32uint:4, r32sint:4, rgb10a2unorm:4, rg11b10ufloat:4,
|
|
8
|
+
rgba16float:8, rgba16uint:8, rgba16sint:8, rg32float:8, rg32uint:8, rg32sint:8,
|
|
9
|
+
rgba32float:16, rgba32uint:16, rgba32sint:16,
|
|
10
|
+
});
|
|
11
|
+
function integer(value, name, minimum=1) {
|
|
12
|
+
if(!Number.isSafeInteger(value)||value<minimum) throw new Error(`${name} must be a safe integer >= ${minimum}.`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function within(value,limit,name) {
|
|
16
|
+
if(Number.isFinite(limit)&&value>limit) throw new Error(`GPU ${name} limit exceeded: requested ${value}, available ${limit}.`);
|
|
17
|
+
}
|
|
18
|
+
function textureBytes(descriptor,limits) {
|
|
19
|
+
const size=descriptor.size;
|
|
20
|
+
const array=Array.isArray(size)||ArrayBuffer.isView(size);
|
|
21
|
+
const width=integer(array?size[0]:size?.width,'Texture width');
|
|
22
|
+
const height=integer((array?size[1]:size?.height)??1,'Texture height');
|
|
23
|
+
const layers=integer((array?size[2]:size?.depthOrArrayLayers)??1,'Texture depth/layers');
|
|
24
|
+
const dimension=descriptor.dimension??'2d';
|
|
25
|
+
if(!['1d','2d','3d'].includes(dimension)) throw new Error('Unsupported GPU texture dimension.');
|
|
26
|
+
const limit=limits[dimension==='3d'?'maxTextureDimension3D':dimension==='1d'?'maxTextureDimension1D':'maxTextureDimension2D'];
|
|
27
|
+
within(Math.max(width,height,dimension==='3d'?layers:1),limit,'texture dimension');
|
|
28
|
+
if(dimension!=='3d') within(layers,limits.maxTextureArrayLayers,'texture layers');
|
|
29
|
+
const bpp=formats[descriptor.format];
|
|
30
|
+
if(!bpp) throw new Error(`GPU memory accounting does not support texture format ${descriptor.format}.`);
|
|
31
|
+
const levels=integer(descriptor.mipLevelCount??1,'Mip count');
|
|
32
|
+
const samples=integer(descriptor.sampleCount??1,'Sample count');
|
|
33
|
+
if(levels>Math.floor(Math.log2(Math.max(width,height,dimension==='3d'?layers:1)))+1) throw new Error('Invalid texture mip count.');
|
|
34
|
+
let bytes=0;
|
|
35
|
+
for(let level=0;level<levels;level++) bytes+=Math.max(1,Math.floor(width/2**level))*Math.max(1,Math.floor(height/2**level))*
|
|
36
|
+
(dimension==='3d'?Math.max(1,Math.floor(layers/2**level)):layers)*bpp*samples;
|
|
37
|
+
return integer(bytes,'Texture allocation');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createGpuAllocationTracker(rawDevice,{budgetBytes,storageUsage=globalThis.GPUBufferUsage?.STORAGE??128}={}) {
|
|
41
|
+
if(budgetBytes!==undefined) integer(budgetBytes,'GPU memory budget');
|
|
42
|
+
const records=new Map();let allocatedBytes=0,peakAllocatedBytes=0;
|
|
43
|
+
function allocate(kind,descriptor,bytes,create) {
|
|
44
|
+
integer(bytes,'GPU allocation',0);
|
|
45
|
+
const total=integer(allocatedBytes+bytes,'GPU allocation total',0);
|
|
46
|
+
if(budgetBytes!==undefined&&total>budgetBytes) throw new Error(`GPU memory budget exceeded: ${total} requested bytes, budget ${budgetBytes}. Reduce quality or scene resources.`);
|
|
47
|
+
const resource=create(descriptor),originalDestroy=resource.destroy.bind(resource);
|
|
48
|
+
const release=()=>{if(!records.has(resource))return;originalDestroy();records.delete(resource);allocatedBytes-=bytes;};
|
|
49
|
+
try{Object.defineProperty(resource,'destroy',{value:release,configurable:true});}
|
|
50
|
+
catch(error){originalDestroy();throw error;}
|
|
51
|
+
records.set(resource,{kind,label:descriptor.label??'',bytes,release});allocatedBytes=total;peakAllocatedBytes=Math.max(total,peakAllocatedBytes);
|
|
52
|
+
return resource;
|
|
53
|
+
}
|
|
54
|
+
const destroy=()=>{for(const record of [...records.values()])record.release();};
|
|
55
|
+
const methods={
|
|
56
|
+
createBuffer(descriptor){
|
|
57
|
+
const bytes=integer(descriptor.size,'Buffer size',0);
|
|
58
|
+
within(bytes,rawDevice.limits?.maxBufferSize,'buffer size');
|
|
59
|
+
if((descriptor.usage??0)&storageUsage) within(bytes,rawDevice.limits?.maxStorageBufferBindingSize,'storage binding size');
|
|
60
|
+
return allocate('buffer',descriptor,bytes,d=>rawDevice.createBuffer(d));
|
|
61
|
+
},
|
|
62
|
+
createTexture(descriptor){return allocate('texture',descriptor,textureBytes(descriptor,rawDevice.limits??{}),d=>rawDevice.createTexture(d));},
|
|
63
|
+
createQuerySet(descriptor){return allocate('query-set',descriptor,integer(descriptor.count,'Query count')*8,d=>rawDevice.createQuerySet(d));},
|
|
64
|
+
destroy(){destroy();rawDevice.destroy();},
|
|
65
|
+
};
|
|
66
|
+
const device=new Proxy(rawDevice,{get(target,key){
|
|
67
|
+
if(Object.hasOwn(methods,key))return methods[key];
|
|
68
|
+
const value=Reflect.get(target,key,target);return typeof value==='function'?value.bind(target):value;
|
|
69
|
+
}});
|
|
70
|
+
return Object.freeze({device,destroy,snapshot:()=>Object.freeze({
|
|
71
|
+
accounting:'requested-resource-bytes',budgetBytes:budgetBytes??null,allocatedBytes,peakAllocatedBytes,
|
|
72
|
+
resources:Object.freeze([...records.values()].map(({kind,label,bytes})=>Object.freeze({kind,label,bytes}))),
|
|
73
|
+
})});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Pop both scopes before awaiting either, so rejection cannot leave a scope open. */
|
|
77
|
+
export function openGpuAllocationErrorScopes(device) {
|
|
78
|
+
if(typeof device.pushErrorScope!=='function'||typeof device.popErrorScope!=='function') return async()=>{};
|
|
79
|
+
let count=0;
|
|
80
|
+
try{device.pushErrorScope('out-of-memory');count++;device.pushErrorScope('validation');count++;}
|
|
81
|
+
catch(error){while(count-->0)Promise.resolve(device.popErrorScope()).catch(()=>{});throw error;}
|
|
82
|
+
return async()=>{
|
|
83
|
+
const completions=[];
|
|
84
|
+
while(count>0){count--;try{completions.push(Promise.resolve(device.popErrorScope()));}catch(error){completions.push(Promise.reject(error));}}
|
|
85
|
+
if(!completions.length)return;
|
|
86
|
+
let timer;
|
|
87
|
+
try{
|
|
88
|
+
const errors=await Promise.race([Promise.all(completions),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('GPU allocation validation timed out.')),10000);})]);
|
|
89
|
+
const error=errors.find(Boolean);if(error)throw new Error(`GPU allocation failed: ${error.message??String(error)}`);
|
|
90
|
+
}finally{clearTimeout(timer);}
|
|
91
|
+
};
|
|
92
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -899,6 +899,11 @@ export interface WavefrontPathTracingComputeConfig {
|
|
|
899
899
|
readonly gpuAccelerationBuildRequired: boolean;
|
|
900
900
|
readonly gpuMeshSource: WavefrontGpuMeshSource;
|
|
901
901
|
readonly gpuMaterialSource: WavefrontGpuMaterialSource;
|
|
902
|
+
readonly instancing: Readonly<{
|
|
903
|
+
instanceCount: number; geometryCount: number; instanceBytes: number;
|
|
904
|
+
tlasNodeCount: number; blasNodeCount: number; buffer: ArrayBuffer;
|
|
905
|
+
acceleration: WavefrontMeshAcceleration;
|
|
906
|
+
}> | null;
|
|
902
907
|
readonly meshAcceleration: WavefrontMeshAcceleration;
|
|
903
908
|
readonly emissiveTriangleIndices: WavefrontEmissiveTriangleIndexSource;
|
|
904
909
|
readonly emissiveTriangleCount: number;
|
|
@@ -1045,6 +1050,8 @@ export interface WavefrontPathTracingMemoryEstimate {
|
|
|
1045
1050
|
readonly pathVertexBytes: number;
|
|
1046
1051
|
readonly sceneObjectBytes: number;
|
|
1047
1052
|
readonly triangleBytes: number;
|
|
1053
|
+
/** Instance lookup texture bytes; separate from totalHotBufferBytes. */
|
|
1054
|
+
readonly instanceBytes: number;
|
|
1048
1055
|
readonly materialTableBytes: number;
|
|
1049
1056
|
readonly bvhNodeBytes: number;
|
|
1050
1057
|
readonly bvhCombinedBytes: number;
|
|
@@ -1102,6 +1109,21 @@ export interface WavefrontGpuParallelismDiagnostics {
|
|
|
1102
1109
|
readonly coreUtilizationStatus: "not-exposed-by-webgpu";
|
|
1103
1110
|
}
|
|
1104
1111
|
|
|
1112
|
+
/** Static column-major affine transform referencing a unique mesh. */
|
|
1113
|
+
export interface WavefrontMeshInstanceInput {
|
|
1114
|
+
readonly id: number;
|
|
1115
|
+
readonly meshId: number;
|
|
1116
|
+
readonly transform: readonly number[] | Float32Array;
|
|
1117
|
+
}
|
|
1118
|
+
export interface WavefrontGpuAllocationSnapshot {
|
|
1119
|
+
/** Requested resource storage; excludes driver overhead and externally owned resources. */
|
|
1120
|
+
readonly accounting: "requested-resource-bytes";
|
|
1121
|
+
readonly budgetBytes: number | null;
|
|
1122
|
+
readonly allocatedBytes: number;
|
|
1123
|
+
readonly peakAllocatedBytes: number;
|
|
1124
|
+
readonly resources: readonly Readonly<{kind: string; label: string; bytes: number}>[];
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1105
1127
|
export interface CreateWavefrontPathTracingComputeRendererOptions {
|
|
1106
1128
|
/** Accumulate linear radiance between frames until camera/scene/reset changes. Default false. */
|
|
1107
1129
|
readonly progressive?: boolean;
|
|
@@ -1127,6 +1149,10 @@ export interface CreateWavefrontPathTracingComputeRendererOptions {
|
|
|
1127
1149
|
readonly mediums?: readonly WavefrontMediumInput[];
|
|
1128
1150
|
readonly mesh?: WavefrontMeshInput;
|
|
1129
1151
|
readonly meshes?: readonly WavefrontMeshInput[];
|
|
1152
|
+
/** Opt-in shared BLAS/TLAS instances. Every mesh must be referenced. Static, non-emissive meshes; CPU BLAS construction. */
|
|
1153
|
+
readonly instances?: readonly WavefrontMeshInstanceInput[];
|
|
1154
|
+
/** Positive renderer-owned resource byte budget. Device limits apply independently. */
|
|
1155
|
+
readonly memoryBudgetBytes?: number;
|
|
1130
1156
|
readonly triangleCapacity?: number;
|
|
1131
1157
|
readonly bvhNodeCapacity?: number;
|
|
1132
1158
|
readonly bvhLeafSortCapacity?: number;
|
|
@@ -1203,6 +1229,8 @@ export interface WavefrontPathTracingComputeRenderer {
|
|
|
1203
1229
|
maxFramePassesPerSubmission: number;
|
|
1204
1230
|
sceneObjectCount: number;
|
|
1205
1231
|
triangleCount: number;
|
|
1232
|
+
instanceCount: number;
|
|
1233
|
+
geometryCount: number;
|
|
1206
1234
|
emissiveTriangleCount: number;
|
|
1207
1235
|
environmentPortalCount: number;
|
|
1208
1236
|
environmentPortalMode: 0 | 1 | 2;
|
|
@@ -1221,6 +1249,7 @@ export interface WavefrontPathTracingComputeRenderer {
|
|
|
1221
1249
|
frameConfigSlots: number;
|
|
1222
1250
|
gpuParallelism: WavefrontGpuParallelismDiagnostics;
|
|
1223
1251
|
memory: WavefrontPathTracingMemoryEstimate;
|
|
1252
|
+
allocations: WavefrontGpuAllocationSnapshot;
|
|
1224
1253
|
}>;
|
|
1225
1254
|
destroy(): void;
|
|
1226
1255
|
}
|
|
@@ -1557,6 +1586,7 @@ export function renderWavefrontPathTracingComputeFrame(
|
|
|
1557
1586
|
}
|
|
1558
1587
|
): Promise<WavefrontPathTracingComputeFrameStats>;
|
|
1559
1588
|
export function createWavefrontPathTracingComputeShaderSource(options?: {
|
|
1589
|
+
readonly instanced?: boolean;
|
|
1560
1590
|
workgroupSize?: number;
|
|
1561
1591
|
outputTextureFormat?: GPUTextureFormat | "rgba8unorm";
|
|
1562
1592
|
}): string;
|
|
@@ -12,6 +12,7 @@ export function createWavefrontTraceBindGroup({
|
|
|
12
12
|
counterBuffer,
|
|
13
13
|
outputView,
|
|
14
14
|
triangleBuffer,
|
|
15
|
+
instanceView,
|
|
15
16
|
bvhNodeBuffer,
|
|
16
17
|
radianceView,
|
|
17
18
|
environmentPortalBuffer,
|
|
@@ -70,6 +71,7 @@ export function createWavefrontTraceBindGroup({
|
|
|
70
71
|
{ binding: 42, resource: extensionAtlasResources.iridescence.view },
|
|
71
72
|
{ binding: 43, resource: extensionAtlasResources.iridescenceThickness.view },
|
|
72
73
|
{ binding: 44, resource: extensionAtlasResources.anisotropy.view },
|
|
74
|
+
...(instanceView ? [{binding:47, resource:instanceView}] : []),
|
|
73
75
|
],
|
|
74
76
|
});
|
|
75
77
|
}
|
package/src/wavefront-compute.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { uploadWavefrontBuffer } from './wavefront-upload.js';
|
|
2
|
+
import { createGpuAllocationTracker, openGpuAllocationErrorScopes } from './gpu-allocation-budget.js';
|
|
1
3
|
import { createWavefrontProgressiveResources } from './wavefront-progressive.js';
|
|
2
4
|
import {
|
|
3
5
|
createGpuParallelismCounters,
|
|
@@ -190,11 +192,16 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
190
192
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
const
|
|
195
|
+
const rawDevice = await adapter.requestDevice(
|
|
194
196
|
createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
|
|
195
197
|
);
|
|
198
|
+
const allocationTracker = createGpuAllocationTracker(rawDevice, {budgetBytes:options.memoryBudgetBytes,storageUsage:constants.buffer.STORAGE});
|
|
199
|
+
const device = allocationTracker.device;
|
|
200
|
+
let context, finishAllocationScopes;
|
|
201
|
+
try {
|
|
202
|
+
finishAllocationScopes = openGpuAllocationErrorScopes(rawDevice);
|
|
196
203
|
const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
|
|
197
|
-
|
|
204
|
+
context = canvas.getContext("webgpu");
|
|
198
205
|
if (!context || typeof context.configure !== "function") {
|
|
199
206
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
200
207
|
}
|
|
@@ -226,13 +233,23 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
226
233
|
canvas.width = config.width;
|
|
227
234
|
canvas.height = config.height;
|
|
228
235
|
context.configure({
|
|
229
|
-
device,
|
|
236
|
+
device: rawDevice,
|
|
230
237
|
format,
|
|
231
238
|
alphaMode: options.alpha === true ? "premultiplied" : "opaque",
|
|
232
239
|
});
|
|
233
240
|
|
|
234
241
|
const bufferUsage = constants.buffer.STORAGE | constants.buffer.COPY_DST;
|
|
235
242
|
const memory = config.memory;
|
|
243
|
+
let instanceTexture = null, instanceView = null;
|
|
244
|
+
if(config.instancing) {
|
|
245
|
+
const count=config.instancing.instanceCount;
|
|
246
|
+
instanceTexture=device.createTexture({label:"plasius.wavefront.instances",size:{width:9,height:count},
|
|
247
|
+
format:"rgba32uint",usage:constants.texture.TEXTURE_BINDING|constants.texture.COPY_DST});
|
|
248
|
+
const upload=new Uint8Array(count*256),source=new Uint8Array(config.instancing.buffer);
|
|
249
|
+
for(let i=0;i<count;i++) upload.set(source.subarray(i*144,(i+1)*144),i*256);
|
|
250
|
+
device.queue.writeTexture({texture:instanceTexture},upload,{bytesPerRow:256,rowsPerImage:count},{width:9,height:count,depthOrArrayLayers:1});
|
|
251
|
+
instanceView=instanceTexture.createView();
|
|
252
|
+
}
|
|
236
253
|
const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
|
|
237
254
|
const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
|
|
238
255
|
const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
|
|
@@ -354,11 +371,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
354
371
|
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
355
372
|
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
356
373
|
});
|
|
357
|
-
device
|
|
358
|
-
device
|
|
359
|
-
device
|
|
360
|
-
device
|
|
361
|
-
device
|
|
374
|
+
await uploadWavefrontBuffer(device, triangleBuffer, packedTriangles.buffer);
|
|
375
|
+
await uploadWavefrontBuffer(device, bvhNodeBuffer, packedBvhNodes.buffer);
|
|
376
|
+
await uploadWavefrontBuffer(device, meshVertexBuffer, config.gpuMeshSource.vertices.buffer);
|
|
377
|
+
await uploadWavefrontBuffer(device, meshIndexBuffer, config.gpuMeshSource.indices.buffer);
|
|
378
|
+
await uploadWavefrontBuffer(device, meshRangeBuffer, config.gpuMeshSource.meshes.buffer);
|
|
362
379
|
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
363
380
|
config.environmentPortals,
|
|
364
381
|
Math.max(1, config.environmentPortalCapacity)
|
|
@@ -501,7 +518,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
501
518
|
},
|
|
502
519
|
computePipelines: pipelines,
|
|
503
520
|
presentPipeline,
|
|
504
|
-
} = await createWavefrontPipelineResources({ device, constants, format });
|
|
521
|
+
} = await createWavefrontPipelineResources({ device, constants, format, instanced:!!config.instancing });
|
|
505
522
|
|
|
506
523
|
function createTraceBindGroups() {
|
|
507
524
|
return createWavefrontTraceBindGroups({
|
|
@@ -516,6 +533,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
516
533
|
counterBuffer,
|
|
517
534
|
outputView,
|
|
518
535
|
triangleBuffer,
|
|
536
|
+
instanceView,
|
|
519
537
|
bvhNodeBuffer,
|
|
520
538
|
radianceView,
|
|
521
539
|
environmentPortalBuffer,
|
|
@@ -1058,6 +1076,8 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
1058
1076
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
1059
1077
|
sceneObjectCount: config.sceneObjectCount,
|
|
1060
1078
|
triangleCount: config.triangleCount,
|
|
1079
|
+
instanceCount: config.instancing?.instanceCount ?? 0,
|
|
1080
|
+
geometryCount: config.instancing?.geometryCount ?? config.gpuMeshSource.meshes.count,
|
|
1061
1081
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
1062
1082
|
environmentPortalCount: config.environmentPortalCount,
|
|
1063
1083
|
environmentPortalMode: config.environmentPortalMode,
|
|
@@ -1076,6 +1096,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
1076
1096
|
frameConfigSlots: frameConfigSlotCount,
|
|
1077
1097
|
gpuParallelism: lastGpuParallelism,
|
|
1078
1098
|
memory: config.memory,
|
|
1099
|
+
allocations: allocationTracker.snapshot(),
|
|
1079
1100
|
});
|
|
1080
1101
|
}
|
|
1081
1102
|
|
|
@@ -1089,6 +1110,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
1089
1110
|
pathVertexBuffer.destroy?.();
|
|
1090
1111
|
sceneObjectBuffer.destroy?.();
|
|
1091
1112
|
triangleBuffer.destroy?.();
|
|
1113
|
+
instanceTexture?.destroy?.();
|
|
1092
1114
|
bvhNodeBuffer.destroy?.();
|
|
1093
1115
|
meshVertexBuffer.destroy?.();
|
|
1094
1116
|
meshIndexBuffer.destroy?.();
|
|
@@ -1133,12 +1155,14 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
1133
1155
|
}
|
|
1134
1156
|
});
|
|
1135
1157
|
context.unconfigure?.();
|
|
1158
|
+
allocationTracker.destroy();
|
|
1136
1159
|
}
|
|
1137
1160
|
|
|
1161
|
+
await finishAllocationScopes();
|
|
1138
1162
|
return Object.freeze({
|
|
1139
1163
|
canvas,
|
|
1140
1164
|
context,
|
|
1141
|
-
device,
|
|
1165
|
+
device: rawDevice,
|
|
1142
1166
|
format,
|
|
1143
1167
|
config,
|
|
1144
1168
|
renderOnce,
|
|
@@ -1150,6 +1174,13 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
1150
1174
|
getSnapshot,
|
|
1151
1175
|
destroy,
|
|
1152
1176
|
});
|
|
1177
|
+
} catch(error) {
|
|
1178
|
+
try { await finishAllocationScopes?.(); } catch { /* Preserve the initiating failure. */ }
|
|
1179
|
+
context?.unconfigure?.();
|
|
1180
|
+
allocationTracker.destroy();
|
|
1181
|
+
rawDevice.destroy?.();
|
|
1182
|
+
throw error;
|
|
1183
|
+
}
|
|
1153
1184
|
}
|
|
1154
1185
|
|
|
1155
1186
|
export async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
package/src/wavefront-config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareWavefrontInstances, buildWavefrontInstances } from './wavefront-instances.js';
|
|
1
2
|
import {
|
|
2
3
|
ACCUMULATION_RECORD_BYTES,
|
|
3
4
|
BVH_LEAF_REF_RECORD_BYTES,
|
|
@@ -357,6 +358,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
357
358
|
const progressiveBytes = options.progressive === true ? width * height * 16 + 16 : 0;
|
|
358
359
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
359
360
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
361
|
+
const instanceBytes = readNonNegativeInteger("instanceBytes", options.instanceBytes, 0);
|
|
360
362
|
const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
|
|
361
363
|
const materialTableBytes = 0;
|
|
362
364
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -431,6 +433,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
431
433
|
pathVertexBytes,
|
|
432
434
|
sceneObjectBytes,
|
|
433
435
|
triangleBytes,
|
|
436
|
+
instanceBytes,
|
|
434
437
|
materialTableBytes,
|
|
435
438
|
bvhNodeBytes,
|
|
436
439
|
bvhCombinedBytes,
|
|
@@ -461,6 +464,7 @@ export function estimateWavefrontPathTracingMemoryForConfig(config, limits = {})
|
|
|
461
464
|
maxDepth: config.maxDepth,
|
|
462
465
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
463
466
|
triangleCapacity: config.triangleCapacity,
|
|
467
|
+
instanceBytes: config.instancing?.instanceBytes ?? 0,
|
|
464
468
|
bvhNodeCapacity: config.bvhNodeCapacity,
|
|
465
469
|
bvhLeafSortCapacity: config.bvhLeafSortCapacity,
|
|
466
470
|
emissiveTriangleCapacity: config.emissiveTriangleCapacity,
|
|
@@ -482,6 +486,7 @@ export function estimateWavefrontPathTracingMemoryForConfig(config, limits = {})
|
|
|
482
486
|
export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
483
487
|
assertAnalyticDisplayQualityPolicy(options);
|
|
484
488
|
const accelerationBuildMode = resolveAccelerationBuildMode(options);
|
|
489
|
+
if(options.instances !== undefined && accelerationBuildMode === "gpu") throw new Error("Instance BLAS construction currently requires cpu-upload acceleration.");
|
|
485
490
|
const canvas = options.canvas;
|
|
486
491
|
const width = readPositiveInteger("width", options.width, getCanvasDimension(canvas, "width", DEFAULT_WIDTH));
|
|
487
492
|
const height = readPositiveInteger("height", options.height, getCanvasDimension(canvas, "height", DEFAULT_HEIGHT));
|
|
@@ -512,6 +517,12 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
512
517
|
);
|
|
513
518
|
const meshes = normalizeMeshes(options);
|
|
514
519
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
520
|
+
if(options.memoryBudgetBytes !== undefined) {
|
|
521
|
+
if(!Number.isSafeInteger(options.memoryBudgetBytes)||options.memoryBudgetBytes<=0) throw new Error("GPU memory budget must be a positive safe integer.");
|
|
522
|
+
const geometryMinimumBytes = meshSourceShape.triangleCount * TRIANGLE_RECORD_BYTES + (options.instances?.length ?? 0) * 144;
|
|
523
|
+
if(!Number.isSafeInteger(geometryMinimumBytes)||geometryMinimumBytes>options.memoryBudgetBytes) throw new Error("Unique geometry exceeds the GPU memory budget before acceleration construction.");
|
|
524
|
+
}
|
|
525
|
+
const instancePlan = options.instances === undefined ? null : prepareWavefrontInstances(meshes, options.instances);
|
|
515
526
|
const gpuMaterialSource =
|
|
516
527
|
meshes.length > 0
|
|
517
528
|
? createWavefrontGpuMaterialSource(meshes)
|
|
@@ -520,10 +531,11 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
520
531
|
meshes.length > 0
|
|
521
532
|
? createWavefrontGpuMeshSource(meshes, gpuMaterialSource)
|
|
522
533
|
: createWavefrontGpuMeshSource([]);
|
|
523
|
-
const
|
|
534
|
+
const instancing = instancePlan ? buildWavefrontInstances(meshes, instancePlan, gpuMaterialSource) : null;
|
|
535
|
+
const meshAcceleration = instancing?.acceleration ?? (
|
|
524
536
|
accelerationBuildMode === "cpu-upload"
|
|
525
537
|
? createWavefrontMeshAcceleration(meshes, gpuMaterialSource)
|
|
526
|
-
: Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
538
|
+
: Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) }));
|
|
527
539
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
528
540
|
meshes,
|
|
529
541
|
options.emissiveTriangleCapacity
|
|
@@ -625,6 +637,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
625
637
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
626
638
|
gpuMeshSource,
|
|
627
639
|
gpuMaterialSource,
|
|
640
|
+
instancing,
|
|
628
641
|
meshAcceleration,
|
|
629
642
|
emissiveTriangleIndices,
|
|
630
643
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -663,6 +676,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
663
676
|
tilePixelCapacity,
|
|
664
677
|
maxDepth,
|
|
665
678
|
sceneObjectCapacity,
|
|
679
|
+
instanceBytes: instancing?.instanceBytes ?? 0,
|
|
666
680
|
triangleCapacity,
|
|
667
681
|
bvhNodeCapacity,
|
|
668
682
|
bvhLeafSortCapacity,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const WAVEFRONT_INSTANCE_SHADER_WGSL = `
|
|
2
|
+
struct MeshInstance {
|
|
3
|
+
world: mat4x4<f32>,
|
|
4
|
+
inverse: mat4x4<f32>,
|
|
5
|
+
objectId: u32,
|
|
6
|
+
root: u32,
|
|
7
|
+
handedness: f32,
|
|
8
|
+
_pad: u32,
|
|
9
|
+
};
|
|
10
|
+
@group(0) @binding(47) var meshInstances: texture_2d<u32>;
|
|
11
|
+
fn read_mesh_instance(index: u32) -> MeshInstance {
|
|
12
|
+
let y = i32(index);
|
|
13
|
+
let metadata = textureLoad(meshInstances, vec2<i32>(8, y), 0);
|
|
14
|
+
return MeshInstance(
|
|
15
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(0, y), 0)),
|
|
16
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(1, y), 0)),
|
|
17
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(2, y), 0)),
|
|
18
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(3, y), 0))),
|
|
19
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(4, y), 0)),
|
|
20
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(5, y), 0)),
|
|
21
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(6, y), 0)),
|
|
22
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(7, y), 0))),
|
|
23
|
+
metadata.x, metadata.y, bitcast<f32>(metadata.z), 0u);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn instance_normal(instance: MeshInstance, normal: vec3<f32>) -> vec3<f32> {
|
|
27
|
+
let normalMatrix = transpose(mat3x3<f32>(instance.inverse[0].xyz, instance.inverse[1].xyz, instance.inverse[2].xyz));
|
|
28
|
+
return safe_normalize(normalMatrix * normal, vec3<f32>(0.0, 1.0, 0.0));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
32
|
+
var triangle = triangles[candidate.triangleIndex];
|
|
33
|
+
let instance = read_mesh_instance(candidate.instanceIndex);
|
|
34
|
+
triangle.v0 = instance.world * vec4<f32>(triangle.v0.xyz, 1.0);
|
|
35
|
+
triangle.v1 = instance.world * vec4<f32>(triangle.v1.xyz, 1.0);
|
|
36
|
+
triangle.v2 = instance.world * vec4<f32>(triangle.v2.xyz, 1.0);
|
|
37
|
+
triangle.meshId = instance.objectId;
|
|
38
|
+
return triangle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
42
|
+
var nearest = initialNearest;
|
|
43
|
+
var best = no_candidate();
|
|
44
|
+
var stack = array<u32, 64>();
|
|
45
|
+
var stackSize = 1u;
|
|
46
|
+
stack[0] = 0u;
|
|
47
|
+
loop {
|
|
48
|
+
if (stackSize == 0u) { break; }
|
|
49
|
+
stackSize -= 1u;
|
|
50
|
+
let node = bvhNodes[stack[stackSize]];
|
|
51
|
+
if (!intersect_bounds(ray, node.boundsMin.xyz, node.boundsMax.xyz, nearest)) { continue; }
|
|
52
|
+
if (node.triangleCount == 0u) {
|
|
53
|
+
stack[stackSize] = node.childOrFirst;
|
|
54
|
+
stack[stackSize + 1u] = node.rightChild;
|
|
55
|
+
stackSize += 2u;
|
|
56
|
+
} else {
|
|
57
|
+
let instanceIndex = node.childOrFirst;
|
|
58
|
+
let instance = read_mesh_instance(instanceIndex);
|
|
59
|
+
var localRay = ray;
|
|
60
|
+
localRay.origin = instance.inverse * vec4<f32>(ray.origin.xyz, 1.0);
|
|
61
|
+
// Keep this direction unnormalized so t remains the world-space ray parameter.
|
|
62
|
+
localRay.direction = instance.inverse * vec4<f32>(ray.direction.xyz, 0.0);
|
|
63
|
+
var candidate = intersect_geometry_bvh(localRay, nearest, instance.root);
|
|
64
|
+
if (candidate.hit == 1u && candidate.distance < nearest) {
|
|
65
|
+
candidate.geometricNormal = instance_normal(instance, candidate.geometricNormal);
|
|
66
|
+
candidate.shadingNormal = repair_shading_normal(candidate.geometricNormal,
|
|
67
|
+
instance_normal(instance, candidate.shadingNormal));
|
|
68
|
+
if (instance.handedness < 0.0) { candidate.frontFace = 1u - candidate.frontFace; }
|
|
69
|
+
candidate.instanceIndex = instanceIndex;
|
|
70
|
+
nearest = candidate.distance;
|
|
71
|
+
best = candidate;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return best;
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
export const WAVEFRONT_UNINSTANCED_SHADER_WGSL = `
|
|
80
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
81
|
+
return triangles[candidate.triangleIndex];
|
|
82
|
+
}
|
|
83
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
84
|
+
return intersect_geometry_bvh(ray, initialNearest, 0u);
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { buildWavefrontBoundsBvh, createWavefrontMeshAcceleration } from './wavefront-mesh-sources.js';
|
|
2
|
+
import { emissionPower, MATERIAL_EMISSIVE } from './wavefront-core.js';
|
|
3
|
+
|
|
4
|
+
export const WAVEFRONT_INSTANCE_BYTES = 144;
|
|
5
|
+
const freeze = Object.freeze;
|
|
6
|
+
const uint = value => Number.isInteger(value) && value >= 0 && value < 0xffffffff;
|
|
7
|
+
|
|
8
|
+
function affineTransform(value) {
|
|
9
|
+
if ((!Array.isArray(value) && !ArrayBuffer.isView(value)) || value.length !== 16 ||
|
|
10
|
+
!Array.from(value).every(n => Number.isFinite(n) && Math.abs(n) <= 1e12) ||
|
|
11
|
+
value[3] !== 0 || value[7] !== 0 || value[11] !== 0 || value[15] !== 1) {
|
|
12
|
+
throw new Error('Instance transform must be a finite affine column-major matrix.');
|
|
13
|
+
}
|
|
14
|
+
// Round to the actual shader representation before inversion and bounds construction.
|
|
15
|
+
const m = Array.from(value, Math.fround);
|
|
16
|
+
const [a,b,c,,d,e,f,,g,h,i] = m;
|
|
17
|
+
const determinant = a*(e*i-f*h)-d*(b*i-c*h)+g*(b*f-c*e);
|
|
18
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-12) {
|
|
19
|
+
throw new Error('Instance transform must be invertible.');
|
|
20
|
+
}
|
|
21
|
+
const inv = [(e*i-f*h)/determinant,(c*h-b*i)/determinant,(b*f-c*e)/determinant,0,
|
|
22
|
+
(f*g-d*i)/determinant,(a*i-c*g)/determinant,(c*d-a*f)/determinant,0,
|
|
23
|
+
(d*h-e*g)/determinant,(b*g-a*h)/determinant,(a*e-b*d)/determinant,0,0,0,0,1];
|
|
24
|
+
for (let r=0;r<3;r++) inv[12+r]=-(inv[r]*m[12]+inv[4+r]*m[13]+inv[8+r]*m[14]);
|
|
25
|
+
if (!inv.every(n=>Number.isFinite(n) && Math.abs(n)<=1e12)) throw new Error('Instance inverse transform is out of range.');
|
|
26
|
+
return {transform:freeze(m),inverse:freeze(inv.map(n=>n===0?0:n)),handedness:Math.sign(determinant)};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function transformInstancePoint(m, p) {
|
|
30
|
+
return [0,1,2].map(r=>m[r]*p[0]+m[4+r]*p[1]+m[8+r]*p[2]+m[12+r]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function meshBounds(mesh) {
|
|
34
|
+
const localMin=[Infinity,Infinity,Infinity],localMax=[-Infinity,-Infinity,-Infinity];
|
|
35
|
+
for(let i=0;i<mesh.positions.length;i+=3) for(let a=0;a<3;a++) {
|
|
36
|
+
localMin[a]=Math.min(localMin[a],Math.fround(mesh.positions[i+a]));localMax[a]=Math.max(localMax[a],Math.fround(mesh.positions[i+a]));
|
|
37
|
+
}
|
|
38
|
+
return {min:localMin,max:localMax};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function boundsOf(local, transform) {
|
|
42
|
+
const localMin=local.min,localMax=local.max;
|
|
43
|
+
const min=[Infinity,Infinity,Infinity],max=[-Infinity,-Infinity,-Infinity];
|
|
44
|
+
for(let corner=0;corner<8;corner++) {
|
|
45
|
+
const p=transformInstancePoint(transform,[0,1,2].map(a=>(corner&(1<<a))?localMax[a]:localMin[a]));
|
|
46
|
+
for(let a=0;a<3;a++){min[a]=Math.min(min[a],p[a]);max[a]=Math.max(max[a],p[a]);}
|
|
47
|
+
}
|
|
48
|
+
if(![...min,...max].every(Number.isFinite)) throw new Error('Instance mesh has invalid bounds.');
|
|
49
|
+
return freeze({min:freeze(min),max:freeze(max)});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function prepareWavefrontInstances(meshes, instances) {
|
|
53
|
+
if(!Array.isArray(instances)||instances.length===0) throw new Error('Instances must be a nonempty array.');
|
|
54
|
+
const byId=new Map(),localBounds=new Map();
|
|
55
|
+
for(const mesh of meshes) {
|
|
56
|
+
if(!uint(mesh.id)||byId.has(mesh.id)) throw new Error('Instanced mesh IDs must be unique unsigned integers.');
|
|
57
|
+
if(mesh.materialKind===MATERIAL_EMISSIVE||emissionPower(mesh.emission??[0,0,0,1])>0.0001) {
|
|
58
|
+
throw new Error('Instanced emissive meshes require per-instance light sampling, which is not supported yet.');
|
|
59
|
+
}
|
|
60
|
+
byId.set(mesh.id,mesh);localBounds.set(mesh.id,meshBounds(mesh));
|
|
61
|
+
}
|
|
62
|
+
const ids=new Set(),used=new Set();
|
|
63
|
+
const records=instances.map(instance=>{
|
|
64
|
+
if(!instance||!uint(instance.id)||ids.has(instance.id)) throw new Error('Instance IDs must be unique unsigned integers.');
|
|
65
|
+
const mesh=byId.get(instance.meshId);
|
|
66
|
+
if(!mesh) throw new Error('Instance references an unknown mesh.');
|
|
67
|
+
ids.add(instance.id);used.add(mesh.id);
|
|
68
|
+
const affine=affineTransform(instance.transform);
|
|
69
|
+
const bounds=boundsOf(localBounds.get(mesh.id),affine.transform);
|
|
70
|
+
return freeze({...affine,id:instance.id,meshId:mesh.id,bounds,
|
|
71
|
+
centroid:freeze(bounds.min.map((v,a)=>(v+bounds.max[a])/2))});
|
|
72
|
+
});
|
|
73
|
+
if(used.size!==meshes.length) throw new Error('Every instanced mesh must have at least one instance.');
|
|
74
|
+
return freeze({records:freeze(records),geometryCount:meshes.length});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildWavefrontInstances(meshes, plan, materialSource) {
|
|
78
|
+
const tlas=buildWavefrontBoundsBvh(plan.records,1);
|
|
79
|
+
// Outward padding covers f32 matrix multiplication and packed-bound rounding.
|
|
80
|
+
const nodes=tlas.nodes.map(node=>{
|
|
81
|
+
const pad=node.bounds.min.map((v,a)=>Math.max(1,Math.abs(v),Math.abs(node.bounds.max[a]))*1e-6);
|
|
82
|
+
return freeze({...node,bounds:freeze({min:freeze(node.bounds.min.map((v,a)=>v-pad[a])),
|
|
83
|
+
max:freeze(node.bounds.max.map((v,a)=>v+pad[a]))})});
|
|
84
|
+
}),triangles=[],roots=new Map();
|
|
85
|
+
for(const [meshIndex,mesh] of meshes.entries()) {
|
|
86
|
+
const blas=createWavefrontMeshAcceleration([mesh],materialSource);
|
|
87
|
+
const nodeOffset=nodes.length,triangleOffset=triangles.length;
|
|
88
|
+
roots.set(mesh.id,nodeOffset);
|
|
89
|
+
for(const node of blas.nodes) nodes.push(freeze({...node,
|
|
90
|
+
firstTriangle:node.firstTriangle+(node.triangleCount>0?triangleOffset:nodeOffset),
|
|
91
|
+
leftChild:node.leftChild+nodeOffset,rightChild:node.rightChild+nodeOffset}));
|
|
92
|
+
for(const triangle of blas.triangles) triangles.push(freeze({...triangle,
|
|
93
|
+
triangleId:triangle.triangleId+triangleOffset,materialSlot:meshIndex}));
|
|
94
|
+
}
|
|
95
|
+
const records=tlas.triangles.map(record=>freeze({...record,root:roots.get(record.meshId)}));
|
|
96
|
+
const buffer=new ArrayBuffer(records.length*WAVEFRONT_INSTANCE_BYTES);
|
|
97
|
+
const floats=new Float32Array(buffer),uints=new Uint32Array(buffer);
|
|
98
|
+
records.forEach((record,index)=>{
|
|
99
|
+
const offset=index*36;
|
|
100
|
+
floats.set(record.transform,offset);floats.set(record.inverse,offset+16);
|
|
101
|
+
uints[offset+32]=record.id;uints[offset+33]=record.root;floats[offset+34]=record.handedness;
|
|
102
|
+
});
|
|
103
|
+
return freeze({records:freeze(records),buffer,instanceCount:records.length,
|
|
104
|
+
geometryCount:meshes.length,instanceBytes:buffer.byteLength,tlasNodeCount:tlas.nodes.length,
|
|
105
|
+
blasNodeCount:nodes.length-tlas.nodes.length,
|
|
106
|
+
acceleration:freeze({nodes:freeze(nodes),triangles:freeze(triangles)})});
|
|
107
|
+
}
|
|
@@ -159,7 +159,7 @@ function chooseSplitAxis(triangles) {
|
|
|
159
159
|
return extent[1] >= extent[2] ? 1 : 2;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function
|
|
162
|
+
export function buildWavefrontBoundsBvh(triangles, maxLeafTriangles = 4) {
|
|
163
163
|
if (triangles.length === 0) {
|
|
164
164
|
return Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
165
165
|
}
|
|
@@ -217,7 +217,7 @@ export function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource =
|
|
|
217
217
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
218
218
|
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
219
219
|
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
220
|
-
return
|
|
220
|
+
return buildWavefrontBoundsBvh(triangles);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
export function estimateMeshSourceShape(meshes) {
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
createComputePipeline,
|
|
4
4
|
createRenderPipeline,
|
|
5
5
|
} from "./wavefront-runtime-support.js";
|
|
6
|
-
import { PRESENT_WGSL,
|
|
6
|
+
import { PRESENT_WGSL, createWavefrontPathTracingComputeShaderSource } from "./wavefront-shaders.js";
|
|
7
7
|
import { CONFIG_BUFFER_BYTES } from "./wavefront-core.js";
|
|
8
8
|
|
|
9
|
-
export function createWavefrontBindGroupLayouts(device, constants) {
|
|
9
|
+
export function createWavefrontBindGroupLayouts(device, constants, instanced = false) {
|
|
10
10
|
const trace = device.createBindGroupLayout({
|
|
11
11
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
12
12
|
entries: [
|
|
@@ -59,6 +59,7 @@ export function createWavefrontBindGroupLayouts(device, constants) {
|
|
|
59
59
|
{ binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
60
60
|
{ binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
61
61
|
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
62
|
+
...(instanced ? [{binding:47, visibility:constants.shader.COMPUTE, texture:{sampleType:"uint"}}] : []),
|
|
62
63
|
],
|
|
63
64
|
});
|
|
64
65
|
const acceleration = device.createBindGroupLayout({
|
|
@@ -258,12 +259,12 @@ export async function createWavefrontPresentPipeline(device, presentBindGroupLay
|
|
|
258
259
|
});
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
export async function createWavefrontPipelineResources({ device, constants, format }) {
|
|
262
|
-
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants);
|
|
262
|
+
export async function createWavefrontPipelineResources({ device, constants, format, instanced = false }) {
|
|
263
|
+
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants, instanced);
|
|
263
264
|
const pipelineLayouts = createWavefrontPipelineLayouts(device, bindGroupLayouts);
|
|
264
265
|
const computeShader = device.createShaderModule({
|
|
265
266
|
label: "plasius.wavefront.computeShader",
|
|
266
|
-
code:
|
|
267
|
+
code: createWavefrontPathTracingComputeShaderSource({instanced}),
|
|
267
268
|
});
|
|
268
269
|
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
269
270
|
|