@plasius/gpu-renderer 0.2.55 → 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 +32 -0
- package/README.md +51 -0
- package/dist/index.cjs +2259 -1819
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2259 -1819
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/canonical-wavefront-meshes.js +8 -9
- 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/dist/index.cjs
CHANGED
|
@@ -1654,14 +1654,6 @@ function textureSample(binding, textures, label, extra = {}) {
|
|
|
1654
1654
|
}
|
|
1655
1655
|
function cloneMaterialExtensions(material, textures) {
|
|
1656
1656
|
const extensions = { ...material.extensions ?? {} };
|
|
1657
|
-
if (material.workflow === "unlit") {
|
|
1658
|
-
const unlitKey = "KHR_materials_unlit";
|
|
1659
|
-
const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
|
|
1660
|
-
if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
|
|
1661
|
-
throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
|
|
1662
|
-
}
|
|
1663
|
-
extensions[unlitKey] = { ...unlitValue, enabled: true };
|
|
1664
|
-
}
|
|
1665
1657
|
for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
|
|
1666
1658
|
const binding = material.textures?.[bindingName];
|
|
1667
1659
|
if (!binding) {
|
|
@@ -1686,7 +1678,10 @@ function mapMaterial(material, materialRefId, textures) {
|
|
|
1686
1678
|
if (material.alphaMode === "mask") {
|
|
1687
1679
|
throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
|
|
1688
1680
|
}
|
|
1689
|
-
if (material.workflow
|
|
1681
|
+
if (material.workflow === "unlit" || Object.hasOwn(material.extensions ?? {}, "KHR_materials_unlit") || Object.hasOwn(material.extensions ?? {}, "unlit")) {
|
|
1682
|
+
throw new Error("Canonical unlit materials are unsupported by WavefrontMeshInput; retain an unlit-capable rendering path.");
|
|
1683
|
+
}
|
|
1684
|
+
if (material.workflow !== "metallic-roughness") {
|
|
1690
1685
|
throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
|
|
1691
1686
|
}
|
|
1692
1687
|
const mapped = {
|
|
@@ -4063,948 +4058,1289 @@ function boundsCentroid(bounds) {
|
|
|
4063
4058
|
];
|
|
4064
4059
|
}
|
|
4065
4060
|
|
|
4066
|
-
// src/wavefront-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
const
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
),
|
|
4080
|
-
normal
|
|
4081
|
-
);
|
|
4082
|
-
}
|
|
4083
|
-
function radicalInverseVdc(bits) {
|
|
4084
|
-
let value = bits >>> 0;
|
|
4085
|
-
value = (value << 16 | value >>> 16) >>> 0;
|
|
4086
|
-
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
4087
|
-
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
4088
|
-
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
4089
|
-
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
4090
|
-
return value * 23283064365386963e-26;
|
|
4091
|
-
}
|
|
4092
|
-
function hammersley(index, count) {
|
|
4093
|
-
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
4094
|
-
}
|
|
4095
|
-
function importanceSampleGgx(sample, roughness, normal) {
|
|
4096
|
-
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
4097
|
-
const phi = 2 * Math.PI * sample[0];
|
|
4098
|
-
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
4099
|
-
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
4100
|
-
const halfVector = localToWorld(
|
|
4101
|
-
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
4102
|
-
normal
|
|
4103
|
-
);
|
|
4104
|
-
return normalize(halfVector, normal);
|
|
4105
|
-
}
|
|
4106
|
-
function geometrySchlickGgx(nDotV, roughness) {
|
|
4107
|
-
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
4108
|
-
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
4109
|
-
}
|
|
4110
|
-
function geometrySmith(nDotV, nDotL, roughness) {
|
|
4111
|
-
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
4061
|
+
// src/wavefront-runtime-support.js
|
|
4062
|
+
var WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 128 * 1024 * 1024;
|
|
4063
|
+
var WEBGPU_DEFAULT_MAX_BUFFER_SIZE = 256 * 1024 * 1024;
|
|
4064
|
+
function readRequestedDeviceLimit(source, name) {
|
|
4065
|
+
const rawValue = source?.[name];
|
|
4066
|
+
if (rawValue === void 0 || rawValue === null) {
|
|
4067
|
+
return 0;
|
|
4068
|
+
}
|
|
4069
|
+
const value = Number(rawValue);
|
|
4070
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4071
|
+
throw new Error(`${name} must be a non-negative safe integer.`);
|
|
4072
|
+
}
|
|
4073
|
+
return value;
|
|
4112
4074
|
}
|
|
4113
|
-
function
|
|
4114
|
-
const
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
4124
|
-
normal
|
|
4075
|
+
function assertAdapterSupportsRequiredLimit(adapter, name, requiredValue) {
|
|
4076
|
+
const availableValue = Number(adapter?.limits?.[name]);
|
|
4077
|
+
if (!Number.isFinite(availableValue)) {
|
|
4078
|
+
throw new Error(
|
|
4079
|
+
`Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter does not expose that limit.`
|
|
4080
|
+
);
|
|
4081
|
+
}
|
|
4082
|
+
if (availableValue < requiredValue) {
|
|
4083
|
+
throw new Error(
|
|
4084
|
+
`Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter exposes ${availableValue} bytes.`
|
|
4125
4085
|
);
|
|
4126
|
-
const nDotL = Math.max(lightDirection[2], 0);
|
|
4127
|
-
const nDotH = Math.max(halfVector[2], 0);
|
|
4128
|
-
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
4129
|
-
continue;
|
|
4130
|
-
}
|
|
4131
|
-
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
4132
|
-
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
4133
|
-
const fresnel2 = (1 - vDotH) ** 5;
|
|
4134
|
-
scaleTerm += (1 - fresnel2) * visibility;
|
|
4135
|
-
biasTerm += fresnel2 * visibility;
|
|
4136
4086
|
}
|
|
4137
|
-
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4138
4087
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
4143
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4144
|
-
return config.tileSize;
|
|
4088
|
+
function getGpuUsageConstants() {
|
|
4089
|
+
if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
|
|
4090
|
+
throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
|
|
4145
4091
|
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
const maxPixelsByPathVertex = Math.floor(
|
|
4149
|
-
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
4150
|
-
);
|
|
4151
|
-
const maxPixels = Math.max(
|
|
4152
|
-
256,
|
|
4153
|
-
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
4154
|
-
);
|
|
4155
|
-
if (config.tilePixelCapacity <= maxPixels) {
|
|
4156
|
-
return config.tileSize;
|
|
4092
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
4093
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
4157
4094
|
}
|
|
4158
|
-
return
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
usage
|
|
4165
|
-
});
|
|
4166
|
-
}
|
|
4167
|
-
function alignTo(value, alignment) {
|
|
4168
|
-
const resolvedAlignment = Math.max(1, alignment);
|
|
4169
|
-
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
4095
|
+
return {
|
|
4096
|
+
buffer: GPUBufferUsage,
|
|
4097
|
+
texture: GPUTextureUsage,
|
|
4098
|
+
shader: GPUShaderStage,
|
|
4099
|
+
map: typeof GPUMapMode === "undefined" ? null : GPUMapMode
|
|
4100
|
+
};
|
|
4170
4101
|
}
|
|
4171
|
-
function
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
let exponent = x >> 23 & 255;
|
|
4179
|
-
if (exponent === 255) {
|
|
4180
|
-
return sign | (mantissa ? 32256 : 31744);
|
|
4102
|
+
function resolveCanvas3(canvasOrSelector, documentRef = globalThis.document) {
|
|
4103
|
+
if (typeof canvasOrSelector === "string") {
|
|
4104
|
+
const resolved = documentRef?.querySelector?.(canvasOrSelector);
|
|
4105
|
+
if (!resolved) {
|
|
4106
|
+
throw new Error(`Unable to find canvas for selector: ${canvasOrSelector}`);
|
|
4107
|
+
}
|
|
4108
|
+
return resolved;
|
|
4181
4109
|
}
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
return sign | 31744;
|
|
4110
|
+
if (canvasOrSelector?.getContext) {
|
|
4111
|
+
return canvasOrSelector;
|
|
4185
4112
|
}
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
}
|
|
4190
|
-
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4191
|
-
return sign | mantissa + 4096 >> 13;
|
|
4113
|
+
const fallback = documentRef?.querySelector?.("canvas[data-plasius-wavefront-path-tracing]");
|
|
4114
|
+
if (!fallback) {
|
|
4115
|
+
throw new Error("A canvas is required for WebGPU wavefront path tracing.");
|
|
4192
4116
|
}
|
|
4193
|
-
return
|
|
4117
|
+
return fallback;
|
|
4194
4118
|
}
|
|
4195
|
-
function
|
|
4196
|
-
if (
|
|
4197
|
-
return
|
|
4119
|
+
async function getPipelineDiagnostics(shaderModule) {
|
|
4120
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4121
|
+
return "";
|
|
4198
4122
|
}
|
|
4199
|
-
|
|
4200
|
-
|
|
4123
|
+
try {
|
|
4124
|
+
const info = await shaderModule.getCompilationInfo();
|
|
4125
|
+
const messages = info.messages ?? [];
|
|
4126
|
+
if (messages.length === 0) {
|
|
4127
|
+
return "";
|
|
4128
|
+
}
|
|
4129
|
+
return messages.map((message) => {
|
|
4130
|
+
const line = message.lineNum ?? "?";
|
|
4131
|
+
const column = message.linePos ?? "?";
|
|
4132
|
+
return `line ${line}:${column} ${message.message}`;
|
|
4133
|
+
}).join("\n");
|
|
4134
|
+
} catch {
|
|
4135
|
+
return "";
|
|
4201
4136
|
}
|
|
4202
|
-
return 1;
|
|
4203
4137
|
}
|
|
4204
|
-
function
|
|
4205
|
-
|
|
4206
|
-
|
|
4138
|
+
async function createComputePipeline(device, shaderModule, layout, entryPoint, label) {
|
|
4139
|
+
const descriptor = {
|
|
4140
|
+
label,
|
|
4141
|
+
layout,
|
|
4142
|
+
compute: {
|
|
4143
|
+
module: shaderModule,
|
|
4144
|
+
entryPoint
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
4147
|
+
try {
|
|
4148
|
+
if (typeof device.createComputePipelineAsync === "function") {
|
|
4149
|
+
return await device.createComputePipelineAsync(descriptor);
|
|
4150
|
+
}
|
|
4151
|
+
return device.createComputePipeline(descriptor);
|
|
4152
|
+
} catch (error) {
|
|
4153
|
+
const diagnostics = await getPipelineDiagnostics(shaderModule);
|
|
4154
|
+
const suffix = diagnostics ? `
|
|
4155
|
+
${diagnostics}` : "";
|
|
4156
|
+
throw new Error(`WGSL compilation failed for ${label}: ${error.message}${suffix}`, {
|
|
4157
|
+
cause: error
|
|
4158
|
+
});
|
|
4207
4159
|
}
|
|
4208
|
-
const width = Math.max(1, environmentMap.width ?? 1);
|
|
4209
|
-
const height = Math.max(1, environmentMap.height ?? 1);
|
|
4210
|
-
return environmentMap.data.length >= width * height * 4;
|
|
4211
4160
|
}
|
|
4212
|
-
function
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
const bytesPerRow = alignTo(width * 4, 256);
|
|
4216
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4217
|
-
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
4218
|
-
for (let y = 0; y < height; y += 1) {
|
|
4219
|
-
const sourceOffset = y * width * 4;
|
|
4220
|
-
const targetOffset = y * bytesPerRow;
|
|
4221
|
-
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
4161
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
4162
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4163
|
+
return;
|
|
4222
4164
|
}
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
});
|
|
4229
|
-
}
|
|
4230
|
-
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
4231
|
-
if (!data || index >= data.length) {
|
|
4232
|
-
return fallback;
|
|
4165
|
+
const info = await shaderModule.getCompilationInfo();
|
|
4166
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
4167
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
4168
|
+
if (errors.length <= 0) {
|
|
4169
|
+
return;
|
|
4233
4170
|
}
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4171
|
+
const diagnostics = errors.map((message) => {
|
|
4172
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
4173
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
4174
|
+
return `line ${line}:${column} ${message.message}`;
|
|
4175
|
+
}).join("\n");
|
|
4176
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
4177
|
+
${diagnostics}`);
|
|
4236
4178
|
}
|
|
4237
|
-
function
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
if (cached) {
|
|
4241
|
-
return cached;
|
|
4242
|
-
}
|
|
4243
|
-
const width = Math.max(1, Math.trunc(size));
|
|
4244
|
-
const height = Math.max(1, Math.trunc(size));
|
|
4245
|
-
const rowBytes = width * 8;
|
|
4246
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4247
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4248
|
-
const view = new DataView(bytes.buffer);
|
|
4249
|
-
for (let y = 0; y < height; y += 1) {
|
|
4250
|
-
const roughness = (y + 0.5) / height;
|
|
4251
|
-
for (let x = 0; x < width; x += 1) {
|
|
4252
|
-
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
4253
|
-
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
4254
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4255
|
-
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
4256
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
4257
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
4258
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4262
|
-
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4263
|
-
return upload;
|
|
4264
|
-
}
|
|
4265
|
-
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
4266
|
-
const width = Math.max(1, environmentMap.width);
|
|
4267
|
-
const height = Math.max(1, environmentMap.height);
|
|
4268
|
-
const pixels = new Float32Array(width * height * 4);
|
|
4269
|
-
const data = environmentMap.data;
|
|
4270
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
4271
|
-
for (let index = 0; index < width * height; index += 1) {
|
|
4272
|
-
const sourceOffset = index * 4;
|
|
4273
|
-
const targetOffset = index * 4;
|
|
4274
|
-
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
4275
|
-
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
4276
|
-
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
4277
|
-
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
4179
|
+
async function createRenderPipeline(device, descriptor) {
|
|
4180
|
+
if (typeof device.createRenderPipelineAsync === "function") {
|
|
4181
|
+
return device.createRenderPipelineAsync(descriptor);
|
|
4278
4182
|
}
|
|
4279
|
-
return
|
|
4280
|
-
}
|
|
4281
|
-
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
4282
|
-
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
4283
|
-
const theta = v * Math.PI;
|
|
4284
|
-
const sinTheta = Math.sin(theta);
|
|
4285
|
-
return [
|
|
4286
|
-
Math.cos(angle) * sinTheta,
|
|
4287
|
-
Math.cos(theta),
|
|
4288
|
-
Math.sin(angle) * sinTheta
|
|
4289
|
-
];
|
|
4183
|
+
return device.createRenderPipeline(descriptor);
|
|
4290
4184
|
}
|
|
4291
|
-
function
|
|
4292
|
-
const
|
|
4293
|
-
const
|
|
4294
|
-
const
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
const y0 = clamp2(Math.floor(y), 0, height - 1);
|
|
4298
|
-
const x1 = (x0 + 1) % width;
|
|
4299
|
-
const y1 = clamp2(y0 + 1, 0, height - 1);
|
|
4300
|
-
const tx = x - Math.floor(x);
|
|
4301
|
-
const ty = y - Math.floor(y);
|
|
4302
|
-
const read = (px, py) => {
|
|
4303
|
-
const offset = (py * width + px) * 4;
|
|
4304
|
-
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
4185
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}, memory = null) {
|
|
4186
|
+
const descriptor = { ...options.deviceDescriptor ?? {} };
|
|
4187
|
+
const descriptorRequiredLimits = { ...descriptor.requiredLimits ?? {} };
|
|
4188
|
+
const requiredLimits = {
|
|
4189
|
+
...descriptorRequiredLimits,
|
|
4190
|
+
...options.requiredLimits ?? {}
|
|
4305
4191
|
};
|
|
4306
|
-
const
|
|
4307
|
-
const
|
|
4308
|
-
const
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
4315
|
-
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
4316
|
-
];
|
|
4317
|
-
}
|
|
4318
|
-
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
4319
|
-
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
4320
|
-
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
4321
|
-
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
4322
|
-
const v = Math.acos(clamp2(unitDirection[1], -1, 1)) / Math.PI;
|
|
4323
|
-
return [u, clamp2(v, 0, 1)];
|
|
4324
|
-
}
|
|
4325
|
-
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
4326
|
-
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
4327
|
-
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
4328
|
-
}
|
|
4329
|
-
function createFloat16RgbaUploadFromLevels(levels) {
|
|
4330
|
-
return levels.map((level) => {
|
|
4331
|
-
const rowBytes = level.width * 8;
|
|
4332
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4333
|
-
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
4334
|
-
const view = new DataView(bytes.buffer);
|
|
4335
|
-
for (let y = 0; y < level.height; y += 1) {
|
|
4336
|
-
for (let x = 0; x < level.width; x += 1) {
|
|
4337
|
-
const sourceOffset = (y * level.width + x) * 4;
|
|
4338
|
-
const targetOffset = y * bytesPerRow + x * 8;
|
|
4339
|
-
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
4340
|
-
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
4341
|
-
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
4342
|
-
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
4343
|
-
}
|
|
4192
|
+
const traceStorageBindings = TRACE_STORAGE_BUFFER_BINDINGS;
|
|
4193
|
+
const traceTextureBindings = TRACE_SAMPLED_TEXTURE_BINDINGS + (options.instances !== void 0 ? 1 : 0);
|
|
4194
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
4195
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
4196
|
+
if (exposedStorageBufferLimit < traceStorageBindings) {
|
|
4197
|
+
throw new Error(
|
|
4198
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${traceStorageBindings}, but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
4199
|
+
);
|
|
4344
4200
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
const
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
height: sourceHeight,
|
|
4357
|
-
data: sourcePixels
|
|
4358
|
-
})
|
|
4359
|
-
];
|
|
4360
|
-
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
4361
|
-
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
4362
|
-
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
4363
|
-
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
4364
|
-
const data = new Float32Array(width * height * 4);
|
|
4365
|
-
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
4366
|
-
for (let y = 0; y < height; y += 1) {
|
|
4367
|
-
for (let x = 0; x < width; x += 1) {
|
|
4368
|
-
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
4369
|
-
const normal = normalize(direction, [0, 1, 0]);
|
|
4370
|
-
const viewDirection = normal;
|
|
4371
|
-
let totalWeight = 0;
|
|
4372
|
-
const accum = [0, 0, 0];
|
|
4373
|
-
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
4374
|
-
const xi = hammersley(sampleIndex, sampleCount);
|
|
4375
|
-
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4376
|
-
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
4377
|
-
const lightDirection = normalize(
|
|
4378
|
-
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
4379
|
-
normal
|
|
4380
|
-
);
|
|
4381
|
-
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
4382
|
-
if (nDotL <= 1e-6) {
|
|
4383
|
-
continue;
|
|
4384
|
-
}
|
|
4385
|
-
const radiance = sampleEnvironmentRadiance(
|
|
4386
|
-
sourcePixels,
|
|
4387
|
-
sourceWidth,
|
|
4388
|
-
sourceHeight,
|
|
4389
|
-
lightDirection,
|
|
4390
|
-
environmentMap.rotationRadians
|
|
4391
|
-
);
|
|
4392
|
-
accum[0] += radiance[0] * nDotL;
|
|
4393
|
-
accum[1] += radiance[1] * nDotL;
|
|
4394
|
-
accum[2] += radiance[2] * nDotL;
|
|
4395
|
-
totalWeight += nDotL;
|
|
4396
|
-
}
|
|
4397
|
-
const offset = (y * width + x) * 4;
|
|
4398
|
-
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
4399
|
-
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
4400
|
-
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
4401
|
-
data[offset + 3] = 1;
|
|
4402
|
-
}
|
|
4201
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
4202
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
4203
|
+
traceStorageBindings
|
|
4204
|
+
);
|
|
4205
|
+
}
|
|
4206
|
+
const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
|
|
4207
|
+
if (Number.isFinite(exposedSampledTextureLimit)) {
|
|
4208
|
+
if (exposedSampledTextureLimit < traceTextureBindings) {
|
|
4209
|
+
throw new Error(
|
|
4210
|
+
`Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${traceTextureBindings}, but this adapter exposes ${exposedSampledTextureLimit}.`
|
|
4211
|
+
);
|
|
4403
4212
|
}
|
|
4404
|
-
|
|
4213
|
+
requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
|
|
4214
|
+
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4215
|
+
traceTextureBindings
|
|
4216
|
+
);
|
|
4405
4217
|
}
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
if (
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
hasImportanceData: false
|
|
4422
|
-
});
|
|
4218
|
+
const sceneStorageBufferBindingBytes = Number(
|
|
4219
|
+
memory?.sceneStorageBufferBindingBytes ?? 0
|
|
4220
|
+
);
|
|
4221
|
+
const requestedStorageBufferBindingSize = Math.max(
|
|
4222
|
+
readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
|
|
4223
|
+
readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
|
|
4224
|
+
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
|
|
4225
|
+
);
|
|
4226
|
+
if (requestedStorageBufferBindingSize > 0) {
|
|
4227
|
+
assertAdapterSupportsRequiredLimit(
|
|
4228
|
+
adapter,
|
|
4229
|
+
"maxStorageBufferBindingSize",
|
|
4230
|
+
requestedStorageBufferBindingSize
|
|
4231
|
+
);
|
|
4232
|
+
requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
|
|
4423
4233
|
}
|
|
4424
|
-
const
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
const theta = (y + 0.5) / height * Math.PI;
|
|
4434
|
-
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
4435
|
-
let rowWeight = 0;
|
|
4436
|
-
for (let x = 0; x < width; x += 1) {
|
|
4437
|
-
const offset = (y * width + x) * 4;
|
|
4438
|
-
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
4439
|
-
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
4440
|
-
pdf[y * width + x] = weight;
|
|
4441
|
-
rowWeight += weight;
|
|
4442
|
-
conditionalCdf[y * width + x] = rowWeight;
|
|
4443
|
-
}
|
|
4444
|
-
rowSums[y] = rowWeight;
|
|
4445
|
-
totalWeight += rowWeight;
|
|
4446
|
-
if (rowWeight > 0) {
|
|
4447
|
-
for (let x = 0; x < width; x += 1) {
|
|
4448
|
-
conditionalCdf[y * width + x] /= rowWeight;
|
|
4449
|
-
}
|
|
4450
|
-
} else {
|
|
4451
|
-
for (let x = 0; x < width; x += 1) {
|
|
4452
|
-
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
4453
|
-
}
|
|
4454
|
-
}
|
|
4455
|
-
marginalCdf[y] = totalWeight;
|
|
4234
|
+
const requestedMaxBufferSize = Math.max(
|
|
4235
|
+
readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
|
|
4236
|
+
readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
|
|
4237
|
+
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
|
|
4238
|
+
requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
|
|
4239
|
+
);
|
|
4240
|
+
if (requestedMaxBufferSize > 0) {
|
|
4241
|
+
assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
|
|
4242
|
+
requiredLimits.maxBufferSize = requestedMaxBufferSize;
|
|
4456
4243
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4244
|
+
const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
|
|
4245
|
+
const adapterFeatures = adapter?.features;
|
|
4246
|
+
const timestampQuerySupported = typeof adapterFeatures?.has === "function" ? adapterFeatures.has("timestamp-query") : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function" ? Array.from(adapterFeatures).includes("timestamp-query") : false;
|
|
4247
|
+
if (options.gpuTimestamps !== false && timestampQuerySupported && !requiredFeatures.includes("timestamp-query")) {
|
|
4248
|
+
requiredFeatures.push("timestamp-query");
|
|
4459
4249
|
}
|
|
4460
|
-
|
|
4461
|
-
|
|
4250
|
+
if (requiredFeatures.length > 0) {
|
|
4251
|
+
descriptor.requiredFeatures = requiredFeatures;
|
|
4462
4252
|
}
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
conditionalCdf,
|
|
4469
|
-
hasImportanceData: true
|
|
4470
|
-
});
|
|
4471
|
-
}
|
|
4472
|
-
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
4473
|
-
if (environmentMap.view) {
|
|
4474
|
-
return Object.freeze({
|
|
4475
|
-
view: environmentMap.view,
|
|
4476
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4477
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4478
|
-
addressModeU: "repeat",
|
|
4479
|
-
addressModeV: "clamp-to-edge",
|
|
4480
|
-
magFilter: "linear",
|
|
4481
|
-
minFilter: "linear",
|
|
4482
|
-
mipmapFilter: "linear"
|
|
4483
|
-
}),
|
|
4484
|
-
texture: null,
|
|
4485
|
-
ownsTexture: false,
|
|
4486
|
-
width: Math.max(1, environmentMap.width),
|
|
4487
|
-
height: Math.max(1, environmentMap.height),
|
|
4488
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4489
|
-
});
|
|
4253
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
4254
|
+
descriptor.requiredLimits = {
|
|
4255
|
+
...descriptor.requiredLimits ?? {},
|
|
4256
|
+
...requiredLimits
|
|
4257
|
+
};
|
|
4490
4258
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
addressModeV: "clamp-to-edge",
|
|
4498
|
-
magFilter: "linear",
|
|
4499
|
-
minFilter: "linear",
|
|
4500
|
-
mipmapFilter: "linear"
|
|
4501
|
-
}),
|
|
4502
|
-
texture: environmentMap.texture,
|
|
4503
|
-
ownsTexture: false,
|
|
4504
|
-
width: Math.max(1, environmentMap.width),
|
|
4505
|
-
height: Math.max(1, environmentMap.height),
|
|
4506
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4507
|
-
});
|
|
4259
|
+
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
4260
|
+
}
|
|
4261
|
+
function readGpuLimit(adapter, device, name) {
|
|
4262
|
+
const adapterValue = Number(adapter?.limits?.[name]);
|
|
4263
|
+
if (Number.isFinite(adapterValue)) {
|
|
4264
|
+
return adapterValue;
|
|
4508
4265
|
}
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
const texture = device.createTexture({
|
|
4512
|
-
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
4513
|
-
size: { width: prefiltered.width, height: prefiltered.height },
|
|
4514
|
-
format: "rgba16float",
|
|
4515
|
-
mipLevelCount: prefiltered.mipLevelCount,
|
|
4516
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4517
|
-
});
|
|
4518
|
-
uploads.forEach((upload, mipLevel) => {
|
|
4519
|
-
device.queue.writeTexture(
|
|
4520
|
-
{ texture, mipLevel },
|
|
4521
|
-
upload.bytes,
|
|
4522
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4523
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4524
|
-
);
|
|
4525
|
-
});
|
|
4526
|
-
return Object.freeze({
|
|
4527
|
-
view: texture.createView(),
|
|
4528
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4529
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4530
|
-
addressModeU: "repeat",
|
|
4531
|
-
addressModeV: "clamp-to-edge",
|
|
4532
|
-
magFilter: "linear",
|
|
4533
|
-
minFilter: "linear",
|
|
4534
|
-
mipmapFilter: "linear"
|
|
4535
|
-
}),
|
|
4536
|
-
texture,
|
|
4537
|
-
ownsTexture: true,
|
|
4538
|
-
width: prefiltered.width,
|
|
4539
|
-
height: prefiltered.height,
|
|
4540
|
-
mipLevelCount: prefiltered.mipLevelCount
|
|
4541
|
-
});
|
|
4266
|
+
const deviceValue = Number(device?.limits?.[name]);
|
|
4267
|
+
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
4542
4268
|
}
|
|
4543
|
-
function
|
|
4544
|
-
const
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
4548
|
-
const view = new DataView(bytes.buffer);
|
|
4549
|
-
for (let y = 0; y < tables.height; y += 1) {
|
|
4550
|
-
for (let x = 0; x < tables.width; x += 1) {
|
|
4551
|
-
const probability = tables.pdf[y * tables.width + x];
|
|
4552
|
-
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
4553
|
-
const marginal = tables.marginalCdf[y];
|
|
4554
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4555
|
-
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
4556
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
4557
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
4558
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4559
|
-
}
|
|
4269
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
4270
|
+
const info = adapter?.info;
|
|
4271
|
+
if (!info || typeof info !== "object") {
|
|
4272
|
+
return null;
|
|
4560
4273
|
}
|
|
4561
|
-
const texture = device.createTexture({
|
|
4562
|
-
label: "plasius.wavefront.environmentSampling",
|
|
4563
|
-
size: { width: tables.width, height: tables.height },
|
|
4564
|
-
format: "rgba16float",
|
|
4565
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4566
|
-
});
|
|
4567
|
-
device.queue.writeTexture(
|
|
4568
|
-
{ texture },
|
|
4569
|
-
bytes,
|
|
4570
|
-
{ bytesPerRow, rowsPerImage: tables.height },
|
|
4571
|
-
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
4572
|
-
);
|
|
4573
4274
|
return Object.freeze({
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4275
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
4276
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
4277
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
4278
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
4578
4279
|
});
|
|
4579
4280
|
}
|
|
4580
|
-
function
|
|
4581
|
-
const upload = createBrdfLutUploadBytes(size);
|
|
4582
|
-
const texture = device.createTexture({
|
|
4583
|
-
label: "plasius.wavefront.brdfLut",
|
|
4584
|
-
size: { width: upload.width, height: upload.height },
|
|
4585
|
-
format: "rgba16float",
|
|
4586
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4587
|
-
});
|
|
4588
|
-
device.queue.writeTexture(
|
|
4589
|
-
{ texture },
|
|
4590
|
-
upload.bytes,
|
|
4591
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4592
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4593
|
-
);
|
|
4281
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
4594
4282
|
return Object.freeze({
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4283
|
+
physicalCoreCount: null,
|
|
4284
|
+
physicalCoreCountAvailable: false,
|
|
4285
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
4286
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
4287
|
+
adapterLimits: Object.freeze({
|
|
4288
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
4289
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
4290
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
4291
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
4292
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
4293
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
4294
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
|
|
4295
|
+
maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize")
|
|
4602
4296
|
}),
|
|
4603
|
-
|
|
4604
|
-
ownsTexture: true,
|
|
4605
|
-
width: upload.width,
|
|
4606
|
-
height: upload.height
|
|
4297
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
4607
4298
|
});
|
|
4608
4299
|
}
|
|
4609
|
-
function
|
|
4610
|
-
|
|
4611
|
-
|
|
4300
|
+
function createEnvironmentMapSnapshot(environmentMap) {
|
|
4301
|
+
return Object.freeze({
|
|
4302
|
+
enabled: environmentMap.enabled,
|
|
4303
|
+
width: environmentMap.width,
|
|
4304
|
+
height: environmentMap.height,
|
|
4305
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
4306
|
+
projection: environmentMap.projection,
|
|
4307
|
+
intensity: environmentMap.intensity,
|
|
4308
|
+
rotationRadians: environmentMap.rotationRadians,
|
|
4309
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
4310
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
function nowMs3() {
|
|
4314
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
4315
|
+
return performance.now();
|
|
4316
|
+
}
|
|
4317
|
+
return Date.now();
|
|
4318
|
+
}
|
|
4319
|
+
function estimateAccelerationBuildWaitFactor(config) {
|
|
4320
|
+
if (config?.gpuAccelerationBuildRequired !== true) {
|
|
4321
|
+
return 1;
|
|
4322
|
+
}
|
|
4323
|
+
const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
|
|
4324
|
+
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
4325
|
+
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
4326
|
+
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
4327
|
+
}
|
|
4328
|
+
function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
|
|
4329
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
4330
|
+
const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
4331
|
+
return Object.freeze({
|
|
4332
|
+
timeoutMs: overrideMs,
|
|
4333
|
+
maxWaitMs: overrideMs
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
const samplesPerPixel = Math.max(
|
|
4612
4337
|
1,
|
|
4613
|
-
|
|
4338
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
4614
4339
|
);
|
|
4615
|
-
const
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
label: "plasius.wavefront.mediumTable",
|
|
4636
|
-
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
4637
|
-
format: "rgba16float",
|
|
4638
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4639
|
-
});
|
|
4640
|
-
device.queue.writeTexture(
|
|
4641
|
-
{ texture },
|
|
4642
|
-
upload.bytes,
|
|
4643
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4644
|
-
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
4340
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
4341
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
4342
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
4343
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
4344
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
4345
|
+
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
4346
|
+
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
4347
|
+
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
4348
|
+
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
4349
|
+
const estimatedWindowMs = Math.round(
|
|
4350
|
+
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
4351
|
+
);
|
|
4352
|
+
const timeoutMs = Math.min(
|
|
4353
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4354
|
+
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
4355
|
+
);
|
|
4356
|
+
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
4357
|
+
const maxWaitMs = Math.min(
|
|
4358
|
+
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
4359
|
+
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
4645
4360
|
);
|
|
4646
4361
|
return Object.freeze({
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
ownsTexture: true,
|
|
4650
|
-
count: normalized.length,
|
|
4651
|
-
width
|
|
4362
|
+
timeoutMs,
|
|
4363
|
+
maxWaitMs
|
|
4652
4364
|
});
|
|
4653
4365
|
}
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
if (
|
|
4658
|
-
return
|
|
4366
|
+
|
|
4367
|
+
// src/wavefront-gpu-synchronization.js
|
|
4368
|
+
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
4369
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
4370
|
+
return true;
|
|
4659
4371
|
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4372
|
+
const timeoutMs = Math.max(
|
|
4373
|
+
1,
|
|
4374
|
+
Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
4375
|
+
);
|
|
4376
|
+
const maxWaitMs = Math.max(
|
|
4377
|
+
timeoutMs,
|
|
4378
|
+
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
4379
|
+
);
|
|
4380
|
+
const allowTimeout = options.allowTimeout !== false;
|
|
4381
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
4382
|
+
() => ({ status: "done" }),
|
|
4383
|
+
(error) => {
|
|
4384
|
+
throw error;
|
|
4668
4385
|
}
|
|
4669
|
-
|
|
4386
|
+
);
|
|
4387
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
4388
|
+
throw new Error(
|
|
4389
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
4390
|
+
);
|
|
4391
|
+
}) : null;
|
|
4392
|
+
const startedAtMs = nowMs3();
|
|
4393
|
+
while (true) {
|
|
4394
|
+
const elapsedMs = Math.max(0, nowMs3() - startedAtMs);
|
|
4395
|
+
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
4396
|
+
if (remainingMs <= 0) {
|
|
4397
|
+
if (!allowTimeout) {
|
|
4398
|
+
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
4399
|
+
}
|
|
4400
|
+
console.warn(
|
|
4401
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
4402
|
+
);
|
|
4670
4403
|
return false;
|
|
4671
4404
|
}
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4405
|
+
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
4406
|
+
let timeoutHandle = null;
|
|
4407
|
+
let resolveTimeoutPromise = null;
|
|
4408
|
+
let timeoutSettled = false;
|
|
4409
|
+
const settleTimeoutPromise = (value) => {
|
|
4410
|
+
if (timeoutSettled) {
|
|
4411
|
+
return;
|
|
4675
4412
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4413
|
+
timeoutSettled = true;
|
|
4414
|
+
resolveTimeoutPromise?.(value);
|
|
4415
|
+
};
|
|
4416
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
4417
|
+
resolveTimeoutPromise = resolve;
|
|
4418
|
+
timeoutHandle = setTimeout(
|
|
4419
|
+
() => settleTimeoutPromise({ status: "timeout" }),
|
|
4420
|
+
waitWindowMs
|
|
4421
|
+
);
|
|
4422
|
+
});
|
|
4423
|
+
let result;
|
|
4424
|
+
try {
|
|
4425
|
+
result = await Promise.race(
|
|
4426
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
4427
|
+
);
|
|
4428
|
+
} finally {
|
|
4429
|
+
if (timeoutHandle !== null) {
|
|
4430
|
+
clearTimeout(timeoutHandle);
|
|
4431
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
if (result?.status === "done") {
|
|
4435
|
+
return true;
|
|
4436
|
+
}
|
|
4437
|
+
if (result?.status !== "timeout") {
|
|
4438
|
+
return true;
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
// src/wavefront-upload.js
|
|
4444
|
+
async function uploadWavefrontBuffer(device, buffer, data, { chunkBytes = 8 * 1024 * 1024 } = {}) {
|
|
4445
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 4 || chunkBytes % 4 !== 0) throw new Error("GPU upload chunk size must be a positive multiple of four.");
|
|
4446
|
+
if (data.byteLength <= chunkBytes) {
|
|
4447
|
+
device.queue.writeBuffer(buffer, 0, data);
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
for (let offset = 0; offset < data.byteLength; offset += chunkBytes) {
|
|
4451
|
+
device.queue.writeBuffer(buffer, offset, data, offset, Math.min(chunkBytes, data.byteLength - offset));
|
|
4452
|
+
await waitForSubmittedGpuWork(device, { timeoutMs: 1e4, maxWaitMs: 1e4, allowTimeout: false });
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
// src/gpu-allocation-budget.js
|
|
4457
|
+
var formats = Object.freeze({
|
|
4458
|
+
r8unorm: 1,
|
|
4459
|
+
r8snorm: 1,
|
|
4460
|
+
r8uint: 1,
|
|
4461
|
+
r8sint: 1,
|
|
4462
|
+
rg8unorm: 2,
|
|
4463
|
+
rg8snorm: 2,
|
|
4464
|
+
rg8uint: 2,
|
|
4465
|
+
rg8sint: 2,
|
|
4466
|
+
r16float: 2,
|
|
4467
|
+
r16uint: 2,
|
|
4468
|
+
r16sint: 2,
|
|
4469
|
+
rgba8unorm: 4,
|
|
4470
|
+
"rgba8unorm-srgb": 4,
|
|
4471
|
+
bgra8unorm: 4,
|
|
4472
|
+
"bgra8unorm-srgb": 4,
|
|
4473
|
+
rgba8snorm: 4,
|
|
4474
|
+
rgba8uint: 4,
|
|
4475
|
+
rgba8sint: 4,
|
|
4476
|
+
rg16float: 4,
|
|
4477
|
+
rg16uint: 4,
|
|
4478
|
+
rg16sint: 4,
|
|
4479
|
+
r32float: 4,
|
|
4480
|
+
r32uint: 4,
|
|
4481
|
+
r32sint: 4,
|
|
4482
|
+
rgb10a2unorm: 4,
|
|
4483
|
+
rg11b10ufloat: 4,
|
|
4484
|
+
rgba16float: 8,
|
|
4485
|
+
rgba16uint: 8,
|
|
4486
|
+
rgba16sint: 8,
|
|
4487
|
+
rg32float: 8,
|
|
4488
|
+
rg32uint: 8,
|
|
4489
|
+
rg32sint: 8,
|
|
4490
|
+
rgba32float: 16,
|
|
4491
|
+
rgba32uint: 16,
|
|
4492
|
+
rgba32sint: 16
|
|
4493
|
+
});
|
|
4494
|
+
function integer(value, name, minimum = 1) {
|
|
4495
|
+
if (!Number.isSafeInteger(value) || value < minimum) throw new Error(`${name} must be a safe integer >= ${minimum}.`);
|
|
4496
|
+
return value;
|
|
4497
|
+
}
|
|
4498
|
+
function within(value, limit, name) {
|
|
4499
|
+
if (Number.isFinite(limit) && value > limit) throw new Error(`GPU ${name} limit exceeded: requested ${value}, available ${limit}.`);
|
|
4500
|
+
}
|
|
4501
|
+
function textureBytes(descriptor, limits) {
|
|
4502
|
+
const size = descriptor.size;
|
|
4503
|
+
const array = Array.isArray(size) || ArrayBuffer.isView(size);
|
|
4504
|
+
const width = integer(array ? size[0] : size?.width, "Texture width");
|
|
4505
|
+
const height = integer((array ? size[1] : size?.height) ?? 1, "Texture height");
|
|
4506
|
+
const layers = integer((array ? size[2] : size?.depthOrArrayLayers) ?? 1, "Texture depth/layers");
|
|
4507
|
+
const dimension2 = descriptor.dimension ?? "2d";
|
|
4508
|
+
if (!["1d", "2d", "3d"].includes(dimension2)) throw new Error("Unsupported GPU texture dimension.");
|
|
4509
|
+
const limit = limits[dimension2 === "3d" ? "maxTextureDimension3D" : dimension2 === "1d" ? "maxTextureDimension1D" : "maxTextureDimension2D"];
|
|
4510
|
+
within(Math.max(width, height, dimension2 === "3d" ? layers : 1), limit, "texture dimension");
|
|
4511
|
+
if (dimension2 !== "3d") within(layers, limits.maxTextureArrayLayers, "texture layers");
|
|
4512
|
+
const bpp = formats[descriptor.format];
|
|
4513
|
+
if (!bpp) throw new Error(`GPU memory accounting does not support texture format ${descriptor.format}.`);
|
|
4514
|
+
const levels = integer(descriptor.mipLevelCount ?? 1, "Mip count");
|
|
4515
|
+
const samples = integer(descriptor.sampleCount ?? 1, "Sample count");
|
|
4516
|
+
if (levels > Math.floor(Math.log2(Math.max(width, height, dimension2 === "3d" ? layers : 1))) + 1) throw new Error("Invalid texture mip count.");
|
|
4517
|
+
let bytes = 0;
|
|
4518
|
+
for (let level = 0; level < levels; level++) bytes += Math.max(1, Math.floor(width / 2 ** level)) * Math.max(1, Math.floor(height / 2 ** level)) * (dimension2 === "3d" ? Math.max(1, Math.floor(layers / 2 ** level)) : layers) * bpp * samples;
|
|
4519
|
+
return integer(bytes, "Texture allocation");
|
|
4520
|
+
}
|
|
4521
|
+
function createGpuAllocationTracker(rawDevice, { budgetBytes, storageUsage = globalThis.GPUBufferUsage?.STORAGE ?? 128 } = {}) {
|
|
4522
|
+
if (budgetBytes !== void 0) integer(budgetBytes, "GPU memory budget");
|
|
4523
|
+
const records = /* @__PURE__ */ new Map();
|
|
4524
|
+
let allocatedBytes = 0, peakAllocatedBytes = 0;
|
|
4525
|
+
function allocate(kind, descriptor, bytes, create) {
|
|
4526
|
+
integer(bytes, "GPU allocation", 0);
|
|
4527
|
+
const total = integer(allocatedBytes + bytes, "GPU allocation total", 0);
|
|
4528
|
+
if (budgetBytes !== void 0 && total > budgetBytes) throw new Error(`GPU memory budget exceeded: ${total} requested bytes, budget ${budgetBytes}. Reduce quality or scene resources.`);
|
|
4529
|
+
const resource = create(descriptor), originalDestroy = resource.destroy.bind(resource);
|
|
4530
|
+
const release = () => {
|
|
4531
|
+
if (!records.has(resource)) return;
|
|
4532
|
+
originalDestroy();
|
|
4533
|
+
records.delete(resource);
|
|
4534
|
+
allocatedBytes -= bytes;
|
|
4535
|
+
};
|
|
4536
|
+
try {
|
|
4537
|
+
Object.defineProperty(resource, "destroy", { value: release, configurable: true });
|
|
4538
|
+
} catch (error) {
|
|
4539
|
+
originalDestroy();
|
|
4540
|
+
throw error;
|
|
4541
|
+
}
|
|
4542
|
+
records.set(resource, { kind, label: descriptor.label ?? "", bytes, release });
|
|
4543
|
+
allocatedBytes = total;
|
|
4544
|
+
peakAllocatedBytes = Math.max(total, peakAllocatedBytes);
|
|
4545
|
+
return resource;
|
|
4546
|
+
}
|
|
4547
|
+
const destroy = () => {
|
|
4548
|
+
for (const record of [...records.values()]) record.release();
|
|
4549
|
+
};
|
|
4550
|
+
const methods = {
|
|
4551
|
+
createBuffer(descriptor) {
|
|
4552
|
+
const bytes = integer(descriptor.size, "Buffer size", 0);
|
|
4553
|
+
within(bytes, rawDevice.limits?.maxBufferSize, "buffer size");
|
|
4554
|
+
if ((descriptor.usage ?? 0) & storageUsage) within(bytes, rawDevice.limits?.maxStorageBufferBindingSize, "storage binding size");
|
|
4555
|
+
return allocate("buffer", descriptor, bytes, (d) => rawDevice.createBuffer(d));
|
|
4556
|
+
},
|
|
4557
|
+
createTexture(descriptor) {
|
|
4558
|
+
return allocate("texture", descriptor, textureBytes(descriptor, rawDevice.limits ?? {}), (d) => rawDevice.createTexture(d));
|
|
4559
|
+
},
|
|
4560
|
+
createQuerySet(descriptor) {
|
|
4561
|
+
return allocate("query-set", descriptor, integer(descriptor.count, "Query count") * 8, (d) => rawDevice.createQuerySet(d));
|
|
4562
|
+
},
|
|
4563
|
+
destroy() {
|
|
4564
|
+
destroy();
|
|
4565
|
+
rawDevice.destroy();
|
|
4566
|
+
}
|
|
4567
|
+
};
|
|
4568
|
+
const device = new Proxy(rawDevice, { get(target, key) {
|
|
4569
|
+
if (Object.hasOwn(methods, key)) return methods[key];
|
|
4570
|
+
const value = Reflect.get(target, key, target);
|
|
4571
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
4572
|
+
} });
|
|
4573
|
+
return Object.freeze({ device, destroy, snapshot: () => Object.freeze({
|
|
4574
|
+
accounting: "requested-resource-bytes",
|
|
4575
|
+
budgetBytes: budgetBytes ?? null,
|
|
4576
|
+
allocatedBytes,
|
|
4577
|
+
peakAllocatedBytes,
|
|
4578
|
+
resources: Object.freeze([...records.values()].map(({ kind, label, bytes }) => Object.freeze({ kind, label, bytes })))
|
|
4579
|
+
}) });
|
|
4580
|
+
}
|
|
4581
|
+
function openGpuAllocationErrorScopes(device) {
|
|
4582
|
+
if (typeof device.pushErrorScope !== "function" || typeof device.popErrorScope !== "function") return async () => {
|
|
4583
|
+
};
|
|
4584
|
+
let count = 0;
|
|
4585
|
+
try {
|
|
4586
|
+
device.pushErrorScope("out-of-memory");
|
|
4587
|
+
count++;
|
|
4588
|
+
device.pushErrorScope("validation");
|
|
4589
|
+
count++;
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
while (count-- > 0) Promise.resolve(device.popErrorScope()).catch(() => {
|
|
4592
|
+
});
|
|
4593
|
+
throw error;
|
|
4594
|
+
}
|
|
4595
|
+
return async () => {
|
|
4596
|
+
const completions = [];
|
|
4597
|
+
while (count > 0) {
|
|
4598
|
+
count--;
|
|
4599
|
+
try {
|
|
4600
|
+
completions.push(Promise.resolve(device.popErrorScope()));
|
|
4601
|
+
} catch (error) {
|
|
4602
|
+
completions.push(Promise.reject(error));
|
|
4678
4603
|
}
|
|
4679
4604
|
}
|
|
4680
|
-
|
|
4681
|
-
|
|
4605
|
+
if (!completions.length) return;
|
|
4606
|
+
let timer;
|
|
4607
|
+
try {
|
|
4608
|
+
const errors = await Promise.race([Promise.all(completions), new Promise((_, reject) => {
|
|
4609
|
+
timer = setTimeout(() => reject(new Error("GPU allocation validation timed out.")), 1e4);
|
|
4610
|
+
})]);
|
|
4611
|
+
const error = errors.find(Boolean);
|
|
4612
|
+
if (error) throw new Error(`GPU allocation failed: ${error.message ?? String(error)}`);
|
|
4613
|
+
} finally {
|
|
4614
|
+
clearTimeout(timer);
|
|
4615
|
+
}
|
|
4616
|
+
};
|
|
4682
4617
|
}
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4618
|
+
|
|
4619
|
+
// src/wavefront-sampling.js
|
|
4620
|
+
function buildOrthonormalBasis(normal) {
|
|
4621
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
4622
|
+
const tangent = normalize(cross2(tangentFallback, normal), [1, 0, 0]);
|
|
4623
|
+
const bitangent = normalize(cross2(normal, tangent), [0, 0, 1]);
|
|
4624
|
+
return { tangent, bitangent };
|
|
4625
|
+
}
|
|
4626
|
+
function localToWorld(local, normal) {
|
|
4627
|
+
const basis = buildOrthonormalBasis(normal);
|
|
4628
|
+
return normalize(
|
|
4629
|
+
add(
|
|
4630
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
4631
|
+
scale(normal, local[2])
|
|
4632
|
+
),
|
|
4633
|
+
normal
|
|
4696
4634
|
);
|
|
4697
|
-
return Object.freeze({
|
|
4698
|
-
texture,
|
|
4699
|
-
view: texture.createView(),
|
|
4700
|
-
ownsTexture: true
|
|
4701
|
-
});
|
|
4702
4635
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
}
|
|
4712
|
-
const value = Number(rawValue);
|
|
4713
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4714
|
-
throw new Error(`${name} must be a non-negative safe integer.`);
|
|
4715
|
-
}
|
|
4716
|
-
return value;
|
|
4636
|
+
function radicalInverseVdc(bits) {
|
|
4637
|
+
let value = bits >>> 0;
|
|
4638
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
4639
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
4640
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
4641
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
4642
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
4643
|
+
return value * 23283064365386963e-26;
|
|
4717
4644
|
}
|
|
4718
|
-
function
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4645
|
+
function hammersley(index, count) {
|
|
4646
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
4647
|
+
}
|
|
4648
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
4649
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
4650
|
+
const phi = 2 * Math.PI * sample[0];
|
|
4651
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
4652
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
4653
|
+
const halfVector = localToWorld(
|
|
4654
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
4655
|
+
normal
|
|
4656
|
+
);
|
|
4657
|
+
return normalize(halfVector, normal);
|
|
4658
|
+
}
|
|
4659
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
4660
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
4661
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
4662
|
+
}
|
|
4663
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
4664
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
4665
|
+
}
|
|
4666
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
4667
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
4668
|
+
const normal = [0, 0, 1];
|
|
4669
|
+
let scaleTerm = 0;
|
|
4670
|
+
let biasTerm = 0;
|
|
4671
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
4672
|
+
const xi = hammersley(index, sampleCount);
|
|
4673
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4674
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
4675
|
+
const lightDirection = normalize(
|
|
4676
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
4677
|
+
normal
|
|
4728
4678
|
);
|
|
4679
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
4680
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
4681
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
4682
|
+
continue;
|
|
4683
|
+
}
|
|
4684
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
4685
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
4686
|
+
const fresnel2 = (1 - vDotH) ** 5;
|
|
4687
|
+
scaleTerm += (1 - fresnel2) * visibility;
|
|
4688
|
+
biasTerm += fresnel2 * visibility;
|
|
4729
4689
|
}
|
|
4690
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4730
4691
|
}
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4692
|
+
|
|
4693
|
+
// src/wavefront-gpu-resources.js
|
|
4694
|
+
function clampTileSizeForDevice(config, device) {
|
|
4695
|
+
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
4696
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4697
|
+
return config.tileSize;
|
|
4734
4698
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4699
|
+
const maxPixelsByRay = Math.floor(limit / RAY_RECORD_BYTES);
|
|
4700
|
+
const maxPixelsByHit = Math.floor(limit / HIT_RECORD_BYTES);
|
|
4701
|
+
const maxPixelsByPathVertex = Math.floor(
|
|
4702
|
+
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
4703
|
+
);
|
|
4704
|
+
const maxPixels = Math.max(
|
|
4705
|
+
256,
|
|
4706
|
+
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
4707
|
+
);
|
|
4708
|
+
if (config.tilePixelCapacity <= maxPixels) {
|
|
4709
|
+
return config.tileSize;
|
|
4737
4710
|
}
|
|
4738
|
-
return
|
|
4739
|
-
buffer: GPUBufferUsage,
|
|
4740
|
-
texture: GPUTextureUsage,
|
|
4741
|
-
shader: GPUShaderStage,
|
|
4742
|
-
map: typeof GPUMapMode === "undefined" ? null : GPUMapMode
|
|
4743
|
-
};
|
|
4711
|
+
return Math.max(16, Math.floor(Math.sqrt(maxPixels)));
|
|
4744
4712
|
}
|
|
4745
|
-
function
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4713
|
+
function createBuffer(device, usage, size, label) {
|
|
4714
|
+
return device.createBuffer({
|
|
4715
|
+
label,
|
|
4716
|
+
size: Math.max(4, size),
|
|
4717
|
+
usage
|
|
4718
|
+
});
|
|
4719
|
+
}
|
|
4720
|
+
function alignTo(value, alignment) {
|
|
4721
|
+
const resolvedAlignment = Math.max(1, alignment);
|
|
4722
|
+
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
4723
|
+
}
|
|
4724
|
+
function float32ToFloat16Bits(value) {
|
|
4725
|
+
const floatView = new Float32Array(1);
|
|
4726
|
+
const intView = new Uint32Array(floatView.buffer);
|
|
4727
|
+
floatView[0] = Number.isFinite(value) ? value : 0;
|
|
4728
|
+
const x = intView[0];
|
|
4729
|
+
const sign = x >> 16 & 32768;
|
|
4730
|
+
let mantissa = x & 8388607;
|
|
4731
|
+
let exponent = x >> 23 & 255;
|
|
4732
|
+
if (exponent === 255) {
|
|
4733
|
+
return sign | (mantissa ? 32256 : 31744);
|
|
4752
4734
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4735
|
+
exponent = exponent - 127 + 15;
|
|
4736
|
+
if (exponent >= 31) {
|
|
4737
|
+
return sign | 31744;
|
|
4755
4738
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4739
|
+
if (exponent <= 0) {
|
|
4740
|
+
if (exponent < -10) {
|
|
4741
|
+
return sign;
|
|
4742
|
+
}
|
|
4743
|
+
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4744
|
+
return sign | mantissa + 4096 >> 13;
|
|
4759
4745
|
}
|
|
4760
|
-
return
|
|
4746
|
+
return sign | exponent << 10 | mantissa + 4096 >> 13;
|
|
4761
4747
|
}
|
|
4762
|
-
|
|
4763
|
-
if (
|
|
4764
|
-
return
|
|
4748
|
+
function environmentMapIntegerScale(data) {
|
|
4749
|
+
if (data instanceof Uint8Array) {
|
|
4750
|
+
return 1 / 255;
|
|
4765
4751
|
}
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
const messages = info.messages ?? [];
|
|
4769
|
-
if (messages.length === 0) {
|
|
4770
|
-
return "";
|
|
4771
|
-
}
|
|
4772
|
-
return messages.map((message) => {
|
|
4773
|
-
const line = message.lineNum ?? "?";
|
|
4774
|
-
const column = message.linePos ?? "?";
|
|
4775
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4776
|
-
}).join("\n");
|
|
4777
|
-
} catch {
|
|
4778
|
-
return "";
|
|
4752
|
+
if (data instanceof Uint16Array) {
|
|
4753
|
+
return 1 / 65535;
|
|
4779
4754
|
}
|
|
4755
|
+
return 1;
|
|
4780
4756
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
const
|
|
4797
|
-
const
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4757
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
4758
|
+
if (!environmentMap || !environmentMap.data) {
|
|
4759
|
+
return false;
|
|
4760
|
+
}
|
|
4761
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
4762
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
4763
|
+
return environmentMap.data.length >= width * height * 4;
|
|
4764
|
+
}
|
|
4765
|
+
function createRgba8TextureUpload(source) {
|
|
4766
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
4767
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
4768
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
4769
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4770
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
4771
|
+
for (let y = 0; y < height; y += 1) {
|
|
4772
|
+
const sourceOffset = y * width * 4;
|
|
4773
|
+
const targetOffset = y * bytesPerRow;
|
|
4774
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
4775
|
+
}
|
|
4776
|
+
return Object.freeze({
|
|
4777
|
+
bytes,
|
|
4778
|
+
bytesPerRow,
|
|
4779
|
+
width,
|
|
4780
|
+
height
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
4784
|
+
if (!data || index >= data.length) {
|
|
4785
|
+
return fallback;
|
|
4802
4786
|
}
|
|
4787
|
+
const value = Number(data[index]);
|
|
4788
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
4803
4789
|
}
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4790
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
4791
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
4792
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
4793
|
+
if (cached) {
|
|
4794
|
+
return cached;
|
|
4807
4795
|
}
|
|
4808
|
-
const
|
|
4809
|
-
const
|
|
4810
|
-
const
|
|
4811
|
-
|
|
4812
|
-
|
|
4796
|
+
const width = Math.max(1, Math.trunc(size));
|
|
4797
|
+
const height = Math.max(1, Math.trunc(size));
|
|
4798
|
+
const rowBytes = width * 8;
|
|
4799
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4800
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4801
|
+
const view = new DataView(bytes.buffer);
|
|
4802
|
+
for (let y = 0; y < height; y += 1) {
|
|
4803
|
+
const roughness = (y + 0.5) / height;
|
|
4804
|
+
for (let x = 0; x < width; x += 1) {
|
|
4805
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
4806
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
4807
|
+
const offset = y * bytesPerRow + x * 8;
|
|
4808
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
4809
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
4810
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
4811
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4812
|
+
}
|
|
4813
4813
|
}
|
|
4814
|
-
const
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4818
|
-
}).join("\n");
|
|
4819
|
-
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
4820
|
-
${diagnostics}`);
|
|
4814
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4815
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4816
|
+
return upload;
|
|
4821
4817
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4818
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
4819
|
+
const width = Math.max(1, environmentMap.width);
|
|
4820
|
+
const height = Math.max(1, environmentMap.height);
|
|
4821
|
+
const pixels = new Float32Array(width * height * 4);
|
|
4822
|
+
const data = environmentMap.data;
|
|
4823
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
4824
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
4825
|
+
const sourceOffset = index * 4;
|
|
4826
|
+
const targetOffset = index * 4;
|
|
4827
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
4828
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
4829
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
4830
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
4825
4831
|
}
|
|
4826
|
-
return
|
|
4832
|
+
return pixels;
|
|
4827
4833
|
}
|
|
4828
|
-
function
|
|
4829
|
-
const
|
|
4830
|
-
const
|
|
4831
|
-
const
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
4835
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
4836
|
+
const theta = v * Math.PI;
|
|
4837
|
+
const sinTheta = Math.sin(theta);
|
|
4838
|
+
return [
|
|
4839
|
+
Math.cos(angle) * sinTheta,
|
|
4840
|
+
Math.cos(theta),
|
|
4841
|
+
Math.sin(angle) * sinTheta
|
|
4842
|
+
];
|
|
4843
|
+
}
|
|
4844
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
4845
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
4846
|
+
const clampedV = clamp2(v, 0, 1);
|
|
4847
|
+
const x = wrappedU * width - 0.5;
|
|
4848
|
+
const y = clampedV * height - 0.5;
|
|
4849
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
4850
|
+
const y0 = clamp2(Math.floor(y), 0, height - 1);
|
|
4851
|
+
const x1 = (x0 + 1) % width;
|
|
4852
|
+
const y1 = clamp2(y0 + 1, 0, height - 1);
|
|
4853
|
+
const tx = x - Math.floor(x);
|
|
4854
|
+
const ty = y - Math.floor(y);
|
|
4855
|
+
const read = (px, py) => {
|
|
4856
|
+
const offset = (py * width + px) * 4;
|
|
4857
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
4834
4858
|
};
|
|
4835
|
-
const
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4859
|
+
const a = read(x0, y0);
|
|
4860
|
+
const b = read(x1, y0);
|
|
4861
|
+
const c = read(x0, y1);
|
|
4862
|
+
const d = read(x1, y1);
|
|
4863
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
4864
|
+
return [
|
|
4865
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
4866
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
4867
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
4868
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
4869
|
+
];
|
|
4870
|
+
}
|
|
4871
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
4872
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
4873
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
4874
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
4875
|
+
const v = Math.acos(clamp2(unitDirection[1], -1, 1)) / Math.PI;
|
|
4876
|
+
return [u, clamp2(v, 0, 1)];
|
|
4877
|
+
}
|
|
4878
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
4879
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
4880
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
4881
|
+
}
|
|
4882
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
4883
|
+
return levels.map((level) => {
|
|
4884
|
+
const rowBytes = level.width * 8;
|
|
4885
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4886
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
4887
|
+
const view = new DataView(bytes.buffer);
|
|
4888
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
4889
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
4890
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
4891
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
4892
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
4893
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
4894
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
4895
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
4896
|
+
}
|
|
4841
4897
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
const
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4898
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
4899
|
+
});
|
|
4900
|
+
}
|
|
4901
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
4902
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
4903
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
4904
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
4905
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
4906
|
+
const levels = [
|
|
4907
|
+
Object.freeze({
|
|
4908
|
+
width: sourceWidth,
|
|
4909
|
+
height: sourceHeight,
|
|
4910
|
+
data: sourcePixels
|
|
4911
|
+
})
|
|
4912
|
+
];
|
|
4913
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
4914
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
4915
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
4916
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
4917
|
+
const data = new Float32Array(width * height * 4);
|
|
4918
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
4919
|
+
for (let y = 0; y < height; y += 1) {
|
|
4920
|
+
for (let x = 0; x < width; x += 1) {
|
|
4921
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
4922
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
4923
|
+
const viewDirection = normal;
|
|
4924
|
+
let totalWeight = 0;
|
|
4925
|
+
const accum = [0, 0, 0];
|
|
4926
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
4927
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
4928
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4929
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
4930
|
+
const lightDirection = normalize(
|
|
4931
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
4932
|
+
normal
|
|
4933
|
+
);
|
|
4934
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
4935
|
+
if (nDotL <= 1e-6) {
|
|
4936
|
+
continue;
|
|
4937
|
+
}
|
|
4938
|
+
const radiance = sampleEnvironmentRadiance(
|
|
4939
|
+
sourcePixels,
|
|
4940
|
+
sourceWidth,
|
|
4941
|
+
sourceHeight,
|
|
4942
|
+
lightDirection,
|
|
4943
|
+
environmentMap.rotationRadians
|
|
4944
|
+
);
|
|
4945
|
+
accum[0] += radiance[0] * nDotL;
|
|
4946
|
+
accum[1] += radiance[1] * nDotL;
|
|
4947
|
+
accum[2] += radiance[2] * nDotL;
|
|
4948
|
+
totalWeight += nDotL;
|
|
4949
|
+
}
|
|
4950
|
+
const offset = (y * width + x) * 4;
|
|
4951
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
4952
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
4953
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
4954
|
+
data[offset + 3] = 1;
|
|
4955
|
+
}
|
|
4853
4956
|
}
|
|
4854
|
-
|
|
4855
|
-
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4856
|
-
TRACE_SAMPLED_TEXTURE_BINDINGS
|
|
4857
|
-
);
|
|
4858
|
-
}
|
|
4859
|
-
const sceneStorageBufferBindingBytes = Number(
|
|
4860
|
-
memory?.sceneStorageBufferBindingBytes ?? 0
|
|
4861
|
-
);
|
|
4862
|
-
const requestedStorageBufferBindingSize = Math.max(
|
|
4863
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
|
|
4864
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
|
|
4865
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
|
|
4866
|
-
);
|
|
4867
|
-
if (requestedStorageBufferBindingSize > 0) {
|
|
4868
|
-
assertAdapterSupportsRequiredLimit(
|
|
4869
|
-
adapter,
|
|
4870
|
-
"maxStorageBufferBindingSize",
|
|
4871
|
-
requestedStorageBufferBindingSize
|
|
4872
|
-
);
|
|
4873
|
-
requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
|
|
4874
|
-
}
|
|
4875
|
-
const requestedMaxBufferSize = Math.max(
|
|
4876
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
|
|
4877
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
|
|
4878
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
|
|
4879
|
-
requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
|
|
4880
|
-
);
|
|
4881
|
-
if (requestedMaxBufferSize > 0) {
|
|
4882
|
-
assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
|
|
4883
|
-
requiredLimits.maxBufferSize = requestedMaxBufferSize;
|
|
4957
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
4884
4958
|
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4959
|
+
return Object.freeze({
|
|
4960
|
+
levels,
|
|
4961
|
+
mipLevelCount,
|
|
4962
|
+
width: sourceWidth,
|
|
4963
|
+
height: sourceHeight
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
4967
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
4968
|
+
return Object.freeze({
|
|
4969
|
+
width: 1,
|
|
4970
|
+
height: 1,
|
|
4971
|
+
pdf: new Float32Array([1]),
|
|
4972
|
+
marginalCdf: new Float32Array([1]),
|
|
4973
|
+
conditionalCdf: new Float32Array([1]),
|
|
4974
|
+
hasImportanceData: false
|
|
4975
|
+
});
|
|
4890
4976
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4977
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
4978
|
+
const width = Math.max(1, environmentMap.width);
|
|
4979
|
+
const height = Math.max(1, environmentMap.height);
|
|
4980
|
+
const pdf = new Float32Array(width * height);
|
|
4981
|
+
const marginalCdf = new Float32Array(height);
|
|
4982
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
4983
|
+
const rowSums = new Float32Array(height);
|
|
4984
|
+
let totalWeight = 0;
|
|
4985
|
+
for (let y = 0; y < height; y += 1) {
|
|
4986
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
4987
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
4988
|
+
let rowWeight = 0;
|
|
4989
|
+
for (let x = 0; x < width; x += 1) {
|
|
4990
|
+
const offset = (y * width + x) * 4;
|
|
4991
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
4992
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
4993
|
+
pdf[y * width + x] = weight;
|
|
4994
|
+
rowWeight += weight;
|
|
4995
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
4996
|
+
}
|
|
4997
|
+
rowSums[y] = rowWeight;
|
|
4998
|
+
totalWeight += rowWeight;
|
|
4999
|
+
if (rowWeight > 0) {
|
|
5000
|
+
for (let x = 0; x < width; x += 1) {
|
|
5001
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
5002
|
+
}
|
|
5003
|
+
} else {
|
|
5004
|
+
for (let x = 0; x < width; x += 1) {
|
|
5005
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
marginalCdf[y] = totalWeight;
|
|
4893
5009
|
}
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
...descriptor.requiredLimits ?? {},
|
|
4897
|
-
...requiredLimits
|
|
4898
|
-
};
|
|
5010
|
+
for (let y = 0; y < height; y += 1) {
|
|
5011
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
4899
5012
|
}
|
|
4900
|
-
|
|
5013
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
5014
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
5015
|
+
}
|
|
5016
|
+
return Object.freeze({
|
|
5017
|
+
width,
|
|
5018
|
+
height,
|
|
5019
|
+
pdf,
|
|
5020
|
+
marginalCdf,
|
|
5021
|
+
conditionalCdf,
|
|
5022
|
+
hasImportanceData: true
|
|
5023
|
+
});
|
|
4901
5024
|
}
|
|
4902
|
-
function
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5025
|
+
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
5026
|
+
if (environmentMap.view) {
|
|
5027
|
+
return Object.freeze({
|
|
5028
|
+
view: environmentMap.view,
|
|
5029
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
5030
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
5031
|
+
addressModeU: "repeat",
|
|
5032
|
+
addressModeV: "clamp-to-edge",
|
|
5033
|
+
magFilter: "linear",
|
|
5034
|
+
minFilter: "linear",
|
|
5035
|
+
mipmapFilter: "linear"
|
|
5036
|
+
}),
|
|
5037
|
+
texture: null,
|
|
5038
|
+
ownsTexture: false,
|
|
5039
|
+
width: Math.max(1, environmentMap.width),
|
|
5040
|
+
height: Math.max(1, environmentMap.height),
|
|
5041
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
5042
|
+
});
|
|
4906
5043
|
}
|
|
4907
|
-
|
|
4908
|
-
|
|
5044
|
+
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
5045
|
+
return Object.freeze({
|
|
5046
|
+
view: environmentMap.texture.createView(),
|
|
5047
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
5048
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
5049
|
+
addressModeU: "repeat",
|
|
5050
|
+
addressModeV: "clamp-to-edge",
|
|
5051
|
+
magFilter: "linear",
|
|
5052
|
+
minFilter: "linear",
|
|
5053
|
+
mipmapFilter: "linear"
|
|
5054
|
+
}),
|
|
5055
|
+
texture: environmentMap.texture,
|
|
5056
|
+
ownsTexture: false,
|
|
5057
|
+
width: Math.max(1, environmentMap.width),
|
|
5058
|
+
height: Math.max(1, environmentMap.height),
|
|
5059
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
5060
|
+
});
|
|
5061
|
+
}
|
|
5062
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
5063
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
5064
|
+
const texture = device.createTexture({
|
|
5065
|
+
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
5066
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
5067
|
+
format: "rgba16float",
|
|
5068
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
5069
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5070
|
+
});
|
|
5071
|
+
uploads.forEach((upload, mipLevel) => {
|
|
5072
|
+
device.queue.writeTexture(
|
|
5073
|
+
{ texture, mipLevel },
|
|
5074
|
+
upload.bytes,
|
|
5075
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5076
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
5077
|
+
);
|
|
5078
|
+
});
|
|
5079
|
+
return Object.freeze({
|
|
5080
|
+
view: texture.createView(),
|
|
5081
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
5082
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
5083
|
+
addressModeU: "repeat",
|
|
5084
|
+
addressModeV: "clamp-to-edge",
|
|
5085
|
+
magFilter: "linear",
|
|
5086
|
+
minFilter: "linear",
|
|
5087
|
+
mipmapFilter: "linear"
|
|
5088
|
+
}),
|
|
5089
|
+
texture,
|
|
5090
|
+
ownsTexture: true,
|
|
5091
|
+
width: prefiltered.width,
|
|
5092
|
+
height: prefiltered.height,
|
|
5093
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
5094
|
+
});
|
|
4909
5095
|
}
|
|
4910
|
-
function
|
|
4911
|
-
const
|
|
4912
|
-
|
|
4913
|
-
|
|
5096
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
5097
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
5098
|
+
const rowBytes = tables.width * 8;
|
|
5099
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
5100
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
5101
|
+
const view = new DataView(bytes.buffer);
|
|
5102
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
5103
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
5104
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
5105
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
5106
|
+
const marginal = tables.marginalCdf[y];
|
|
5107
|
+
const offset = y * bytesPerRow + x * 8;
|
|
5108
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
5109
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
5110
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
5111
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
5112
|
+
}
|
|
4914
5113
|
}
|
|
5114
|
+
const texture = device.createTexture({
|
|
5115
|
+
label: "plasius.wavefront.environmentSampling",
|
|
5116
|
+
size: { width: tables.width, height: tables.height },
|
|
5117
|
+
format: "rgba16float",
|
|
5118
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5119
|
+
});
|
|
5120
|
+
device.queue.writeTexture(
|
|
5121
|
+
{ texture },
|
|
5122
|
+
bytes,
|
|
5123
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
5124
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
5125
|
+
);
|
|
4915
5126
|
return Object.freeze({
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
5127
|
+
view: texture.createView(),
|
|
5128
|
+
texture,
|
|
5129
|
+
ownsTexture: true,
|
|
5130
|
+
hasImportanceData: tables.hasImportanceData
|
|
4920
5131
|
});
|
|
4921
5132
|
}
|
|
4922
|
-
function
|
|
5133
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
5134
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
5135
|
+
const texture = device.createTexture({
|
|
5136
|
+
label: "plasius.wavefront.brdfLut",
|
|
5137
|
+
size: { width: upload.width, height: upload.height },
|
|
5138
|
+
format: "rgba16float",
|
|
5139
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5140
|
+
});
|
|
5141
|
+
device.queue.writeTexture(
|
|
5142
|
+
{ texture },
|
|
5143
|
+
upload.bytes,
|
|
5144
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5145
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
5146
|
+
);
|
|
4923
5147
|
return Object.freeze({
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
4932
|
-
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
4933
|
-
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
4934
|
-
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
4935
|
-
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
|
|
4936
|
-
maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize")
|
|
5148
|
+
view: texture.createView(),
|
|
5149
|
+
sampler: device.createSampler({
|
|
5150
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
5151
|
+
addressModeU: "clamp-to-edge",
|
|
5152
|
+
addressModeV: "clamp-to-edge",
|
|
5153
|
+
magFilter: "linear",
|
|
5154
|
+
minFilter: "linear"
|
|
4937
5155
|
}),
|
|
4938
|
-
|
|
5156
|
+
texture,
|
|
5157
|
+
ownsTexture: true,
|
|
5158
|
+
width: upload.width,
|
|
5159
|
+
height: upload.height
|
|
4939
5160
|
});
|
|
4940
5161
|
}
|
|
4941
|
-
function
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
5162
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
5163
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
5164
|
+
const width = Math.max(
|
|
5165
|
+
1,
|
|
5166
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
5167
|
+
);
|
|
5168
|
+
const level = {
|
|
5169
|
+
width,
|
|
5170
|
+
height: MEDIUM_TABLE_ROWS,
|
|
5171
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
5172
|
+
};
|
|
5173
|
+
for (const medium of normalized) {
|
|
5174
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
5175
|
+
const absorptionOffset = mediumId * 4;
|
|
5176
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
5177
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
5178
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
5179
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
5180
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
5181
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
5182
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
5183
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
5184
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
5185
|
+
}
|
|
5186
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
5187
|
+
const texture = device.createTexture({
|
|
5188
|
+
label: "plasius.wavefront.mediumTable",
|
|
5189
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
5190
|
+
format: "rgba16float",
|
|
5191
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5192
|
+
});
|
|
5193
|
+
device.queue.writeTexture(
|
|
5194
|
+
{ texture },
|
|
5195
|
+
upload.bytes,
|
|
5196
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5197
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
5198
|
+
);
|
|
5199
|
+
return Object.freeze({
|
|
5200
|
+
texture,
|
|
5201
|
+
view: texture.createView(),
|
|
5202
|
+
ownsTexture: true,
|
|
5203
|
+
count: normalized.length,
|
|
5204
|
+
width
|
|
4952
5205
|
});
|
|
4953
5206
|
}
|
|
4954
|
-
function
|
|
4955
|
-
|
|
4956
|
-
|
|
5207
|
+
function mediumTablesEqual(left, right) {
|
|
5208
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
5209
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
5210
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
5211
|
+
return false;
|
|
4957
5212
|
}
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
5213
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
5214
|
+
const leftMedium = leftMediums[index];
|
|
5215
|
+
const rightMedium = rightMediums[index];
|
|
5216
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
5217
|
+
return false;
|
|
5218
|
+
}
|
|
5219
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
5220
|
+
return false;
|
|
5221
|
+
}
|
|
5222
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
5223
|
+
return false;
|
|
5224
|
+
}
|
|
5225
|
+
for (let component = 0; component < 3; component += 1) {
|
|
5226
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
5227
|
+
return false;
|
|
5228
|
+
}
|
|
5229
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
5230
|
+
return false;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
4963
5233
|
}
|
|
4964
|
-
|
|
4965
|
-
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
4966
|
-
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
4967
|
-
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
5234
|
+
return true;
|
|
4968
5235
|
}
|
|
4969
|
-
function
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
}
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
4983
|
-
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
4984
|
-
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
4985
|
-
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
4986
|
-
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
4987
|
-
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
4988
|
-
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
4989
|
-
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
4990
|
-
const estimatedWindowMs = Math.round(
|
|
4991
|
-
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
4992
|
-
);
|
|
4993
|
-
const timeoutMs = Math.min(
|
|
4994
|
-
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4995
|
-
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
4996
|
-
);
|
|
4997
|
-
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
4998
|
-
const maxWaitMs = Math.min(
|
|
4999
|
-
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
5000
|
-
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
5236
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
5237
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
5238
|
+
const texture = device.createTexture({
|
|
5239
|
+
label,
|
|
5240
|
+
size: { width: upload.width, height: upload.height },
|
|
5241
|
+
format: "rgba8unorm",
|
|
5242
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5243
|
+
});
|
|
5244
|
+
device.queue.writeTexture(
|
|
5245
|
+
{ texture },
|
|
5246
|
+
upload.bytes,
|
|
5247
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5248
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
5001
5249
|
);
|
|
5002
5250
|
return Object.freeze({
|
|
5003
|
-
|
|
5004
|
-
|
|
5251
|
+
texture,
|
|
5252
|
+
view: texture.createView(),
|
|
5253
|
+
ownsTexture: true
|
|
5005
5254
|
});
|
|
5006
5255
|
}
|
|
5007
5256
|
|
|
5257
|
+
// src/wavefront-instance-shader.js
|
|
5258
|
+
var WAVEFRONT_INSTANCE_SHADER_WGSL = `
|
|
5259
|
+
struct MeshInstance {
|
|
5260
|
+
world: mat4x4<f32>,
|
|
5261
|
+
inverse: mat4x4<f32>,
|
|
5262
|
+
objectId: u32,
|
|
5263
|
+
root: u32,
|
|
5264
|
+
handedness: f32,
|
|
5265
|
+
_pad: u32,
|
|
5266
|
+
};
|
|
5267
|
+
@group(0) @binding(47) var meshInstances: texture_2d<u32>;
|
|
5268
|
+
fn read_mesh_instance(index: u32) -> MeshInstance {
|
|
5269
|
+
let y = i32(index);
|
|
5270
|
+
let metadata = textureLoad(meshInstances, vec2<i32>(8, y), 0);
|
|
5271
|
+
return MeshInstance(
|
|
5272
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(0, y), 0)),
|
|
5273
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(1, y), 0)),
|
|
5274
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(2, y), 0)),
|
|
5275
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(3, y), 0))),
|
|
5276
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(4, y), 0)),
|
|
5277
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(5, y), 0)),
|
|
5278
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(6, y), 0)),
|
|
5279
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(7, y), 0))),
|
|
5280
|
+
metadata.x, metadata.y, bitcast<f32>(metadata.z), 0u);
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
fn instance_normal(instance: MeshInstance, normal: vec3<f32>) -> vec3<f32> {
|
|
5284
|
+
let normalMatrix = transpose(mat3x3<f32>(instance.inverse[0].xyz, instance.inverse[1].xyz, instance.inverse[2].xyz));
|
|
5285
|
+
return safe_normalize(normalMatrix * normal, vec3<f32>(0.0, 1.0, 0.0));
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5288
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
5289
|
+
var triangle = triangles[candidate.triangleIndex];
|
|
5290
|
+
let instance = read_mesh_instance(candidate.instanceIndex);
|
|
5291
|
+
triangle.v0 = instance.world * vec4<f32>(triangle.v0.xyz, 1.0);
|
|
5292
|
+
triangle.v1 = instance.world * vec4<f32>(triangle.v1.xyz, 1.0);
|
|
5293
|
+
triangle.v2 = instance.world * vec4<f32>(triangle.v2.xyz, 1.0);
|
|
5294
|
+
triangle.meshId = instance.objectId;
|
|
5295
|
+
return triangle;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5298
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
5299
|
+
var nearest = initialNearest;
|
|
5300
|
+
var best = no_candidate();
|
|
5301
|
+
var stack = array<u32, 64>();
|
|
5302
|
+
var stackSize = 1u;
|
|
5303
|
+
stack[0] = 0u;
|
|
5304
|
+
loop {
|
|
5305
|
+
if (stackSize == 0u) { break; }
|
|
5306
|
+
stackSize -= 1u;
|
|
5307
|
+
let node = bvhNodes[stack[stackSize]];
|
|
5308
|
+
if (!intersect_bounds(ray, node.boundsMin.xyz, node.boundsMax.xyz, nearest)) { continue; }
|
|
5309
|
+
if (node.triangleCount == 0u) {
|
|
5310
|
+
stack[stackSize] = node.childOrFirst;
|
|
5311
|
+
stack[stackSize + 1u] = node.rightChild;
|
|
5312
|
+
stackSize += 2u;
|
|
5313
|
+
} else {
|
|
5314
|
+
let instanceIndex = node.childOrFirst;
|
|
5315
|
+
let instance = read_mesh_instance(instanceIndex);
|
|
5316
|
+
var localRay = ray;
|
|
5317
|
+
localRay.origin = instance.inverse * vec4<f32>(ray.origin.xyz, 1.0);
|
|
5318
|
+
// Keep this direction unnormalized so t remains the world-space ray parameter.
|
|
5319
|
+
localRay.direction = instance.inverse * vec4<f32>(ray.direction.xyz, 0.0);
|
|
5320
|
+
var candidate = intersect_geometry_bvh(localRay, nearest, instance.root);
|
|
5321
|
+
if (candidate.hit == 1u && candidate.distance < nearest) {
|
|
5322
|
+
candidate.geometricNormal = instance_normal(instance, candidate.geometricNormal);
|
|
5323
|
+
candidate.shadingNormal = repair_shading_normal(candidate.geometricNormal,
|
|
5324
|
+
instance_normal(instance, candidate.shadingNormal));
|
|
5325
|
+
if (instance.handedness < 0.0) { candidate.frontFace = 1u - candidate.frontFace; }
|
|
5326
|
+
candidate.instanceIndex = instanceIndex;
|
|
5327
|
+
nearest = candidate.distance;
|
|
5328
|
+
best = candidate;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
return best;
|
|
5333
|
+
}
|
|
5334
|
+
`;
|
|
5335
|
+
var WAVEFRONT_UNINSTANCED_SHADER_WGSL = `
|
|
5336
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
5337
|
+
return triangles[candidate.triangleIndex];
|
|
5338
|
+
}
|
|
5339
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
5340
|
+
return intersect_geometry_bvh(ray, initialNearest, 0u);
|
|
5341
|
+
}
|
|
5342
|
+
`;
|
|
5343
|
+
|
|
5008
5344
|
// src/wavefront-shader-bvh.js
|
|
5009
5345
|
var WAVEFRONT_SHADER_BVH_WGSL = `
|
|
5010
5346
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -5467,6 +5803,7 @@ fn no_candidate() -> Candidate {
|
|
|
5467
5803
|
1u,
|
|
5468
5804
|
0xffffffffu,
|
|
5469
5805
|
0xffffffffu,
|
|
5806
|
+
0xffffffffu,
|
|
5470
5807
|
0u,
|
|
5471
5808
|
0u
|
|
5472
5809
|
);
|
|
@@ -5493,6 +5830,7 @@ fn surface_candidate(
|
|
|
5493
5830
|
uv,
|
|
5494
5831
|
frontFace,
|
|
5495
5832
|
triangleIndex,
|
|
5833
|
+
0xffffffffu,
|
|
5496
5834
|
primitiveId,
|
|
5497
5835
|
materialRefId,
|
|
5498
5836
|
mediumRefId
|
|
@@ -5554,7 +5892,7 @@ fn intersect_triangle(ray: RayRecord, triangle: TriangleRecord, triangleIndex: u
|
|
|
5554
5892
|
);
|
|
5555
5893
|
}
|
|
5556
5894
|
|
|
5557
|
-
fn
|
|
5895
|
+
fn intersect_geometry_bvh(ray: RayRecord, initialNearest: f32, root: u32) -> Candidate {
|
|
5558
5896
|
var nearest = initialNearest;
|
|
5559
5897
|
var best = no_candidate();
|
|
5560
5898
|
if (config.bvhNodeCount == 0u || config.triangleCount == 0u) {
|
|
@@ -5563,7 +5901,7 @@ fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
|
5563
5901
|
|
|
5564
5902
|
var stack = array<u32, 64>();
|
|
5565
5903
|
var stackSize = 1u;
|
|
5566
|
-
stack[0] =
|
|
5904
|
+
stack[0] = root;
|
|
5567
5905
|
|
|
5568
5906
|
loop {
|
|
5569
5907
|
if (stackSize == 0u) {
|
|
@@ -5854,7 +6192,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5854
6192
|
if (meshCandidate.hit == 1u && meshCandidate.distance < nearest) {
|
|
5855
6193
|
nearest = meshCandidate.distance;
|
|
5856
6194
|
candidate = meshCandidate;
|
|
5857
|
-
hitTriangle =
|
|
6195
|
+
hitTriangle = triangle_for_candidate(meshCandidate);
|
|
5858
6196
|
}
|
|
5859
6197
|
|
|
5860
6198
|
if (candidate.hit == 0u) {
|
|
@@ -7008,6 +7346,7 @@ struct Candidate {
|
|
|
7008
7346
|
uv: vec2<f32>,
|
|
7009
7347
|
frontFace: u32,
|
|
7010
7348
|
triangleIndex: u32,
|
|
7349
|
+
instanceIndex: u32,
|
|
7011
7350
|
primitiveId: u32,
|
|
7012
7351
|
materialRefId: u32,
|
|
7013
7352
|
mediumRefId: u32,
|
|
@@ -8605,6 +8944,7 @@ var WAVEFRONT_COMPUTE_WGSL = [
|
|
|
8605
8944
|
WAVEFRONT_SHADER_MATERIALS_WGSL,
|
|
8606
8945
|
WAVEFRONT_SHADER_LIGHTING_WGSL,
|
|
8607
8946
|
WAVEFRONT_SHADER_BVH_WGSL,
|
|
8947
|
+
WAVEFRONT_UNINSTANCED_SHADER_WGSL,
|
|
8608
8948
|
WAVEFRONT_SHADER_KERNELS_WGSL
|
|
8609
8949
|
].join("\n");
|
|
8610
8950
|
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
@@ -8616,7 +8956,7 @@ function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
|
8616
8956
|
if (workgroupSize !== WAVEFRONT_COMPUTE_WORKGROUP_SIZE) {
|
|
8617
8957
|
throw new Error(`wavefront mesh compute currently requires workgroupSize=${WAVEFRONT_COMPUTE_WORKGROUP_SIZE}`);
|
|
8618
8958
|
}
|
|
8619
|
-
return WAVEFRONT_COMPUTE_WGSL;
|
|
8959
|
+
return options.instanced === true ? WAVEFRONT_COMPUTE_WGSL.replace(WAVEFRONT_UNINSTANCED_SHADER_WGSL, WAVEFRONT_INSTANCE_SHADER_WGSL) : WAVEFRONT_COMPUTE_WGSL;
|
|
8620
8960
|
}
|
|
8621
8961
|
|
|
8622
8962
|
// src/wavefront-progressive-shader.js
|
|
@@ -9620,7 +9960,7 @@ function chooseSplitAxis(triangles) {
|
|
|
9620
9960
|
}
|
|
9621
9961
|
return extent[1] >= extent[2] ? 1 : 2;
|
|
9622
9962
|
}
|
|
9623
|
-
function
|
|
9963
|
+
function buildWavefrontBoundsBvh(triangles, maxLeafTriangles = 4) {
|
|
9624
9964
|
if (triangles.length === 0) {
|
|
9625
9965
|
return Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
9626
9966
|
}
|
|
@@ -9672,7 +10012,7 @@ function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null)
|
|
|
9672
10012
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
9673
10013
|
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
9674
10014
|
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
9675
|
-
return
|
|
10015
|
+
return buildWavefrontBoundsBvh(triangles);
|
|
9676
10016
|
}
|
|
9677
10017
|
function estimateMeshSourceShape(meshes) {
|
|
9678
10018
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
@@ -10490,6 +10830,145 @@ function dispatchWavefrontGpuAccelerationBuild({
|
|
|
10490
10830
|
return true;
|
|
10491
10831
|
}
|
|
10492
10832
|
|
|
10833
|
+
// src/wavefront-instances.js
|
|
10834
|
+
var WAVEFRONT_INSTANCE_BYTES = 144;
|
|
10835
|
+
var freeze = Object.freeze;
|
|
10836
|
+
var uint = (value) => Number.isInteger(value) && value >= 0 && value < 4294967295;
|
|
10837
|
+
function affineTransform(value) {
|
|
10838
|
+
if (!Array.isArray(value) && !ArrayBuffer.isView(value) || value.length !== 16 || !Array.from(value).every((n) => Number.isFinite(n) && Math.abs(n) <= 1e12) || value[3] !== 0 || value[7] !== 0 || value[11] !== 0 || value[15] !== 1) {
|
|
10839
|
+
throw new Error("Instance transform must be a finite affine column-major matrix.");
|
|
10840
|
+
}
|
|
10841
|
+
const m = Array.from(value, Math.fround);
|
|
10842
|
+
const [a, b, c, , d, e, f, , g, h, i] = m;
|
|
10843
|
+
const determinant = a * (e * i - f * h) - d * (b * i - c * h) + g * (b * f - c * e);
|
|
10844
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-12) {
|
|
10845
|
+
throw new Error("Instance transform must be invertible.");
|
|
10846
|
+
}
|
|
10847
|
+
const inv = [
|
|
10848
|
+
(e * i - f * h) / determinant,
|
|
10849
|
+
(c * h - b * i) / determinant,
|
|
10850
|
+
(b * f - c * e) / determinant,
|
|
10851
|
+
0,
|
|
10852
|
+
(f * g - d * i) / determinant,
|
|
10853
|
+
(a * i - c * g) / determinant,
|
|
10854
|
+
(c * d - a * f) / determinant,
|
|
10855
|
+
0,
|
|
10856
|
+
(d * h - e * g) / determinant,
|
|
10857
|
+
(b * g - a * h) / determinant,
|
|
10858
|
+
(a * e - b * d) / determinant,
|
|
10859
|
+
0,
|
|
10860
|
+
0,
|
|
10861
|
+
0,
|
|
10862
|
+
0,
|
|
10863
|
+
1
|
|
10864
|
+
];
|
|
10865
|
+
for (let r = 0; r < 3; r++) inv[12 + r] = -(inv[r] * m[12] + inv[4 + r] * m[13] + inv[8 + r] * m[14]);
|
|
10866
|
+
if (!inv.every((n) => Number.isFinite(n) && Math.abs(n) <= 1e12)) throw new Error("Instance inverse transform is out of range.");
|
|
10867
|
+
return { transform: freeze(m), inverse: freeze(inv.map((n) => n === 0 ? 0 : n)), handedness: Math.sign(determinant) };
|
|
10868
|
+
}
|
|
10869
|
+
function transformInstancePoint(m, p) {
|
|
10870
|
+
return [0, 1, 2].map((r) => m[r] * p[0] + m[4 + r] * p[1] + m[8 + r] * p[2] + m[12 + r]);
|
|
10871
|
+
}
|
|
10872
|
+
function meshBounds(mesh) {
|
|
10873
|
+
const localMin = [Infinity, Infinity, Infinity], localMax = [-Infinity, -Infinity, -Infinity];
|
|
10874
|
+
for (let i = 0; i < mesh.positions.length; i += 3) for (let a = 0; a < 3; a++) {
|
|
10875
|
+
localMin[a] = Math.min(localMin[a], Math.fround(mesh.positions[i + a]));
|
|
10876
|
+
localMax[a] = Math.max(localMax[a], Math.fround(mesh.positions[i + a]));
|
|
10877
|
+
}
|
|
10878
|
+
return { min: localMin, max: localMax };
|
|
10879
|
+
}
|
|
10880
|
+
function boundsOf(local, transform) {
|
|
10881
|
+
const localMin = local.min, localMax = local.max;
|
|
10882
|
+
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
|
|
10883
|
+
for (let corner = 0; corner < 8; corner++) {
|
|
10884
|
+
const p = transformInstancePoint(transform, [0, 1, 2].map((a) => corner & 1 << a ? localMax[a] : localMin[a]));
|
|
10885
|
+
for (let a = 0; a < 3; a++) {
|
|
10886
|
+
min[a] = Math.min(min[a], p[a]);
|
|
10887
|
+
max[a] = Math.max(max[a], p[a]);
|
|
10888
|
+
}
|
|
10889
|
+
}
|
|
10890
|
+
if (![...min, ...max].every(Number.isFinite)) throw new Error("Instance mesh has invalid bounds.");
|
|
10891
|
+
return freeze({ min: freeze(min), max: freeze(max) });
|
|
10892
|
+
}
|
|
10893
|
+
function prepareWavefrontInstances(meshes, instances) {
|
|
10894
|
+
if (!Array.isArray(instances) || instances.length === 0) throw new Error("Instances must be a nonempty array.");
|
|
10895
|
+
const byId = /* @__PURE__ */ new Map(), localBounds = /* @__PURE__ */ new Map();
|
|
10896
|
+
for (const mesh of meshes) {
|
|
10897
|
+
if (!uint(mesh.id) || byId.has(mesh.id)) throw new Error("Instanced mesh IDs must be unique unsigned integers.");
|
|
10898
|
+
if (mesh.materialKind === MATERIAL_EMISSIVE || emissionPower(mesh.emission ?? [0, 0, 0, 1]) > 1e-4) {
|
|
10899
|
+
throw new Error("Instanced emissive meshes require per-instance light sampling, which is not supported yet.");
|
|
10900
|
+
}
|
|
10901
|
+
byId.set(mesh.id, mesh);
|
|
10902
|
+
localBounds.set(mesh.id, meshBounds(mesh));
|
|
10903
|
+
}
|
|
10904
|
+
const ids = /* @__PURE__ */ new Set(), used = /* @__PURE__ */ new Set();
|
|
10905
|
+
const records = instances.map((instance) => {
|
|
10906
|
+
if (!instance || !uint(instance.id) || ids.has(instance.id)) throw new Error("Instance IDs must be unique unsigned integers.");
|
|
10907
|
+
const mesh = byId.get(instance.meshId);
|
|
10908
|
+
if (!mesh) throw new Error("Instance references an unknown mesh.");
|
|
10909
|
+
ids.add(instance.id);
|
|
10910
|
+
used.add(mesh.id);
|
|
10911
|
+
const affine = affineTransform(instance.transform);
|
|
10912
|
+
const bounds = boundsOf(localBounds.get(mesh.id), affine.transform);
|
|
10913
|
+
return freeze({
|
|
10914
|
+
...affine,
|
|
10915
|
+
id: instance.id,
|
|
10916
|
+
meshId: mesh.id,
|
|
10917
|
+
bounds,
|
|
10918
|
+
centroid: freeze(bounds.min.map((v, a) => (v + bounds.max[a]) / 2))
|
|
10919
|
+
});
|
|
10920
|
+
});
|
|
10921
|
+
if (used.size !== meshes.length) throw new Error("Every instanced mesh must have at least one instance.");
|
|
10922
|
+
return freeze({ records: freeze(records), geometryCount: meshes.length });
|
|
10923
|
+
}
|
|
10924
|
+
function buildWavefrontInstances(meshes, plan, materialSource) {
|
|
10925
|
+
const tlas = buildWavefrontBoundsBvh(plan.records, 1);
|
|
10926
|
+
const nodes = tlas.nodes.map((node) => {
|
|
10927
|
+
const pad = node.bounds.min.map((v, a) => Math.max(1, Math.abs(v), Math.abs(node.bounds.max[a])) * 1e-6);
|
|
10928
|
+
return freeze({ ...node, bounds: freeze({
|
|
10929
|
+
min: freeze(node.bounds.min.map((v, a) => v - pad[a])),
|
|
10930
|
+
max: freeze(node.bounds.max.map((v, a) => v + pad[a]))
|
|
10931
|
+
}) });
|
|
10932
|
+
}), triangles = [], roots = /* @__PURE__ */ new Map();
|
|
10933
|
+
for (const [meshIndex, mesh] of meshes.entries()) {
|
|
10934
|
+
const blas = createWavefrontMeshAcceleration([mesh], materialSource);
|
|
10935
|
+
const nodeOffset = nodes.length, triangleOffset = triangles.length;
|
|
10936
|
+
roots.set(mesh.id, nodeOffset);
|
|
10937
|
+
for (const node of blas.nodes) nodes.push(freeze({
|
|
10938
|
+
...node,
|
|
10939
|
+
firstTriangle: node.firstTriangle + (node.triangleCount > 0 ? triangleOffset : nodeOffset),
|
|
10940
|
+
leftChild: node.leftChild + nodeOffset,
|
|
10941
|
+
rightChild: node.rightChild + nodeOffset
|
|
10942
|
+
}));
|
|
10943
|
+
for (const triangle of blas.triangles) triangles.push(freeze({
|
|
10944
|
+
...triangle,
|
|
10945
|
+
triangleId: triangle.triangleId + triangleOffset,
|
|
10946
|
+
materialSlot: meshIndex
|
|
10947
|
+
}));
|
|
10948
|
+
}
|
|
10949
|
+
const records = tlas.triangles.map((record) => freeze({ ...record, root: roots.get(record.meshId) }));
|
|
10950
|
+
const buffer = new ArrayBuffer(records.length * WAVEFRONT_INSTANCE_BYTES);
|
|
10951
|
+
const floats = new Float32Array(buffer), uints = new Uint32Array(buffer);
|
|
10952
|
+
records.forEach((record, index) => {
|
|
10953
|
+
const offset = index * 36;
|
|
10954
|
+
floats.set(record.transform, offset);
|
|
10955
|
+
floats.set(record.inverse, offset + 16);
|
|
10956
|
+
uints[offset + 32] = record.id;
|
|
10957
|
+
uints[offset + 33] = record.root;
|
|
10958
|
+
floats[offset + 34] = record.handedness;
|
|
10959
|
+
});
|
|
10960
|
+
return freeze({
|
|
10961
|
+
records: freeze(records),
|
|
10962
|
+
buffer,
|
|
10963
|
+
instanceCount: records.length,
|
|
10964
|
+
geometryCount: meshes.length,
|
|
10965
|
+
instanceBytes: buffer.byteLength,
|
|
10966
|
+
tlasNodeCount: tlas.nodes.length,
|
|
10967
|
+
blasNodeCount: nodes.length - tlas.nodes.length,
|
|
10968
|
+
acceleration: freeze({ nodes: freeze(nodes), triangles: freeze(triangles) })
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10971
|
+
|
|
10493
10972
|
// src/wavefront-config.js
|
|
10494
10973
|
var WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 256;
|
|
10495
10974
|
function allocatedRecordBytes(count, recordBytes) {
|
|
@@ -10763,6 +11242,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10763
11242
|
const progressiveBytes = options.progressive === true ? width * height * 16 + 16 : 0;
|
|
10764
11243
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
10765
11244
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
11245
|
+
const instanceBytes = readNonNegativeInteger2("instanceBytes", options.instanceBytes, 0);
|
|
10766
11246
|
const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
|
|
10767
11247
|
const materialTableBytes = 0;
|
|
10768
11248
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -10818,6 +11298,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10818
11298
|
pathVertexBytes,
|
|
10819
11299
|
sceneObjectBytes,
|
|
10820
11300
|
triangleBytes,
|
|
11301
|
+
instanceBytes,
|
|
10821
11302
|
materialTableBytes,
|
|
10822
11303
|
bvhNodeBytes,
|
|
10823
11304
|
bvhCombinedBytes,
|
|
@@ -10847,6 +11328,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10847
11328
|
maxDepth: config.maxDepth,
|
|
10848
11329
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
10849
11330
|
triangleCapacity: config.triangleCapacity,
|
|
11331
|
+
instanceBytes: config.instancing?.instanceBytes ?? 0,
|
|
10850
11332
|
bvhNodeCapacity: config.bvhNodeCapacity,
|
|
10851
11333
|
bvhLeafSortCapacity: config.bvhLeafSortCapacity,
|
|
10852
11334
|
emissiveTriangleCapacity: config.emissiveTriangleCapacity,
|
|
@@ -10865,6 +11347,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10865
11347
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
10866
11348
|
assertAnalyticDisplayQualityPolicy(options);
|
|
10867
11349
|
const accelerationBuildMode = resolveAccelerationBuildMode(options);
|
|
11350
|
+
if (options.instances !== void 0 && accelerationBuildMode === "gpu") throw new Error("Instance BLAS construction currently requires cpu-upload acceleration.");
|
|
10868
11351
|
const canvas = options.canvas;
|
|
10869
11352
|
const width = readPositiveInteger2("width", options.width, getCanvasDimension(canvas, "width", DEFAULT_WIDTH));
|
|
10870
11353
|
const height = readPositiveInteger2("height", options.height, getCanvasDimension(canvas, "height", DEFAULT_HEIGHT));
|
|
@@ -10895,9 +11378,16 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10895
11378
|
);
|
|
10896
11379
|
const meshes = normalizeMeshes(options);
|
|
10897
11380
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
11381
|
+
if (options.memoryBudgetBytes !== void 0) {
|
|
11382
|
+
if (!Number.isSafeInteger(options.memoryBudgetBytes) || options.memoryBudgetBytes <= 0) throw new Error("GPU memory budget must be a positive safe integer.");
|
|
11383
|
+
const geometryMinimumBytes = meshSourceShape.triangleCount * TRIANGLE_RECORD_BYTES + (options.instances?.length ?? 0) * 144;
|
|
11384
|
+
if (!Number.isSafeInteger(geometryMinimumBytes) || geometryMinimumBytes > options.memoryBudgetBytes) throw new Error("Unique geometry exceeds the GPU memory budget before acceleration construction.");
|
|
11385
|
+
}
|
|
11386
|
+
const instancePlan = options.instances === void 0 ? null : prepareWavefrontInstances(meshes, options.instances);
|
|
10898
11387
|
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
10899
11388
|
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
10900
|
-
const
|
|
11389
|
+
const instancing = instancePlan ? buildWavefrontInstances(meshes, instancePlan, gpuMaterialSource) : null;
|
|
11390
|
+
const meshAcceleration = instancing?.acceleration ?? (accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) }));
|
|
10901
11391
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
10902
11392
|
meshes,
|
|
10903
11393
|
options.emissiveTriangleCapacity
|
|
@@ -10975,6 +11465,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10975
11465
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
10976
11466
|
gpuMeshSource,
|
|
10977
11467
|
gpuMaterialSource,
|
|
11468
|
+
instancing,
|
|
10978
11469
|
meshAcceleration,
|
|
10979
11470
|
emissiveTriangleIndices,
|
|
10980
11471
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -11013,6 +11504,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
11013
11504
|
tilePixelCapacity,
|
|
11014
11505
|
maxDepth,
|
|
11015
11506
|
sceneObjectCapacity,
|
|
11507
|
+
instanceBytes: instancing?.instanceBytes ?? 0,
|
|
11016
11508
|
triangleCapacity,
|
|
11017
11509
|
bvhNodeCapacity,
|
|
11018
11510
|
bvhLeafSortCapacity,
|
|
@@ -11046,6 +11538,7 @@ function createWavefrontTraceBindGroup({
|
|
|
11046
11538
|
counterBuffer,
|
|
11047
11539
|
outputView,
|
|
11048
11540
|
triangleBuffer,
|
|
11541
|
+
instanceView,
|
|
11049
11542
|
bvhNodeBuffer,
|
|
11050
11543
|
radianceView,
|
|
11051
11544
|
environmentPortalBuffer,
|
|
@@ -11103,7 +11596,8 @@ function createWavefrontTraceBindGroup({
|
|
|
11103
11596
|
{ binding: 41, resource: extensionAtlasResources.specularColor.view },
|
|
11104
11597
|
{ binding: 42, resource: extensionAtlasResources.iridescence.view },
|
|
11105
11598
|
{ binding: 43, resource: extensionAtlasResources.iridescenceThickness.view },
|
|
11106
|
-
{ binding: 44, resource: extensionAtlasResources.anisotropy.view }
|
|
11599
|
+
{ binding: 44, resource: extensionAtlasResources.anisotropy.view },
|
|
11600
|
+
...instanceView ? [{ binding: 47, resource: instanceView }] : []
|
|
11107
11601
|
]
|
|
11108
11602
|
});
|
|
11109
11603
|
}
|
|
@@ -11201,115 +11695,39 @@ function createWavefrontDenoiseBindGroups({
|
|
|
11201
11695
|
inputView: radianceView,
|
|
11202
11696
|
targetView: denoiseScratchView,
|
|
11203
11697
|
label: "plasius.wavefront.bind.denoise.radianceToScratch"
|
|
11204
|
-
}),
|
|
11205
|
-
resolve: createWavefrontDenoiseResolveBindGroup({
|
|
11206
|
-
device,
|
|
11207
|
-
denoiseResolveBindGroupLayout,
|
|
11208
|
-
configBuffer,
|
|
11209
|
-
inputView: denoiseScratchView,
|
|
11210
|
-
targetView: outputView,
|
|
11211
|
-
label: "plasius.wavefront.bind.denoise.scratchToOutput"
|
|
11212
|
-
}),
|
|
11213
|
-
directResolve: createWavefrontDenoiseResolveBindGroup({
|
|
11214
|
-
device,
|
|
11215
|
-
denoiseResolveBindGroupLayout,
|
|
11216
|
-
configBuffer,
|
|
11217
|
-
inputView: radianceView,
|
|
11218
|
-
targetView: outputView,
|
|
11219
|
-
label: "plasius.wavefront.bind.denoise.radianceToOutput"
|
|
11220
|
-
})
|
|
11221
|
-
});
|
|
11222
|
-
}
|
|
11223
|
-
function createWavefrontPresentBindGroup({
|
|
11224
|
-
device,
|
|
11225
|
-
presentBindGroupLayout,
|
|
11226
|
-
outputView,
|
|
11227
|
-
sampler
|
|
11228
|
-
}) {
|
|
11229
|
-
return device.createBindGroup({
|
|
11230
|
-
label: "plasius.wavefront.presentBindGroup",
|
|
11231
|
-
layout: presentBindGroupLayout,
|
|
11232
|
-
entries: [
|
|
11233
|
-
{ binding: 0, resource: outputView },
|
|
11234
|
-
{ binding: 1, resource: sampler }
|
|
11235
|
-
]
|
|
11236
|
-
});
|
|
11237
|
-
}
|
|
11238
|
-
|
|
11239
|
-
// src/wavefront-gpu-synchronization.js
|
|
11240
|
-
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
11241
|
-
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
11242
|
-
return true;
|
|
11243
|
-
}
|
|
11244
|
-
const timeoutMs = Math.max(
|
|
11245
|
-
1,
|
|
11246
|
-
Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
11247
|
-
);
|
|
11248
|
-
const maxWaitMs = Math.max(
|
|
11249
|
-
timeoutMs,
|
|
11250
|
-
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
11251
|
-
);
|
|
11252
|
-
const allowTimeout = options.allowTimeout !== false;
|
|
11253
|
-
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
11254
|
-
() => ({ status: "done" }),
|
|
11255
|
-
(error) => {
|
|
11256
|
-
throw error;
|
|
11257
|
-
}
|
|
11258
|
-
);
|
|
11259
|
-
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
11260
|
-
throw new Error(
|
|
11261
|
-
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
11262
|
-
);
|
|
11263
|
-
}) : null;
|
|
11264
|
-
const startedAtMs = nowMs3();
|
|
11265
|
-
while (true) {
|
|
11266
|
-
const elapsedMs = Math.max(0, nowMs3() - startedAtMs);
|
|
11267
|
-
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
11268
|
-
if (remainingMs <= 0) {
|
|
11269
|
-
if (!allowTimeout) {
|
|
11270
|
-
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
11271
|
-
}
|
|
11272
|
-
console.warn(
|
|
11273
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
11274
|
-
);
|
|
11275
|
-
return false;
|
|
11276
|
-
}
|
|
11277
|
-
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
11278
|
-
let timeoutHandle = null;
|
|
11279
|
-
let resolveTimeoutPromise = null;
|
|
11280
|
-
let timeoutSettled = false;
|
|
11281
|
-
const settleTimeoutPromise = (value) => {
|
|
11282
|
-
if (timeoutSettled) {
|
|
11283
|
-
return;
|
|
11284
|
-
}
|
|
11285
|
-
timeoutSettled = true;
|
|
11286
|
-
resolveTimeoutPromise?.(value);
|
|
11287
|
-
};
|
|
11288
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
11289
|
-
resolveTimeoutPromise = resolve;
|
|
11290
|
-
timeoutHandle = setTimeout(
|
|
11291
|
-
() => settleTimeoutPromise({ status: "timeout" }),
|
|
11292
|
-
waitWindowMs
|
|
11293
|
-
);
|
|
11294
|
-
});
|
|
11295
|
-
let result;
|
|
11296
|
-
try {
|
|
11297
|
-
result = await Promise.race(
|
|
11298
|
-
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
11299
|
-
);
|
|
11300
|
-
} finally {
|
|
11301
|
-
if (timeoutHandle !== null) {
|
|
11302
|
-
clearTimeout(timeoutHandle);
|
|
11303
|
-
settleTimeoutPromise({ status: "cancelled" });
|
|
11304
|
-
}
|
|
11305
|
-
}
|
|
11306
|
-
if (result?.status === "done") {
|
|
11307
|
-
return true;
|
|
11308
|
-
}
|
|
11309
|
-
if (result?.status !== "timeout") {
|
|
11310
|
-
return true;
|
|
11311
|
-
}
|
|
11312
|
-
}
|
|
11698
|
+
}),
|
|
11699
|
+
resolve: createWavefrontDenoiseResolveBindGroup({
|
|
11700
|
+
device,
|
|
11701
|
+
denoiseResolveBindGroupLayout,
|
|
11702
|
+
configBuffer,
|
|
11703
|
+
inputView: denoiseScratchView,
|
|
11704
|
+
targetView: outputView,
|
|
11705
|
+
label: "plasius.wavefront.bind.denoise.scratchToOutput"
|
|
11706
|
+
}),
|
|
11707
|
+
directResolve: createWavefrontDenoiseResolveBindGroup({
|
|
11708
|
+
device,
|
|
11709
|
+
denoiseResolveBindGroupLayout,
|
|
11710
|
+
configBuffer,
|
|
11711
|
+
inputView: radianceView,
|
|
11712
|
+
targetView: outputView,
|
|
11713
|
+
label: "plasius.wavefront.bind.denoise.radianceToOutput"
|
|
11714
|
+
})
|
|
11715
|
+
});
|
|
11716
|
+
}
|
|
11717
|
+
function createWavefrontPresentBindGroup({
|
|
11718
|
+
device,
|
|
11719
|
+
presentBindGroupLayout,
|
|
11720
|
+
outputView,
|
|
11721
|
+
sampler
|
|
11722
|
+
}) {
|
|
11723
|
+
return device.createBindGroup({
|
|
11724
|
+
label: "plasius.wavefront.presentBindGroup",
|
|
11725
|
+
layout: presentBindGroupLayout,
|
|
11726
|
+
entries: [
|
|
11727
|
+
{ binding: 0, resource: outputView },
|
|
11728
|
+
{ binding: 1, resource: sampler }
|
|
11729
|
+
]
|
|
11730
|
+
});
|
|
11313
11731
|
}
|
|
11314
11732
|
|
|
11315
11733
|
// src/wavefront-frame-telemetry.js
|
|
@@ -12136,7 +12554,7 @@ function dispatchWavefrontFrameAwaitingGpu({
|
|
|
12136
12554
|
}
|
|
12137
12555
|
|
|
12138
12556
|
// src/wavefront-pipelines.js
|
|
12139
|
-
function createWavefrontBindGroupLayouts(device, constants) {
|
|
12557
|
+
function createWavefrontBindGroupLayouts(device, constants, instanced = false) {
|
|
12140
12558
|
const trace = device.createBindGroupLayout({
|
|
12141
12559
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
12142
12560
|
entries: [
|
|
@@ -12188,7 +12606,8 @@ function createWavefrontBindGroupLayouts(device, constants) {
|
|
|
12188
12606
|
{ binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12189
12607
|
{ binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12190
12608
|
{ binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12191
|
-
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
12609
|
+
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12610
|
+
...instanced ? [{ binding: 47, visibility: constants.shader.COMPUTE, texture: { sampleType: "uint" } }] : []
|
|
12192
12611
|
]
|
|
12193
12612
|
});
|
|
12194
12613
|
const acceleration = device.createBindGroupLayout({
|
|
@@ -12383,12 +12802,12 @@ async function createWavefrontPresentPipeline(device, presentBindGroupLayout, fo
|
|
|
12383
12802
|
primitive: { topology: "triangle-list" }
|
|
12384
12803
|
});
|
|
12385
12804
|
}
|
|
12386
|
-
async function createWavefrontPipelineResources({ device, constants, format }) {
|
|
12387
|
-
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants);
|
|
12805
|
+
async function createWavefrontPipelineResources({ device, constants, format, instanced = false }) {
|
|
12806
|
+
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants, instanced);
|
|
12388
12807
|
const pipelineLayouts = createWavefrontPipelineLayouts(device, bindGroupLayouts);
|
|
12389
12808
|
const computeShader = device.createShaderModule({
|
|
12390
12809
|
label: "plasius.wavefront.computeShader",
|
|
12391
|
-
code:
|
|
12810
|
+
code: createWavefrontPathTracingComputeShaderSource({ instanced })
|
|
12392
12811
|
});
|
|
12393
12812
|
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
12394
12813
|
return Object.freeze({
|
|
@@ -12821,890 +13240,911 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
12821
13240
|
if (!adapter) {
|
|
12822
13241
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
12823
13242
|
}
|
|
12824
|
-
const
|
|
13243
|
+
const rawDevice = await adapter.requestDevice(
|
|
12825
13244
|
createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
|
|
12826
13245
|
);
|
|
12827
|
-
const
|
|
12828
|
-
const
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
const accumulationBuffer = createBuffer(
|
|
12865
|
-
device,
|
|
12866
|
-
bufferUsage,
|
|
12867
|
-
memory.accumulationBytes,
|
|
12868
|
-
"plasius.wavefront.accumulation"
|
|
12869
|
-
);
|
|
12870
|
-
const pathVertexBuffer = createBuffer(
|
|
12871
|
-
device,
|
|
12872
|
-
bufferUsage,
|
|
12873
|
-
memory.pathVertexBytes,
|
|
12874
|
-
"plasius.wavefront.pathVertices"
|
|
12875
|
-
);
|
|
12876
|
-
const sceneObjectBuffer = createBuffer(
|
|
12877
|
-
device,
|
|
12878
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12879
|
-
memory.sceneObjectBytes,
|
|
12880
|
-
"plasius.wavefront.sceneObjects"
|
|
12881
|
-
);
|
|
12882
|
-
const triangleBuffer = createBuffer(
|
|
12883
|
-
device,
|
|
12884
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12885
|
-
memory.triangleBytes,
|
|
12886
|
-
"plasius.wavefront.triangles"
|
|
12887
|
-
);
|
|
12888
|
-
const bvhNodeBuffer = createBuffer(
|
|
12889
|
-
device,
|
|
12890
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12891
|
-
memory.bvhCombinedBytes,
|
|
12892
|
-
"plasius.wavefront.bvhNodes"
|
|
12893
|
-
);
|
|
12894
|
-
const meshVertexBuffer = createBuffer(
|
|
12895
|
-
device,
|
|
12896
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12897
|
-
memory.meshVertexBytes,
|
|
12898
|
-
"plasius.wavefront.meshVertices"
|
|
12899
|
-
);
|
|
12900
|
-
const meshIndexBuffer = createBuffer(
|
|
12901
|
-
device,
|
|
12902
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12903
|
-
memory.meshIndexBytes,
|
|
12904
|
-
"plasius.wavefront.meshIndices"
|
|
12905
|
-
);
|
|
12906
|
-
const meshRangeBuffer = createBuffer(
|
|
12907
|
-
device,
|
|
12908
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12909
|
-
memory.meshRangeBytes,
|
|
12910
|
-
"plasius.wavefront.meshRanges"
|
|
12911
|
-
);
|
|
12912
|
-
const environmentPortalBuffer = createBuffer(
|
|
12913
|
-
device,
|
|
12914
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12915
|
-
memory.environmentPortalBytes,
|
|
12916
|
-
"plasius.wavefront.environmentPortals"
|
|
12917
|
-
);
|
|
12918
|
-
const bvhLeafRefBuffer = createBuffer(
|
|
12919
|
-
device,
|
|
12920
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12921
|
-
memory.bvhLeafReferenceBytes,
|
|
12922
|
-
"plasius.wavefront.bvhLeafRefs"
|
|
12923
|
-
);
|
|
12924
|
-
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
12925
|
-
const configBufferStride = memory.configBufferStride;
|
|
12926
|
-
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
12927
|
-
const frameConfigSlotCount = Math.max(
|
|
12928
|
-
1,
|
|
12929
|
-
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise || config.progressive ? 1 : 0)
|
|
12930
|
-
);
|
|
12931
|
-
const configBuffer = createBuffer(
|
|
12932
|
-
device,
|
|
12933
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12934
|
-
memory.frameConfigBytes,
|
|
12935
|
-
"plasius.wavefront.frameConfig"
|
|
12936
|
-
);
|
|
12937
|
-
const bvhBuildConfigBuffer = createBuffer(
|
|
12938
|
-
device,
|
|
12939
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12940
|
-
memory.bvhBuildConfigBytes,
|
|
12941
|
-
"plasius.wavefront.bvhBuildConfig"
|
|
12942
|
-
);
|
|
12943
|
-
const counterBuffer = createBuffer(
|
|
12944
|
-
device,
|
|
12945
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
12946
|
-
memory.counterBytes,
|
|
12947
|
-
"plasius.wavefront.counters"
|
|
12948
|
-
);
|
|
12949
|
-
const activeDispatchBuffer = createBuffer(
|
|
12950
|
-
device,
|
|
12951
|
-
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
12952
|
-
memory.indirectDispatchBytes,
|
|
12953
|
-
"plasius.wavefront.activeDispatchArgs"
|
|
12954
|
-
);
|
|
12955
|
-
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
12956
|
-
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
12957
|
-
const packedTriangles = packWavefrontTriangles(
|
|
12958
|
-
config.meshAcceleration.triangles,
|
|
12959
|
-
Math.max(1, config.triangleCapacity)
|
|
12960
|
-
);
|
|
12961
|
-
const packedBvhNodes = packWavefrontBvhNodes(
|
|
12962
|
-
config.meshAcceleration.nodes,
|
|
12963
|
-
Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity)
|
|
12964
|
-
);
|
|
12965
|
-
const packedBvhNodeUints = new Uint32Array(packedBvhNodes.buffer);
|
|
12966
|
-
const packedBvhNodeFloats = new Float32Array(packedBvhNodes.buffer);
|
|
12967
|
-
const orderedEmissiveTriangleIndices = resolveOrderedEmissiveTriangleIndices(config);
|
|
12968
|
-
const emissiveWeights = orderedEmissiveTriangleIndices.map(
|
|
12969
|
-
(triangleIndex) => emissiveTriangleWeight(config.meshAcceleration.triangles[triangleIndex])
|
|
12970
|
-
);
|
|
12971
|
-
const totalEmissiveWeight = emissiveWeights.reduce((total, weight) => total + weight, 0);
|
|
12972
|
-
let cumulativeEmissiveWeight = 0;
|
|
12973
|
-
orderedEmissiveTriangleIndices.forEach((triangleIndex, index) => {
|
|
12974
|
-
const nodeOffset = (config.bvhNodeCapacity + index) * (BVH_NODE_RECORD_BYTES / 4);
|
|
12975
|
-
cumulativeEmissiveWeight += emissiveWeights[index] ?? 0;
|
|
12976
|
-
packedBvhNodeFloats[nodeOffset] = cumulativeEmissiveWeight;
|
|
12977
|
-
packedBvhNodeFloats[nodeOffset + 1] = emissiveWeights[index] ?? 0;
|
|
12978
|
-
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
12979
|
-
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
12980
|
-
});
|
|
12981
|
-
device.queue.writeBuffer(triangleBuffer, 0, packedTriangles.buffer);
|
|
12982
|
-
device.queue.writeBuffer(bvhNodeBuffer, 0, packedBvhNodes.buffer);
|
|
12983
|
-
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
12984
|
-
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
12985
|
-
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
12986
|
-
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
12987
|
-
config.environmentPortals,
|
|
12988
|
-
Math.max(1, config.environmentPortalCapacity)
|
|
12989
|
-
);
|
|
12990
|
-
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
12991
|
-
const radianceTexture = device.createTexture({
|
|
12992
|
-
label: "plasius.wavefront.radiance",
|
|
12993
|
-
size: { width: config.width, height: config.height },
|
|
12994
|
-
format: "rgba16float",
|
|
12995
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
12996
|
-
});
|
|
12997
|
-
const radianceView = radianceTexture.createView();
|
|
12998
|
-
const denoiseScratchTexture = device.createTexture({
|
|
12999
|
-
label: "plasius.wavefront.denoiseScratch",
|
|
13000
|
-
size: { width: config.width, height: config.height },
|
|
13001
|
-
format: "rgba16float",
|
|
13002
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING
|
|
13003
|
-
});
|
|
13004
|
-
const denoiseScratchView = denoiseScratchTexture.createView();
|
|
13005
|
-
const outputTexture = device.createTexture({
|
|
13006
|
-
label: "plasius.wavefront.output",
|
|
13007
|
-
size: { width: config.width, height: config.height },
|
|
13008
|
-
format: "rgba8unorm",
|
|
13009
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_SRC
|
|
13010
|
-
});
|
|
13011
|
-
const outputView = outputTexture.createView();
|
|
13012
|
-
const sampler = device.createSampler({
|
|
13013
|
-
label: "plasius.wavefront.presentSampler",
|
|
13014
|
-
magFilter: "nearest",
|
|
13015
|
-
minFilter: "nearest"
|
|
13016
|
-
});
|
|
13017
|
-
const environmentMapResource = createEnvironmentMapResource(
|
|
13018
|
-
device,
|
|
13019
|
-
constants,
|
|
13020
|
-
config.environmentMap,
|
|
13021
|
-
config.environmentColor
|
|
13022
|
-
);
|
|
13023
|
-
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
13024
|
-
device,
|
|
13025
|
-
constants,
|
|
13026
|
-
config.environmentMap,
|
|
13027
|
-
config.environmentColor
|
|
13028
|
-
);
|
|
13029
|
-
let mediumTextureResource = createMediumTextureResource(
|
|
13030
|
-
device,
|
|
13031
|
-
constants,
|
|
13032
|
-
config.mediums
|
|
13033
|
-
);
|
|
13034
|
-
config = Object.freeze({
|
|
13035
|
-
...config,
|
|
13036
|
-
environmentMap: Object.freeze({
|
|
13037
|
-
...config.environmentMap,
|
|
13038
|
-
width: environmentMapResource.width,
|
|
13039
|
-
height: environmentMapResource.height,
|
|
13040
|
-
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
13041
|
-
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
13042
|
-
})
|
|
13043
|
-
});
|
|
13044
|
-
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
13045
|
-
const baseColorAtlasResource = createAtlasTextureResource(
|
|
13046
|
-
device,
|
|
13047
|
-
constants,
|
|
13048
|
-
config.gpuMaterialSource.baseColorAtlas,
|
|
13049
|
-
"plasius.wavefront.materialAtlas.baseColor"
|
|
13050
|
-
);
|
|
13051
|
-
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
13052
|
-
device,
|
|
13053
|
-
constants,
|
|
13054
|
-
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
13055
|
-
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
13056
|
-
);
|
|
13057
|
-
const normalAtlasResource = createAtlasTextureResource(
|
|
13058
|
-
device,
|
|
13059
|
-
constants,
|
|
13060
|
-
config.gpuMaterialSource.normalAtlas,
|
|
13061
|
-
"plasius.wavefront.materialAtlas.normal"
|
|
13062
|
-
);
|
|
13063
|
-
const occlusionAtlasResource = createAtlasTextureResource(
|
|
13064
|
-
device,
|
|
13065
|
-
constants,
|
|
13066
|
-
config.gpuMaterialSource.occlusionAtlas,
|
|
13067
|
-
"plasius.wavefront.materialAtlas.occlusion"
|
|
13068
|
-
);
|
|
13069
|
-
const emissiveAtlasResource = createAtlasTextureResource(
|
|
13070
|
-
device,
|
|
13071
|
-
constants,
|
|
13072
|
-
config.gpuMaterialSource.emissiveAtlas,
|
|
13073
|
-
"plasius.wavefront.materialAtlas.emissive"
|
|
13074
|
-
);
|
|
13075
|
-
const extensionAtlasNames = [
|
|
13076
|
-
"clearcoat",
|
|
13077
|
-
"clearcoatRoughness",
|
|
13078
|
-
"clearcoatNormal",
|
|
13079
|
-
"transmission",
|
|
13080
|
-
"thickness",
|
|
13081
|
-
"sheenColor",
|
|
13082
|
-
"sheenRoughness",
|
|
13083
|
-
"specular",
|
|
13084
|
-
"specularColor",
|
|
13085
|
-
"iridescence",
|
|
13086
|
-
"iridescenceThickness",
|
|
13087
|
-
"anisotropy"
|
|
13088
|
-
];
|
|
13089
|
-
const extensionAtlasResources = Object.freeze(
|
|
13090
|
-
Object.fromEntries(
|
|
13091
|
-
extensionAtlasNames.map((name) => [
|
|
13092
|
-
name,
|
|
13093
|
-
createAtlasTextureResource(
|
|
13246
|
+
const allocationTracker = createGpuAllocationTracker(rawDevice, { budgetBytes: options.memoryBudgetBytes, storageUsage: constants.buffer.STORAGE });
|
|
13247
|
+
const device = allocationTracker.device;
|
|
13248
|
+
let context, finishAllocationScopes;
|
|
13249
|
+
try {
|
|
13250
|
+
let createTraceBindGroups = function() {
|
|
13251
|
+
return createWavefrontTraceBindGroups({
|
|
13252
|
+
device,
|
|
13253
|
+
traceBindGroupLayout,
|
|
13254
|
+
activeQueue,
|
|
13255
|
+
nextQueue,
|
|
13256
|
+
frameConfigBuffer: configBuffer,
|
|
13257
|
+
hitBuffer,
|
|
13258
|
+
accumulationBuffer,
|
|
13259
|
+
sceneObjectBuffer,
|
|
13260
|
+
counterBuffer,
|
|
13261
|
+
outputView,
|
|
13262
|
+
triangleBuffer,
|
|
13263
|
+
instanceView,
|
|
13264
|
+
bvhNodeBuffer,
|
|
13265
|
+
radianceView,
|
|
13266
|
+
environmentPortalBuffer,
|
|
13267
|
+
environmentMapResource,
|
|
13268
|
+
pathVertexBuffer,
|
|
13269
|
+
baseColorAtlasResource,
|
|
13270
|
+
metallicRoughnessAtlasResource,
|
|
13271
|
+
normalAtlasResource,
|
|
13272
|
+
occlusionAtlasResource,
|
|
13273
|
+
emissiveAtlasResource,
|
|
13274
|
+
materialAtlasSampler,
|
|
13275
|
+
brdfLutResource,
|
|
13276
|
+
environmentSamplingResource,
|
|
13277
|
+
mediumTextureResource,
|
|
13278
|
+
extensionAtlasResources
|
|
13279
|
+
});
|
|
13280
|
+
}, ensureFrameTelemetryResources = function() {
|
|
13281
|
+
if (!frameTelemetryResources) {
|
|
13282
|
+
frameTelemetryResources = createWavefrontFrameTelemetryResources({
|
|
13094
13283
|
device,
|
|
13095
13284
|
constants,
|
|
13096
|
-
config.
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
)
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
})
|
|
13149
|
-
|
|
13150
|
-
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
|
|
13154
|
-
|
|
13155
|
-
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
bvhLeafRefBuffer
|
|
13161
|
-
});
|
|
13162
|
-
const {
|
|
13163
|
-
radiance: denoiseRadianceBindGroup,
|
|
13164
|
-
resolve: denoiseResolveBindGroup,
|
|
13165
|
-
directResolve: denoiseDirectResolveBindGroup
|
|
13166
|
-
} = createWavefrontDenoiseBindGroups({
|
|
13167
|
-
device,
|
|
13168
|
-
denoiseRadianceBindGroupLayout,
|
|
13169
|
-
denoiseResolveBindGroupLayout,
|
|
13170
|
-
configBuffer,
|
|
13171
|
-
radianceView,
|
|
13172
|
-
denoiseScratchView,
|
|
13173
|
-
outputView
|
|
13174
|
-
});
|
|
13175
|
-
const presentBindGroup = createWavefrontPresentBindGroup({
|
|
13176
|
-
device,
|
|
13177
|
-
presentBindGroupLayout,
|
|
13178
|
-
outputView,
|
|
13179
|
-
sampler
|
|
13180
|
-
});
|
|
13181
|
-
const progressive = await createWavefrontProgressiveResources({ device, constants, config, configBuffer, radianceTexture, radianceView, outputView });
|
|
13182
|
-
let activeFrameTelemetry = null;
|
|
13183
|
-
let frameTelemetryResources = null;
|
|
13184
|
-
const frameEncoder = createWavefrontFrameEncoder({
|
|
13185
|
-
getConfig: () => config,
|
|
13186
|
-
progressive,
|
|
13187
|
-
getBindGroups: () => bindGroups,
|
|
13188
|
-
pipelines,
|
|
13189
|
-
counterBuffer,
|
|
13190
|
-
activeDispatchBuffer,
|
|
13191
|
-
denoiseRadianceBindGroup,
|
|
13192
|
-
denoiseResolveBindGroup,
|
|
13193
|
-
denoiseDirectResolveBindGroup,
|
|
13194
|
-
presentPipeline,
|
|
13195
|
-
presentBindGroup,
|
|
13196
|
-
context,
|
|
13197
|
-
getFrameTelemetry: () => activeFrameTelemetry
|
|
13198
|
-
});
|
|
13199
|
-
let frame = 0;
|
|
13200
|
-
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
13201
|
-
let accelerationBuildCount = 0;
|
|
13202
|
-
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA2;
|
|
13203
|
-
let lastCompletedFrameTimeMs = null;
|
|
13204
|
-
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
13205
|
-
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
13206
|
-
gpuAdapterParallelism,
|
|
13207
|
-
createGpuParallelismCounters()
|
|
13208
|
-
);
|
|
13209
|
-
const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
|
|
13210
|
-
function ensureFrameTelemetryResources() {
|
|
13211
|
-
if (!frameTelemetryResources) {
|
|
13212
|
-
frameTelemetryResources = createWavefrontFrameTelemetryResources({
|
|
13285
|
+
maxRayCountRecords: tiles.length * config.samplesPerPixel * config.maxDepth
|
|
13286
|
+
});
|
|
13287
|
+
}
|
|
13288
|
+
return frameTelemetryResources;
|
|
13289
|
+
}, resolveRenderedSamplesPerPixel = function(renderOptions = {}, awaitGPUCompletion = true) {
|
|
13290
|
+
return resolveWavefrontRenderedSamplesPerPixel({
|
|
13291
|
+
config,
|
|
13292
|
+
renderOptions,
|
|
13293
|
+
awaitGPUCompletion,
|
|
13294
|
+
lastCompletedFrameTimeMs,
|
|
13295
|
+
lastCompletedSamplesPerPixel
|
|
13296
|
+
});
|
|
13297
|
+
}, createFrameStats = function({
|
|
13298
|
+
frameIndex,
|
|
13299
|
+
accelerationBuildSubmitted,
|
|
13300
|
+
frameSubmissionCount,
|
|
13301
|
+
parallelismCounters,
|
|
13302
|
+
renderedSamplesPerPixel,
|
|
13303
|
+
targetSamplesPerPixel,
|
|
13304
|
+
frameTimeBudgetMs,
|
|
13305
|
+
budgetConstrained
|
|
13306
|
+
}) {
|
|
13307
|
+
const stats = createWavefrontFrameStats({
|
|
13308
|
+
config,
|
|
13309
|
+
tiles,
|
|
13310
|
+
frame,
|
|
13311
|
+
frameIndex,
|
|
13312
|
+
accelerationBuilt,
|
|
13313
|
+
accelerationBuildCount,
|
|
13314
|
+
accelerationBuildSubmitted,
|
|
13315
|
+
frameSubmissionCount,
|
|
13316
|
+
frameConfigSlotCount,
|
|
13317
|
+
gpuAdapterParallelism,
|
|
13318
|
+
parallelismCounters,
|
|
13319
|
+
renderedSamplesPerPixel,
|
|
13320
|
+
targetSamplesPerPixel,
|
|
13321
|
+
frameTimeBudgetMs,
|
|
13322
|
+
budgetConstrained
|
|
13323
|
+
});
|
|
13324
|
+
lastGpuParallelism = stats.gpuParallelism;
|
|
13325
|
+
return stats.frameStats;
|
|
13326
|
+
}, writeFrameConfigSlot = function(slot, tile, frameIndex, buildRange = {}) {
|
|
13327
|
+
if (slot >= frameConfigSlotCount) {
|
|
13328
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
13329
|
+
}
|
|
13330
|
+
const offset = slot * configBufferStride;
|
|
13331
|
+
device.queue.writeBuffer(
|
|
13332
|
+
configBuffer,
|
|
13333
|
+
offset,
|
|
13334
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
13335
|
+
);
|
|
13336
|
+
return offset;
|
|
13337
|
+
}, createFrameConfigWriter = function(frameIndex) {
|
|
13338
|
+
let slot = 0;
|
|
13339
|
+
return (tile, buildRange = {}) => {
|
|
13340
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
13341
|
+
slot += 1;
|
|
13342
|
+
return offset;
|
|
13343
|
+
};
|
|
13344
|
+
}, dispatchGpuAccelerationBuild = function(frameIndex, parallelism) {
|
|
13345
|
+
const submitted = dispatchWavefrontGpuAccelerationBuild({
|
|
13346
|
+
config,
|
|
13347
|
+
accelerationBuilt,
|
|
13348
|
+
tiles,
|
|
13213
13349
|
device,
|
|
13214
|
-
|
|
13215
|
-
|
|
13350
|
+
bvhBuildConfigBuffer,
|
|
13351
|
+
configBufferStride,
|
|
13352
|
+
bvhBuildBindGroup,
|
|
13353
|
+
pipelines,
|
|
13354
|
+
parallelism,
|
|
13355
|
+
frameIndex
|
|
13356
|
+
});
|
|
13357
|
+
if (submitted) {
|
|
13358
|
+
accelerationBuilt = true;
|
|
13359
|
+
accelerationBuildCount += 1;
|
|
13360
|
+
}
|
|
13361
|
+
return submitted;
|
|
13362
|
+
}, dispatchFrame = function(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
13363
|
+
return dispatchWavefrontFrame({
|
|
13364
|
+
config,
|
|
13365
|
+
tiles,
|
|
13366
|
+
device,
|
|
13367
|
+
frameIndex,
|
|
13368
|
+
parallelism,
|
|
13369
|
+
renderedSamplesPerPixel,
|
|
13370
|
+
frameEncoder,
|
|
13371
|
+
createFrameConfigWriter
|
|
13372
|
+
});
|
|
13373
|
+
}, renderOnce = function(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
13374
|
+
const frameStartTimeMs = nowMs3();
|
|
13375
|
+
frame += 1;
|
|
13376
|
+
const frameIndex = frame + config.frameIndex;
|
|
13377
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
13378
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
13379
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13380
|
+
const frameSubmissionCount = dispatchFrame(
|
|
13381
|
+
frameIndex,
|
|
13382
|
+
parallelismCounters,
|
|
13383
|
+
samplingPlan.renderedSamplesPerPixel
|
|
13384
|
+
);
|
|
13385
|
+
const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
|
|
13386
|
+
return Object.freeze({
|
|
13387
|
+
...createFrameStats({
|
|
13388
|
+
frameIndex,
|
|
13389
|
+
accelerationBuildSubmitted,
|
|
13390
|
+
frameSubmissionCount,
|
|
13391
|
+
parallelismCounters,
|
|
13392
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13393
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13394
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13395
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
13396
|
+
}),
|
|
13397
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
13398
|
+
lastGpuParallelism,
|
|
13399
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
13400
|
+
frameTimeMs,
|
|
13401
|
+
false
|
|
13402
|
+
),
|
|
13403
|
+
timings: createWavefrontFrameTimingTelemetry({
|
|
13404
|
+
status: "not-requested",
|
|
13405
|
+
source: "cpu-submit",
|
|
13406
|
+
timestampQueryStatus: "not-recorded",
|
|
13407
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
13408
|
+
})
|
|
13409
|
+
});
|
|
13410
|
+
}, dispatchFrameAwaitingGpu = function(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
|
|
13411
|
+
return dispatchWavefrontFrameAwaitingGpu({
|
|
13412
|
+
config,
|
|
13413
|
+
tiles,
|
|
13414
|
+
device,
|
|
13415
|
+
frameIndex,
|
|
13416
|
+
parallelism,
|
|
13417
|
+
renderedSamplesPerPixel,
|
|
13418
|
+
frameEncoder,
|
|
13419
|
+
writeFrameConfigSlot,
|
|
13420
|
+
optionsForFrame
|
|
13421
|
+
});
|
|
13422
|
+
}, rebuildLiveConfig = function(overrides = {}) {
|
|
13423
|
+
return createWavefrontPathTracingComputeConfig({
|
|
13424
|
+
...options,
|
|
13425
|
+
canvas,
|
|
13426
|
+
width: config.width,
|
|
13427
|
+
height: config.height,
|
|
13428
|
+
maxDepth: config.maxDepth,
|
|
13429
|
+
tileSize: config.tileSize,
|
|
13430
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
13431
|
+
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
13432
|
+
sceneObjects: packedScene.objects,
|
|
13433
|
+
camera: activeCameraOptions,
|
|
13434
|
+
environmentMap: {
|
|
13435
|
+
...config.environmentMap
|
|
13436
|
+
},
|
|
13437
|
+
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13438
|
+
featureFlags: options.featureFlags,
|
|
13439
|
+
frameIndex: config.frameIndex,
|
|
13440
|
+
...overrides
|
|
13441
|
+
});
|
|
13442
|
+
}, rebuildMediumResources = function(nextConfig) {
|
|
13443
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
13444
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
13445
|
+
bindGroups = createTraceBindGroups();
|
|
13446
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
13447
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
13448
|
+
}
|
|
13449
|
+
}, updateSceneObjects = function(sceneObjects) {
|
|
13450
|
+
progressive?.reset();
|
|
13451
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
13452
|
+
packedScene = nextPackedScene;
|
|
13453
|
+
const nextConfig = rebuildLiveConfig();
|
|
13454
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
13455
|
+
rebuildMediumResources(nextConfig);
|
|
13456
|
+
}
|
|
13457
|
+
config = nextConfig;
|
|
13458
|
+
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
13459
|
+
return config;
|
|
13460
|
+
}, updateCamera = function(cameraOptions = {}) {
|
|
13461
|
+
progressive?.reset();
|
|
13462
|
+
activeCameraOptions = cameraOptions;
|
|
13463
|
+
config = updateWavefrontCameraConfig(config, cameraOptions);
|
|
13464
|
+
return config;
|
|
13465
|
+
}, getSnapshot = function() {
|
|
13466
|
+
return Object.freeze({
|
|
13467
|
+
frame,
|
|
13468
|
+
accumulatedSamplesPerPixel: progressive?.samples ?? 0,
|
|
13469
|
+
width: config.width,
|
|
13470
|
+
height: config.height,
|
|
13471
|
+
maxDepth: config.maxDepth,
|
|
13472
|
+
tiles: tiles.length,
|
|
13473
|
+
tileSize: config.tileSize,
|
|
13474
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
13475
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
13476
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
13477
|
+
triangleCount: config.triangleCount,
|
|
13478
|
+
instanceCount: config.instancing?.instanceCount ?? 0,
|
|
13479
|
+
geometryCount: config.instancing?.geometryCount ?? config.gpuMeshSource.meshes.count,
|
|
13480
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
13481
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
13482
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
13483
|
+
mediumCount: config.mediumCount,
|
|
13484
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
13485
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
13486
|
+
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13487
|
+
transportExperiments: config.transportExperiments,
|
|
13488
|
+
transportExperimentFlags: config.transportExperimentFlags,
|
|
13489
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
13490
|
+
displayQuality: config.displayQuality,
|
|
13491
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
13492
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
13493
|
+
accelerationBuilt,
|
|
13494
|
+
accelerationBuildCount,
|
|
13495
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
13496
|
+
gpuParallelism: lastGpuParallelism,
|
|
13497
|
+
memory: config.memory,
|
|
13498
|
+
allocations: allocationTracker.snapshot()
|
|
13499
|
+
});
|
|
13500
|
+
}, destroy = function() {
|
|
13501
|
+
progressive?.destroy();
|
|
13502
|
+
frameTelemetryResources?.destroy?.();
|
|
13503
|
+
activeQueue.destroy?.();
|
|
13504
|
+
nextQueue.destroy?.();
|
|
13505
|
+
hitBuffer.destroy?.();
|
|
13506
|
+
accumulationBuffer.destroy?.();
|
|
13507
|
+
pathVertexBuffer.destroy?.();
|
|
13508
|
+
sceneObjectBuffer.destroy?.();
|
|
13509
|
+
triangleBuffer.destroy?.();
|
|
13510
|
+
instanceTexture?.destroy?.();
|
|
13511
|
+
bvhNodeBuffer.destroy?.();
|
|
13512
|
+
meshVertexBuffer.destroy?.();
|
|
13513
|
+
meshIndexBuffer.destroy?.();
|
|
13514
|
+
meshRangeBuffer.destroy?.();
|
|
13515
|
+
environmentPortalBuffer.destroy?.();
|
|
13516
|
+
bvhLeafRefBuffer.destroy?.();
|
|
13517
|
+
configBuffer.destroy?.();
|
|
13518
|
+
bvhBuildConfigBuffer.destroy?.();
|
|
13519
|
+
counterBuffer.destroy?.();
|
|
13520
|
+
activeDispatchBuffer.destroy?.();
|
|
13521
|
+
radianceTexture.destroy?.();
|
|
13522
|
+
denoiseScratchTexture.destroy?.();
|
|
13523
|
+
outputTexture.destroy?.();
|
|
13524
|
+
if (environmentMapResource.ownsTexture) {
|
|
13525
|
+
environmentMapResource.texture?.destroy?.();
|
|
13526
|
+
}
|
|
13527
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
13528
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
13529
|
+
}
|
|
13530
|
+
if (mediumTextureResource.ownsTexture) {
|
|
13531
|
+
mediumTextureResource.texture?.destroy?.();
|
|
13532
|
+
}
|
|
13533
|
+
brdfLutResource.texture?.destroy?.();
|
|
13534
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
13535
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
13536
|
+
}
|
|
13537
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
13538
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
13539
|
+
}
|
|
13540
|
+
if (normalAtlasResource.ownsTexture) {
|
|
13541
|
+
normalAtlasResource.texture?.destroy?.();
|
|
13542
|
+
}
|
|
13543
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
13544
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
13545
|
+
}
|
|
13546
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
13547
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
13548
|
+
}
|
|
13549
|
+
Object.values(extensionAtlasResources).forEach((resource) => {
|
|
13550
|
+
if (resource.ownsTexture) {
|
|
13551
|
+
resource.texture?.destroy?.();
|
|
13552
|
+
}
|
|
13553
|
+
});
|
|
13554
|
+
context.unconfigure?.();
|
|
13555
|
+
allocationTracker.destroy();
|
|
13556
|
+
};
|
|
13557
|
+
finishAllocationScopes = openGpuAllocationErrorScopes(rawDevice);
|
|
13558
|
+
const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
|
|
13559
|
+
context = canvas.getContext("webgpu");
|
|
13560
|
+
if (!context || typeof context.configure !== "function") {
|
|
13561
|
+
throw new Error("Canvas WebGPU context does not support configure().");
|
|
13562
|
+
}
|
|
13563
|
+
const format = options.format ?? (typeof navigatorRef.gpu.getPreferredCanvasFormat === "function" ? navigatorRef.gpu.getPreferredCanvasFormat() : "bgra8unorm");
|
|
13564
|
+
let config = initialConfig;
|
|
13565
|
+
const safeTileSize = clampTileSizeForDevice(config, device);
|
|
13566
|
+
if (safeTileSize !== config.tileSize) {
|
|
13567
|
+
config = createWavefrontPathTracingComputeConfig({
|
|
13568
|
+
...options,
|
|
13569
|
+
canvas,
|
|
13570
|
+
width: config.width,
|
|
13571
|
+
height: config.height,
|
|
13572
|
+
tileSize: safeTileSize,
|
|
13573
|
+
tilePixelCapacity: Math.min(
|
|
13574
|
+
config.tilePixelCapacity,
|
|
13575
|
+
safeTileSize * safeTileSize
|
|
13576
|
+
)
|
|
13216
13577
|
});
|
|
13217
13578
|
}
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13579
|
+
config = Object.freeze({
|
|
13580
|
+
...config,
|
|
13581
|
+
memory: estimateWavefrontPathTracingMemoryForConfig(config, device.limits)
|
|
13582
|
+
});
|
|
13583
|
+
canvas.width = config.width;
|
|
13584
|
+
canvas.height = config.height;
|
|
13585
|
+
context.configure({
|
|
13586
|
+
device: rawDevice,
|
|
13587
|
+
format,
|
|
13588
|
+
alphaMode: options.alpha === true ? "premultiplied" : "opaque"
|
|
13589
|
+
});
|
|
13590
|
+
const bufferUsage = constants.buffer.STORAGE | constants.buffer.COPY_DST;
|
|
13591
|
+
const memory = config.memory;
|
|
13592
|
+
let instanceTexture = null, instanceView = null;
|
|
13593
|
+
if (config.instancing) {
|
|
13594
|
+
const count = config.instancing.instanceCount;
|
|
13595
|
+
instanceTexture = device.createTexture({
|
|
13596
|
+
label: "plasius.wavefront.instances",
|
|
13597
|
+
size: { width: 9, height: count },
|
|
13598
|
+
format: "rgba32uint",
|
|
13599
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
13600
|
+
});
|
|
13601
|
+
const upload = new Uint8Array(count * 256), source = new Uint8Array(config.instancing.buffer);
|
|
13602
|
+
for (let i = 0; i < count; i++) upload.set(source.subarray(i * 144, (i + 1) * 144), i * 256);
|
|
13603
|
+
device.queue.writeTexture({ texture: instanceTexture }, upload, { bytesPerRow: 256, rowsPerImage: count }, { width: 9, height: count, depthOrArrayLayers: 1 });
|
|
13604
|
+
instanceView = instanceTexture.createView();
|
|
13605
|
+
}
|
|
13606
|
+
const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
|
|
13607
|
+
const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
|
|
13608
|
+
const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
|
|
13609
|
+
const accumulationBuffer = createBuffer(
|
|
13610
|
+
device,
|
|
13611
|
+
bufferUsage,
|
|
13612
|
+
memory.accumulationBytes,
|
|
13613
|
+
"plasius.wavefront.accumulation"
|
|
13614
|
+
);
|
|
13615
|
+
const pathVertexBuffer = createBuffer(
|
|
13616
|
+
device,
|
|
13617
|
+
bufferUsage,
|
|
13618
|
+
memory.pathVertexBytes,
|
|
13619
|
+
"plasius.wavefront.pathVertices"
|
|
13620
|
+
);
|
|
13621
|
+
const sceneObjectBuffer = createBuffer(
|
|
13622
|
+
device,
|
|
13623
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13624
|
+
memory.sceneObjectBytes,
|
|
13625
|
+
"plasius.wavefront.sceneObjects"
|
|
13626
|
+
);
|
|
13627
|
+
const triangleBuffer = createBuffer(
|
|
13628
|
+
device,
|
|
13629
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13630
|
+
memory.triangleBytes,
|
|
13631
|
+
"plasius.wavefront.triangles"
|
|
13632
|
+
);
|
|
13633
|
+
const bvhNodeBuffer = createBuffer(
|
|
13634
|
+
device,
|
|
13635
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13636
|
+
memory.bvhCombinedBytes,
|
|
13637
|
+
"plasius.wavefront.bvhNodes"
|
|
13638
|
+
);
|
|
13639
|
+
const meshVertexBuffer = createBuffer(
|
|
13640
|
+
device,
|
|
13641
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13642
|
+
memory.meshVertexBytes,
|
|
13643
|
+
"plasius.wavefront.meshVertices"
|
|
13644
|
+
);
|
|
13645
|
+
const meshIndexBuffer = createBuffer(
|
|
13646
|
+
device,
|
|
13647
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13648
|
+
memory.meshIndexBytes,
|
|
13649
|
+
"plasius.wavefront.meshIndices"
|
|
13650
|
+
);
|
|
13651
|
+
const meshRangeBuffer = createBuffer(
|
|
13652
|
+
device,
|
|
13653
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13654
|
+
memory.meshRangeBytes,
|
|
13655
|
+
"plasius.wavefront.meshRanges"
|
|
13656
|
+
);
|
|
13657
|
+
const environmentPortalBuffer = createBuffer(
|
|
13658
|
+
device,
|
|
13659
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13660
|
+
memory.environmentPortalBytes,
|
|
13661
|
+
"plasius.wavefront.environmentPortals"
|
|
13662
|
+
);
|
|
13663
|
+
const bvhLeafRefBuffer = createBuffer(
|
|
13664
|
+
device,
|
|
13665
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13666
|
+
memory.bvhLeafReferenceBytes,
|
|
13667
|
+
"plasius.wavefront.bvhLeafRefs"
|
|
13668
|
+
);
|
|
13669
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
13670
|
+
const configBufferStride = memory.configBufferStride;
|
|
13671
|
+
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
13672
|
+
const frameConfigSlotCount = Math.max(
|
|
13673
|
+
1,
|
|
13674
|
+
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise || config.progressive ? 1 : 0)
|
|
13675
|
+
);
|
|
13676
|
+
const configBuffer = createBuffer(
|
|
13677
|
+
device,
|
|
13678
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
13679
|
+
memory.frameConfigBytes,
|
|
13680
|
+
"plasius.wavefront.frameConfig"
|
|
13681
|
+
);
|
|
13682
|
+
const bvhBuildConfigBuffer = createBuffer(
|
|
13683
|
+
device,
|
|
13684
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
13685
|
+
memory.bvhBuildConfigBytes,
|
|
13686
|
+
"plasius.wavefront.bvhBuildConfig"
|
|
13687
|
+
);
|
|
13688
|
+
const counterBuffer = createBuffer(
|
|
13689
|
+
device,
|
|
13690
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
13691
|
+
memory.counterBytes,
|
|
13692
|
+
"plasius.wavefront.counters"
|
|
13693
|
+
);
|
|
13694
|
+
const activeDispatchBuffer = createBuffer(
|
|
13695
|
+
device,
|
|
13696
|
+
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
13697
|
+
memory.indirectDispatchBytes,
|
|
13698
|
+
"plasius.wavefront.activeDispatchArgs"
|
|
13699
|
+
);
|
|
13700
|
+
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
13701
|
+
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
13702
|
+
const packedTriangles = packWavefrontTriangles(
|
|
13703
|
+
config.meshAcceleration.triangles,
|
|
13704
|
+
Math.max(1, config.triangleCapacity)
|
|
13705
|
+
);
|
|
13706
|
+
const packedBvhNodes = packWavefrontBvhNodes(
|
|
13707
|
+
config.meshAcceleration.nodes,
|
|
13708
|
+
Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity)
|
|
13709
|
+
);
|
|
13710
|
+
const packedBvhNodeUints = new Uint32Array(packedBvhNodes.buffer);
|
|
13711
|
+
const packedBvhNodeFloats = new Float32Array(packedBvhNodes.buffer);
|
|
13712
|
+
const orderedEmissiveTriangleIndices = resolveOrderedEmissiveTriangleIndices(config);
|
|
13713
|
+
const emissiveWeights = orderedEmissiveTriangleIndices.map(
|
|
13714
|
+
(triangleIndex) => emissiveTriangleWeight(config.meshAcceleration.triangles[triangleIndex])
|
|
13715
|
+
);
|
|
13716
|
+
const totalEmissiveWeight = emissiveWeights.reduce((total, weight) => total + weight, 0);
|
|
13717
|
+
let cumulativeEmissiveWeight = 0;
|
|
13718
|
+
orderedEmissiveTriangleIndices.forEach((triangleIndex, index) => {
|
|
13719
|
+
const nodeOffset = (config.bvhNodeCapacity + index) * (BVH_NODE_RECORD_BYTES / 4);
|
|
13720
|
+
cumulativeEmissiveWeight += emissiveWeights[index] ?? 0;
|
|
13721
|
+
packedBvhNodeFloats[nodeOffset] = cumulativeEmissiveWeight;
|
|
13722
|
+
packedBvhNodeFloats[nodeOffset + 1] = emissiveWeights[index] ?? 0;
|
|
13723
|
+
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
13724
|
+
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
13227
13725
|
});
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
config,
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
frameIndex,
|
|
13244
|
-
accelerationBuilt,
|
|
13245
|
-
accelerationBuildCount,
|
|
13246
|
-
accelerationBuildSubmitted,
|
|
13247
|
-
frameSubmissionCount,
|
|
13248
|
-
frameConfigSlotCount,
|
|
13249
|
-
gpuAdapterParallelism,
|
|
13250
|
-
parallelismCounters,
|
|
13251
|
-
renderedSamplesPerPixel,
|
|
13252
|
-
targetSamplesPerPixel,
|
|
13253
|
-
frameTimeBudgetMs,
|
|
13254
|
-
budgetConstrained
|
|
13726
|
+
await uploadWavefrontBuffer(device, triangleBuffer, packedTriangles.buffer);
|
|
13727
|
+
await uploadWavefrontBuffer(device, bvhNodeBuffer, packedBvhNodes.buffer);
|
|
13728
|
+
await uploadWavefrontBuffer(device, meshVertexBuffer, config.gpuMeshSource.vertices.buffer);
|
|
13729
|
+
await uploadWavefrontBuffer(device, meshIndexBuffer, config.gpuMeshSource.indices.buffer);
|
|
13730
|
+
await uploadWavefrontBuffer(device, meshRangeBuffer, config.gpuMeshSource.meshes.buffer);
|
|
13731
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
13732
|
+
config.environmentPortals,
|
|
13733
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
13734
|
+
);
|
|
13735
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
13736
|
+
const radianceTexture = device.createTexture({
|
|
13737
|
+
label: "plasius.wavefront.radiance",
|
|
13738
|
+
size: { width: config.width, height: config.height },
|
|
13739
|
+
format: "rgba16float",
|
|
13740
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
13255
13741
|
});
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13742
|
+
const radianceView = radianceTexture.createView();
|
|
13743
|
+
const denoiseScratchTexture = device.createTexture({
|
|
13744
|
+
label: "plasius.wavefront.denoiseScratch",
|
|
13745
|
+
size: { width: config.width, height: config.height },
|
|
13746
|
+
format: "rgba16float",
|
|
13747
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING
|
|
13748
|
+
});
|
|
13749
|
+
const denoiseScratchView = denoiseScratchTexture.createView();
|
|
13750
|
+
const outputTexture = device.createTexture({
|
|
13751
|
+
label: "plasius.wavefront.output",
|
|
13752
|
+
size: { width: config.width, height: config.height },
|
|
13753
|
+
format: "rgba8unorm",
|
|
13754
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_SRC
|
|
13755
|
+
});
|
|
13756
|
+
const outputView = outputTexture.createView();
|
|
13757
|
+
const sampler = device.createSampler({
|
|
13758
|
+
label: "plasius.wavefront.presentSampler",
|
|
13759
|
+
magFilter: "nearest",
|
|
13760
|
+
minFilter: "nearest"
|
|
13761
|
+
});
|
|
13762
|
+
const environmentMapResource = createEnvironmentMapResource(
|
|
13261
13763
|
device,
|
|
13262
13764
|
constants,
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
});
|
|
13266
|
-
}
|
|
13267
|
-
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
13268
|
-
if (slot >= frameConfigSlotCount) {
|
|
13269
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
13270
|
-
}
|
|
13271
|
-
const offset = slot * configBufferStride;
|
|
13272
|
-
device.queue.writeBuffer(
|
|
13273
|
-
configBuffer,
|
|
13274
|
-
offset,
|
|
13275
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
13765
|
+
config.environmentMap,
|
|
13766
|
+
config.environmentColor
|
|
13276
13767
|
);
|
|
13277
|
-
|
|
13278
|
-
}
|
|
13279
|
-
function createFrameConfigWriter(frameIndex) {
|
|
13280
|
-
let slot = 0;
|
|
13281
|
-
return (tile, buildRange = {}) => {
|
|
13282
|
-
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
13283
|
-
slot += 1;
|
|
13284
|
-
return offset;
|
|
13285
|
-
};
|
|
13286
|
-
}
|
|
13287
|
-
function dispatchGpuAccelerationBuild(frameIndex, parallelism) {
|
|
13288
|
-
const submitted = dispatchWavefrontGpuAccelerationBuild({
|
|
13289
|
-
config,
|
|
13290
|
-
accelerationBuilt,
|
|
13291
|
-
tiles,
|
|
13768
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
13292
13769
|
device,
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
frameIndex
|
|
13299
|
-
});
|
|
13300
|
-
if (submitted) {
|
|
13301
|
-
accelerationBuilt = true;
|
|
13302
|
-
accelerationBuildCount += 1;
|
|
13303
|
-
}
|
|
13304
|
-
return submitted;
|
|
13305
|
-
}
|
|
13306
|
-
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
13307
|
-
return dispatchWavefrontFrame({
|
|
13308
|
-
config,
|
|
13309
|
-
tiles,
|
|
13770
|
+
constants,
|
|
13771
|
+
config.environmentMap,
|
|
13772
|
+
config.environmentColor
|
|
13773
|
+
);
|
|
13774
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
13310
13775
|
device,
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
renderedSamplesPerPixel,
|
|
13314
|
-
frameEncoder,
|
|
13315
|
-
createFrameConfigWriter
|
|
13316
|
-
});
|
|
13317
|
-
}
|
|
13318
|
-
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
13319
|
-
const frameStartTimeMs = nowMs3();
|
|
13320
|
-
frame += 1;
|
|
13321
|
-
const frameIndex = frame + config.frameIndex;
|
|
13322
|
-
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
13323
|
-
const parallelismCounters = createGpuParallelismCounters();
|
|
13324
|
-
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13325
|
-
const frameSubmissionCount = dispatchFrame(
|
|
13326
|
-
frameIndex,
|
|
13327
|
-
parallelismCounters,
|
|
13328
|
-
samplingPlan.renderedSamplesPerPixel
|
|
13776
|
+
constants,
|
|
13777
|
+
config.mediums
|
|
13329
13778
|
);
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13339
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13340
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13341
|
-
}),
|
|
13342
|
-
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
13343
|
-
lastGpuParallelism,
|
|
13344
|
-
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
13345
|
-
frameTimeMs,
|
|
13346
|
-
false
|
|
13347
|
-
),
|
|
13348
|
-
timings: createWavefrontFrameTimingTelemetry({
|
|
13349
|
-
status: "not-requested",
|
|
13350
|
-
source: "cpu-submit",
|
|
13351
|
-
timestampQueryStatus: "not-recorded",
|
|
13352
|
-
totalRenderJobTimeMs: frameTimeMs
|
|
13779
|
+
config = Object.freeze({
|
|
13780
|
+
...config,
|
|
13781
|
+
environmentMap: Object.freeze({
|
|
13782
|
+
...config.environmentMap,
|
|
13783
|
+
width: environmentMapResource.width,
|
|
13784
|
+
height: environmentMapResource.height,
|
|
13785
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
13786
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
13353
13787
|
})
|
|
13354
13788
|
});
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
return dispatchWavefrontFrameAwaitingGpu({
|
|
13358
|
-
config,
|
|
13359
|
-
tiles,
|
|
13789
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
13790
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
13360
13791
|
device,
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
optionsForFrame
|
|
13367
|
-
});
|
|
13368
|
-
}
|
|
13369
|
-
async function readOutputProbe(optionsForProbe = {}) {
|
|
13370
|
-
return readWavefrontOutputProbe({
|
|
13792
|
+
constants,
|
|
13793
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
13794
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
13795
|
+
);
|
|
13796
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
13371
13797
|
device,
|
|
13372
13798
|
constants,
|
|
13373
|
-
config,
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13799
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
13800
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
13801
|
+
);
|
|
13802
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
13803
|
+
device,
|
|
13804
|
+
constants,
|
|
13805
|
+
config.gpuMaterialSource.normalAtlas,
|
|
13806
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
13807
|
+
);
|
|
13808
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
13809
|
+
device,
|
|
13810
|
+
constants,
|
|
13811
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
13812
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
13813
|
+
);
|
|
13814
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
13815
|
+
device,
|
|
13816
|
+
constants,
|
|
13817
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
13818
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
13819
|
+
);
|
|
13820
|
+
const extensionAtlasNames = [
|
|
13821
|
+
"clearcoat",
|
|
13822
|
+
"clearcoatRoughness",
|
|
13823
|
+
"clearcoatNormal",
|
|
13824
|
+
"transmission",
|
|
13825
|
+
"thickness",
|
|
13826
|
+
"sheenColor",
|
|
13827
|
+
"sheenRoughness",
|
|
13828
|
+
"specular",
|
|
13829
|
+
"specularColor",
|
|
13830
|
+
"iridescence",
|
|
13831
|
+
"iridescenceThickness",
|
|
13832
|
+
"anisotropy"
|
|
13833
|
+
];
|
|
13834
|
+
const extensionAtlasResources = Object.freeze(
|
|
13835
|
+
Object.fromEntries(
|
|
13836
|
+
extensionAtlasNames.map((name) => [
|
|
13837
|
+
name,
|
|
13838
|
+
createAtlasTextureResource(
|
|
13839
|
+
device,
|
|
13840
|
+
constants,
|
|
13841
|
+
config.gpuMaterialSource.extensionAtlases[name],
|
|
13842
|
+
`plasius.wavefront.materialAtlas.${name}`
|
|
13843
|
+
)
|
|
13844
|
+
])
|
|
13845
|
+
)
|
|
13846
|
+
);
|
|
13847
|
+
const materialAtlasSampler = device.createSampler({
|
|
13848
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
13849
|
+
addressModeU: "clamp-to-edge",
|
|
13850
|
+
addressModeV: "clamp-to-edge",
|
|
13851
|
+
magFilter: "linear",
|
|
13852
|
+
minFilter: "linear"
|
|
13377
13853
|
});
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
const
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13854
|
+
const {
|
|
13855
|
+
bindGroupLayouts: {
|
|
13856
|
+
trace: traceBindGroupLayout,
|
|
13857
|
+
acceleration: accelerationBindGroupLayout,
|
|
13858
|
+
denoiseRadiance: denoiseRadianceBindGroupLayout,
|
|
13859
|
+
denoiseResolve: denoiseResolveBindGroupLayout,
|
|
13860
|
+
present: presentBindGroupLayout
|
|
13861
|
+
},
|
|
13862
|
+
computePipelines: pipelines,
|
|
13863
|
+
presentPipeline
|
|
13864
|
+
} = await createWavefrontPipelineResources({ device, constants, format, instanced: !!config.instancing });
|
|
13865
|
+
let bindGroups = createTraceBindGroups();
|
|
13866
|
+
const bvhBuildBindGroup = createWavefrontBvhBuildBindGroup({
|
|
13867
|
+
device,
|
|
13868
|
+
accelerationBindGroupLayout,
|
|
13869
|
+
bvhBuildConfigBuffer,
|
|
13870
|
+
triangleBuffer,
|
|
13871
|
+
bvhNodeBuffer,
|
|
13872
|
+
meshVertexBuffer,
|
|
13873
|
+
meshIndexBuffer,
|
|
13874
|
+
meshRangeBuffer,
|
|
13875
|
+
bvhLeafRefBuffer
|
|
13876
|
+
});
|
|
13877
|
+
const {
|
|
13878
|
+
radiance: denoiseRadianceBindGroup,
|
|
13879
|
+
resolve: denoiseResolveBindGroup,
|
|
13880
|
+
directResolve: denoiseDirectResolveBindGroup
|
|
13881
|
+
} = createWavefrontDenoiseBindGroups({
|
|
13882
|
+
device,
|
|
13883
|
+
denoiseRadianceBindGroupLayout,
|
|
13884
|
+
denoiseResolveBindGroupLayout,
|
|
13885
|
+
configBuffer,
|
|
13886
|
+
radianceView,
|
|
13887
|
+
denoiseScratchView,
|
|
13888
|
+
outputView
|
|
13889
|
+
});
|
|
13890
|
+
const presentBindGroup = createWavefrontPresentBindGroup({
|
|
13891
|
+
device,
|
|
13892
|
+
presentBindGroupLayout,
|
|
13893
|
+
outputView,
|
|
13894
|
+
sampler
|
|
13895
|
+
});
|
|
13896
|
+
const progressive = await createWavefrontProgressiveResources({ device, constants, config, configBuffer, radianceTexture, radianceView, outputView });
|
|
13897
|
+
let activeFrameTelemetry = null;
|
|
13898
|
+
let frameTelemetryResources = null;
|
|
13899
|
+
const frameEncoder = createWavefrontFrameEncoder({
|
|
13900
|
+
getConfig: () => config,
|
|
13901
|
+
progressive,
|
|
13902
|
+
getBindGroups: () => bindGroups,
|
|
13903
|
+
pipelines,
|
|
13904
|
+
counterBuffer,
|
|
13905
|
+
activeDispatchBuffer,
|
|
13906
|
+
denoiseRadianceBindGroup,
|
|
13907
|
+
denoiseResolveBindGroup,
|
|
13908
|
+
denoiseDirectResolveBindGroup,
|
|
13909
|
+
presentPipeline,
|
|
13910
|
+
presentBindGroup,
|
|
13911
|
+
context,
|
|
13912
|
+
getFrameTelemetry: () => activeFrameTelemetry
|
|
13913
|
+
});
|
|
13914
|
+
let frame = 0;
|
|
13915
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
13916
|
+
let accelerationBuildCount = 0;
|
|
13917
|
+
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA2;
|
|
13918
|
+
let lastCompletedFrameTimeMs = null;
|
|
13919
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
13920
|
+
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
13921
|
+
gpuAdapterParallelism,
|
|
13922
|
+
createGpuParallelismCounters()
|
|
13923
|
+
);
|
|
13924
|
+
const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
|
|
13925
|
+
async function readTerminationMetrics() {
|
|
13926
|
+
return readWavefrontTerminationMetrics({
|
|
13927
|
+
device,
|
|
13928
|
+
constants,
|
|
13929
|
+
counterBuffer,
|
|
13930
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2
|
|
13931
|
+
});
|
|
13932
|
+
}
|
|
13933
|
+
async function readOutputProbe(optionsForProbe = {}) {
|
|
13934
|
+
return readWavefrontOutputProbe({
|
|
13935
|
+
device,
|
|
13936
|
+
constants,
|
|
13937
|
+
config,
|
|
13938
|
+
outputTexture,
|
|
13939
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2,
|
|
13940
|
+
optionsForProbe
|
|
13941
|
+
});
|
|
13942
|
+
}
|
|
13943
|
+
async function renderFrame(renderOptions = {}) {
|
|
13944
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
13945
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
13946
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
13947
|
+
const telemetryRequested = renderOptions.readStats === true;
|
|
13948
|
+
const telemetryCanRecord = telemetryRequested && awaitGPUCompletion;
|
|
13949
|
+
const telemetryResourcesForFrame = telemetryCanRecord ? ensureFrameTelemetryResources() : null;
|
|
13950
|
+
if (telemetryResourcesForFrame?.available) {
|
|
13951
|
+
telemetryResourcesForFrame.beginFrame();
|
|
13952
|
+
activeFrameTelemetry = telemetryResourcesForFrame;
|
|
13953
|
+
}
|
|
13954
|
+
const frameStartTimeMs = nowMs3();
|
|
13955
|
+
let frameStats;
|
|
13956
|
+
try {
|
|
13957
|
+
if (useThrottledHighSamplePath) {
|
|
13958
|
+
frame += 1;
|
|
13959
|
+
const frameIndex = frame + config.frameIndex;
|
|
13960
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
13961
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13962
|
+
let frameSubmissionCount = 0;
|
|
13963
|
+
let frameConfigSlot = 0;
|
|
13964
|
+
if (accelerationBuildSubmitted) {
|
|
13965
|
+
const accelerationWaitOptions = {
|
|
13966
|
+
...estimateSubmittedGpuWorkTiming(
|
|
13967
|
+
{ ...config, renderedSamplesPerPixel: 1 },
|
|
13968
|
+
1,
|
|
13969
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
13970
|
+
{ includeAccelerationBuild: true }
|
|
13971
|
+
),
|
|
13972
|
+
allowTimeout: false
|
|
13973
|
+
};
|
|
13974
|
+
await waitForSubmittedGpuWork2(accelerationWaitOptions);
|
|
13975
|
+
}
|
|
13976
|
+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
|
|
13977
|
+
const tileRangeDispatch = dispatchFrameAwaitingGpu(
|
|
13978
|
+
frameIndex,
|
|
13979
|
+
parallelismCounters,
|
|
13980
|
+
samplingPlan.renderedSamplesPerPixel,
|
|
13435
13981
|
{
|
|
13436
|
-
|
|
13982
|
+
sampleRangeStart: 0,
|
|
13983
|
+
sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
|
|
13984
|
+
tileStartIndex: tileIndex,
|
|
13985
|
+
tileEndIndex: tileIndex + 1,
|
|
13986
|
+
startingSubmissionCount: frameSubmissionCount,
|
|
13987
|
+
startingSlot: frameConfigSlot,
|
|
13988
|
+
includeDenoise: tileIndex + 1 >= tiles.length,
|
|
13437
13989
|
includePresent: tileIndex + 1 >= tiles.length
|
|
13438
13990
|
}
|
|
13439
|
-
)
|
|
13991
|
+
);
|
|
13992
|
+
frameSubmissionCount = tileRangeDispatch.submissionCount;
|
|
13993
|
+
frameConfigSlot = tileRangeDispatch.slot;
|
|
13994
|
+
const tileWaitOptions = {
|
|
13995
|
+
...estimateSubmittedGpuWorkTiming(
|
|
13996
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13997
|
+
1,
|
|
13998
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
13999
|
+
{
|
|
14000
|
+
includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
|
|
14001
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
14002
|
+
}
|
|
14003
|
+
),
|
|
14004
|
+
allowTimeout: false
|
|
14005
|
+
};
|
|
14006
|
+
await waitForSubmittedGpuWork2(tileWaitOptions);
|
|
14007
|
+
}
|
|
14008
|
+
frameStats = createFrameStats({
|
|
14009
|
+
frameIndex,
|
|
14010
|
+
accelerationBuildSubmitted,
|
|
14011
|
+
frameSubmissionCount,
|
|
14012
|
+
parallelismCounters,
|
|
14013
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
14014
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
14015
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
14016
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
14017
|
+
});
|
|
14018
|
+
} else {
|
|
14019
|
+
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
14020
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
14021
|
+
tiles.length,
|
|
14022
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
14023
|
+
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
14024
|
+
);
|
|
14025
|
+
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
14026
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
14027
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
13440
14028
|
allowTimeout: false
|
|
14029
|
+
} : {
|
|
14030
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
14031
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13441
14032
|
};
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
accelerationBuildSubmitted,
|
|
13447
|
-
frameSubmissionCount,
|
|
13448
|
-
parallelismCounters,
|
|
13449
|
-
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13450
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13451
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13452
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13453
|
-
});
|
|
13454
|
-
} else {
|
|
13455
|
-
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
13456
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13457
|
-
tiles.length,
|
|
13458
|
-
renderOptions.submittedWorkTimeoutMs,
|
|
13459
|
-
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
13460
|
-
);
|
|
13461
|
-
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
13462
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13463
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
13464
|
-
allowTimeout: false
|
|
13465
|
-
} : {
|
|
13466
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13467
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13468
|
-
};
|
|
13469
|
-
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
13470
|
-
if (awaitGPUCompletion) {
|
|
13471
|
-
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
14033
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
14034
|
+
if (awaitGPUCompletion) {
|
|
14035
|
+
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
14036
|
+
}
|
|
13472
14037
|
}
|
|
14038
|
+
} finally {
|
|
14039
|
+
activeFrameTelemetry = null;
|
|
13473
14040
|
}
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
)
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
14041
|
+
const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
|
|
14042
|
+
if (awaitGPUCompletion) {
|
|
14043
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
14044
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
14045
|
+
}
|
|
14046
|
+
const expectedPrimaryRays = config.width * config.height * samplingPlan.renderedSamplesPerPixel;
|
|
14047
|
+
const expectedRayCounts = tiles.length * samplingPlan.renderedSamplesPerPixel * config.maxDepth;
|
|
14048
|
+
const telemetryReadback = telemetryCanRecord ? await telemetryResourcesForFrame.readFrame({
|
|
14049
|
+
expectedPrimaryRays,
|
|
14050
|
+
expectedRayCounts,
|
|
14051
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2
|
|
14052
|
+
}) : null;
|
|
14053
|
+
const rayCounts = !telemetryRequested ? createWavefrontRayCountTelemetry() : !awaitGPUCompletion ? createWavefrontRayCountTelemetry({
|
|
14054
|
+
status: "unavailable",
|
|
14055
|
+
expectedPrimaryRays,
|
|
14056
|
+
expectedRayCounts,
|
|
14057
|
+
reason: "gpu-work-not-awaited"
|
|
14058
|
+
}) : telemetryReadback.rayCounts;
|
|
14059
|
+
const timings = !telemetryRequested ? createWavefrontFrameTimingTelemetry({
|
|
14060
|
+
status: "not-requested",
|
|
14061
|
+
source: awaitGPUCompletion ? "queue-completion" : "cpu-submit",
|
|
14062
|
+
timestampQueryStatus: "not-recorded",
|
|
14063
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
14064
|
+
}) : !awaitGPUCompletion ? createWavefrontFrameTimingTelemetry({
|
|
14065
|
+
status: "unavailable",
|
|
14066
|
+
source: "cpu-submit",
|
|
14067
|
+
timestampQueryStatus: "not-recorded",
|
|
14068
|
+
totalRenderJobTimeMs: frameTimeMs,
|
|
14069
|
+
reason: "gpu-work-not-awaited"
|
|
14070
|
+
}) : telemetryReadback.timestampQueryStatus === "available" ? createWavefrontFrameTimingTelemetry({
|
|
14071
|
+
status: "available",
|
|
14072
|
+
source: "timestamp-query",
|
|
14073
|
+
timestampQueryStatus: "available",
|
|
14074
|
+
totalGpuTimeMs: telemetryReadback.totalGpuTimeMs,
|
|
14075
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
14076
|
+
}) : createWavefrontFrameTimingTelemetry({
|
|
14077
|
+
status: "fallback",
|
|
14078
|
+
source: "queue-completion",
|
|
14079
|
+
timestampQueryStatus: telemetryReadback.timestampQueryStatus,
|
|
14080
|
+
totalRenderJobTimeMs: frameTimeMs,
|
|
14081
|
+
reason: telemetryReadback.reason
|
|
14082
|
+
});
|
|
14083
|
+
const deviceLossStatus = awaitGPUCompletion ? "not-detected" : typeof device.lost?.then === "function" ? "pending" : "not-exposed";
|
|
14084
|
+
frameStats = Object.freeze({
|
|
14085
|
+
...frameStats,
|
|
14086
|
+
deviceLossStatus,
|
|
14087
|
+
secondaryRays: rayCounts.status === "available" ? rayCounts.secondaryRays : null,
|
|
14088
|
+
totalPathSegments: rayCounts.status === "available" ? rayCounts.totalPathSegments : null,
|
|
14089
|
+
rayCounts,
|
|
14090
|
+
timings,
|
|
14091
|
+
telemetryMemoryBytes: telemetryResourcesForFrame?.memoryBytes ?? 0,
|
|
14092
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
14093
|
+
frameStats.gpuParallelism,
|
|
14094
|
+
frameStats.commandSubmissions,
|
|
14095
|
+
frameTimeMs,
|
|
14096
|
+
awaitGPUCompletion
|
|
14097
|
+
)
|
|
14098
|
+
});
|
|
14099
|
+
const terminationMetrics = awaitGPUCompletion && renderOptions.readStats === true ? await readTerminationMetrics() : EMPTY_TERMINATION_METRICS;
|
|
14100
|
+
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
14101
|
+
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
14102
|
+
const completedFrame = Object.freeze({
|
|
14103
|
+
...frameStats,
|
|
14104
|
+
outputProbe: probe ? Object.freeze({
|
|
14105
|
+
...probe,
|
|
14106
|
+
sampledPixels: 1,
|
|
14107
|
+
nonZeroSamples: maxChannel > 0 ? 1 : 0,
|
|
14108
|
+
maxChannel
|
|
14109
|
+
}) : null,
|
|
14110
|
+
bounces: [],
|
|
14111
|
+
termination: terminationMetrics.termination,
|
|
14112
|
+
terminalRadiance: terminationMetrics.terminalRadiance,
|
|
14113
|
+
radianceDiagnostics: terminationMetrics.radianceDiagnostics,
|
|
14114
|
+
transportContributions: terminationMetrics.transportContributions,
|
|
14115
|
+
queueOverflow: terminationMetrics.queueOverflow
|
|
14116
|
+
});
|
|
14117
|
+
return Object.freeze({
|
|
14118
|
+
...completedFrame,
|
|
14119
|
+
transportGuardrails: createWavefrontTransportGuardrailSummary(completedFrame)
|
|
14120
|
+
});
|
|
14121
|
+
}
|
|
14122
|
+
await finishAllocationScopes();
|
|
13553
14123
|
return Object.freeze({
|
|
13554
|
-
...completedFrame,
|
|
13555
|
-
transportGuardrails: createWavefrontTransportGuardrailSummary(completedFrame)
|
|
13556
|
-
});
|
|
13557
|
-
}
|
|
13558
|
-
function rebuildLiveConfig(overrides = {}) {
|
|
13559
|
-
return createWavefrontPathTracingComputeConfig({
|
|
13560
|
-
...options,
|
|
13561
14124
|
canvas,
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
featureFlags: options.featureFlags,
|
|
13575
|
-
frameIndex: config.frameIndex,
|
|
13576
|
-
...overrides
|
|
14125
|
+
context,
|
|
14126
|
+
device: rawDevice,
|
|
14127
|
+
format,
|
|
14128
|
+
config,
|
|
14129
|
+
renderOnce,
|
|
14130
|
+
renderFrame,
|
|
14131
|
+
readOutputProbe,
|
|
14132
|
+
updateSceneObjects,
|
|
14133
|
+
updateCamera,
|
|
14134
|
+
resetAccumulation: () => progressive?.reset(),
|
|
14135
|
+
getSnapshot,
|
|
14136
|
+
destroy
|
|
13577
14137
|
});
|
|
13578
|
-
}
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
bindGroups = createTraceBindGroups();
|
|
13583
|
-
if (previousMediumTextureResource?.ownsTexture) {
|
|
13584
|
-
previousMediumTextureResource.texture?.destroy?.();
|
|
13585
|
-
}
|
|
13586
|
-
}
|
|
13587
|
-
function updateSceneObjects(sceneObjects) {
|
|
13588
|
-
progressive?.reset();
|
|
13589
|
-
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
13590
|
-
packedScene = nextPackedScene;
|
|
13591
|
-
const nextConfig = rebuildLiveConfig();
|
|
13592
|
-
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
13593
|
-
rebuildMediumResources(nextConfig);
|
|
14138
|
+
} catch (error) {
|
|
14139
|
+
try {
|
|
14140
|
+
await finishAllocationScopes?.();
|
|
14141
|
+
} catch {
|
|
13594
14142
|
}
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
function updateCamera(cameraOptions = {}) {
|
|
13600
|
-
progressive?.reset();
|
|
13601
|
-
activeCameraOptions = cameraOptions;
|
|
13602
|
-
config = updateWavefrontCameraConfig(config, cameraOptions);
|
|
13603
|
-
return config;
|
|
13604
|
-
}
|
|
13605
|
-
function getSnapshot() {
|
|
13606
|
-
return Object.freeze({
|
|
13607
|
-
frame,
|
|
13608
|
-
accumulatedSamplesPerPixel: progressive?.samples ?? 0,
|
|
13609
|
-
width: config.width,
|
|
13610
|
-
height: config.height,
|
|
13611
|
-
maxDepth: config.maxDepth,
|
|
13612
|
-
tiles: tiles.length,
|
|
13613
|
-
tileSize: config.tileSize,
|
|
13614
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
13615
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
13616
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
13617
|
-
triangleCount: config.triangleCount,
|
|
13618
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
13619
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
13620
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
13621
|
-
mediumCount: config.mediumCount,
|
|
13622
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
13623
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
13624
|
-
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13625
|
-
transportExperiments: config.transportExperiments,
|
|
13626
|
-
transportExperimentFlags: config.transportExperimentFlags,
|
|
13627
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
13628
|
-
displayQuality: config.displayQuality,
|
|
13629
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
13630
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
13631
|
-
accelerationBuilt,
|
|
13632
|
-
accelerationBuildCount,
|
|
13633
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
13634
|
-
gpuParallelism: lastGpuParallelism,
|
|
13635
|
-
memory: config.memory
|
|
13636
|
-
});
|
|
13637
|
-
}
|
|
13638
|
-
function destroy() {
|
|
13639
|
-
progressive?.destroy();
|
|
13640
|
-
frameTelemetryResources?.destroy?.();
|
|
13641
|
-
activeQueue.destroy?.();
|
|
13642
|
-
nextQueue.destroy?.();
|
|
13643
|
-
hitBuffer.destroy?.();
|
|
13644
|
-
accumulationBuffer.destroy?.();
|
|
13645
|
-
pathVertexBuffer.destroy?.();
|
|
13646
|
-
sceneObjectBuffer.destroy?.();
|
|
13647
|
-
triangleBuffer.destroy?.();
|
|
13648
|
-
bvhNodeBuffer.destroy?.();
|
|
13649
|
-
meshVertexBuffer.destroy?.();
|
|
13650
|
-
meshIndexBuffer.destroy?.();
|
|
13651
|
-
meshRangeBuffer.destroy?.();
|
|
13652
|
-
environmentPortalBuffer.destroy?.();
|
|
13653
|
-
bvhLeafRefBuffer.destroy?.();
|
|
13654
|
-
configBuffer.destroy?.();
|
|
13655
|
-
bvhBuildConfigBuffer.destroy?.();
|
|
13656
|
-
counterBuffer.destroy?.();
|
|
13657
|
-
activeDispatchBuffer.destroy?.();
|
|
13658
|
-
radianceTexture.destroy?.();
|
|
13659
|
-
denoiseScratchTexture.destroy?.();
|
|
13660
|
-
outputTexture.destroy?.();
|
|
13661
|
-
if (environmentMapResource.ownsTexture) {
|
|
13662
|
-
environmentMapResource.texture?.destroy?.();
|
|
13663
|
-
}
|
|
13664
|
-
if (environmentSamplingResource.ownsTexture) {
|
|
13665
|
-
environmentSamplingResource.texture?.destroy?.();
|
|
13666
|
-
}
|
|
13667
|
-
if (mediumTextureResource.ownsTexture) {
|
|
13668
|
-
mediumTextureResource.texture?.destroy?.();
|
|
13669
|
-
}
|
|
13670
|
-
brdfLutResource.texture?.destroy?.();
|
|
13671
|
-
if (baseColorAtlasResource.ownsTexture) {
|
|
13672
|
-
baseColorAtlasResource.texture?.destroy?.();
|
|
13673
|
-
}
|
|
13674
|
-
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
13675
|
-
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
13676
|
-
}
|
|
13677
|
-
if (normalAtlasResource.ownsTexture) {
|
|
13678
|
-
normalAtlasResource.texture?.destroy?.();
|
|
13679
|
-
}
|
|
13680
|
-
if (occlusionAtlasResource.ownsTexture) {
|
|
13681
|
-
occlusionAtlasResource.texture?.destroy?.();
|
|
13682
|
-
}
|
|
13683
|
-
if (emissiveAtlasResource.ownsTexture) {
|
|
13684
|
-
emissiveAtlasResource.texture?.destroy?.();
|
|
13685
|
-
}
|
|
13686
|
-
Object.values(extensionAtlasResources).forEach((resource) => {
|
|
13687
|
-
if (resource.ownsTexture) {
|
|
13688
|
-
resource.texture?.destroy?.();
|
|
13689
|
-
}
|
|
13690
|
-
});
|
|
13691
|
-
context.unconfigure?.();
|
|
14143
|
+
context?.unconfigure?.();
|
|
14144
|
+
allocationTracker.destroy();
|
|
14145
|
+
rawDevice.destroy?.();
|
|
14146
|
+
throw error;
|
|
13692
14147
|
}
|
|
13693
|
-
return Object.freeze({
|
|
13694
|
-
canvas,
|
|
13695
|
-
context,
|
|
13696
|
-
device,
|
|
13697
|
-
format,
|
|
13698
|
-
config,
|
|
13699
|
-
renderOnce,
|
|
13700
|
-
renderFrame,
|
|
13701
|
-
readOutputProbe,
|
|
13702
|
-
updateSceneObjects,
|
|
13703
|
-
updateCamera,
|
|
13704
|
-
resetAccumulation: () => progressive?.reset(),
|
|
13705
|
-
getSnapshot,
|
|
13706
|
-
destroy
|
|
13707
|
-
});
|
|
13708
14148
|
}
|
|
13709
14149
|
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
13710
14150
|
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|