@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.js
CHANGED
|
@@ -1558,14 +1558,6 @@ function textureSample(binding, textures, label, extra = {}) {
|
|
|
1558
1558
|
}
|
|
1559
1559
|
function cloneMaterialExtensions(material, textures) {
|
|
1560
1560
|
const extensions = { ...material.extensions ?? {} };
|
|
1561
|
-
if (material.workflow === "unlit") {
|
|
1562
|
-
const unlitKey = "KHR_materials_unlit";
|
|
1563
|
-
const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
|
|
1564
|
-
if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
|
|
1565
|
-
throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
|
|
1566
|
-
}
|
|
1567
|
-
extensions[unlitKey] = { ...unlitValue, enabled: true };
|
|
1568
|
-
}
|
|
1569
1561
|
for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
|
|
1570
1562
|
const binding = material.textures?.[bindingName];
|
|
1571
1563
|
if (!binding) {
|
|
@@ -1590,7 +1582,10 @@ function mapMaterial(material, materialRefId, textures) {
|
|
|
1590
1582
|
if (material.alphaMode === "mask") {
|
|
1591
1583
|
throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
|
|
1592
1584
|
}
|
|
1593
|
-
if (material.workflow
|
|
1585
|
+
if (material.workflow === "unlit" || Object.hasOwn(material.extensions ?? {}, "KHR_materials_unlit") || Object.hasOwn(material.extensions ?? {}, "unlit")) {
|
|
1586
|
+
throw new Error("Canonical unlit materials are unsupported by WavefrontMeshInput; retain an unlit-capable rendering path.");
|
|
1587
|
+
}
|
|
1588
|
+
if (material.workflow !== "metallic-roughness") {
|
|
1594
1589
|
throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
|
|
1595
1590
|
}
|
|
1596
1591
|
const mapped = {
|
|
@@ -3971,948 +3966,1289 @@ function boundsCentroid(bounds) {
|
|
|
3971
3966
|
];
|
|
3972
3967
|
}
|
|
3973
3968
|
|
|
3974
|
-
// src/wavefront-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
const
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
const
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
),
|
|
3988
|
-
normal
|
|
3989
|
-
);
|
|
3990
|
-
}
|
|
3991
|
-
function radicalInverseVdc(bits) {
|
|
3992
|
-
let value = bits >>> 0;
|
|
3993
|
-
value = (value << 16 | value >>> 16) >>> 0;
|
|
3994
|
-
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
3995
|
-
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
3996
|
-
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
3997
|
-
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
3998
|
-
return value * 23283064365386963e-26;
|
|
3999
|
-
}
|
|
4000
|
-
function hammersley(index, count) {
|
|
4001
|
-
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
4002
|
-
}
|
|
4003
|
-
function importanceSampleGgx(sample, roughness, normal) {
|
|
4004
|
-
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
4005
|
-
const phi = 2 * Math.PI * sample[0];
|
|
4006
|
-
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
4007
|
-
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
4008
|
-
const halfVector = localToWorld(
|
|
4009
|
-
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
4010
|
-
normal
|
|
4011
|
-
);
|
|
4012
|
-
return normalize(halfVector, normal);
|
|
4013
|
-
}
|
|
4014
|
-
function geometrySchlickGgx(nDotV, roughness) {
|
|
4015
|
-
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
4016
|
-
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
4017
|
-
}
|
|
4018
|
-
function geometrySmith(nDotV, nDotL, roughness) {
|
|
4019
|
-
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
3969
|
+
// src/wavefront-runtime-support.js
|
|
3970
|
+
var WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 128 * 1024 * 1024;
|
|
3971
|
+
var WEBGPU_DEFAULT_MAX_BUFFER_SIZE = 256 * 1024 * 1024;
|
|
3972
|
+
function readRequestedDeviceLimit(source, name) {
|
|
3973
|
+
const rawValue = source?.[name];
|
|
3974
|
+
if (rawValue === void 0 || rawValue === null) {
|
|
3975
|
+
return 0;
|
|
3976
|
+
}
|
|
3977
|
+
const value = Number(rawValue);
|
|
3978
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
3979
|
+
throw new Error(`${name} must be a non-negative safe integer.`);
|
|
3980
|
+
}
|
|
3981
|
+
return value;
|
|
4020
3982
|
}
|
|
4021
|
-
function
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
4032
|
-
normal
|
|
3983
|
+
function assertAdapterSupportsRequiredLimit(adapter, name, requiredValue) {
|
|
3984
|
+
const availableValue = Number(adapter?.limits?.[name]);
|
|
3985
|
+
if (!Number.isFinite(availableValue)) {
|
|
3986
|
+
throw new Error(
|
|
3987
|
+
`Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter does not expose that limit.`
|
|
3988
|
+
);
|
|
3989
|
+
}
|
|
3990
|
+
if (availableValue < requiredValue) {
|
|
3991
|
+
throw new Error(
|
|
3992
|
+
`Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter exposes ${availableValue} bytes.`
|
|
4033
3993
|
);
|
|
4034
|
-
const nDotL = Math.max(lightDirection[2], 0);
|
|
4035
|
-
const nDotH = Math.max(halfVector[2], 0);
|
|
4036
|
-
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
4037
|
-
continue;
|
|
4038
|
-
}
|
|
4039
|
-
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
4040
|
-
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
4041
|
-
const fresnel2 = (1 - vDotH) ** 5;
|
|
4042
|
-
scaleTerm += (1 - fresnel2) * visibility;
|
|
4043
|
-
biasTerm += fresnel2 * visibility;
|
|
4044
3994
|
}
|
|
4045
|
-
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4046
3995
|
}
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
4051
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4052
|
-
return config.tileSize;
|
|
3996
|
+
function getGpuUsageConstants() {
|
|
3997
|
+
if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
|
|
3998
|
+
throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
|
|
4053
3999
|
}
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
const maxPixelsByPathVertex = Math.floor(
|
|
4057
|
-
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
4058
|
-
);
|
|
4059
|
-
const maxPixels = Math.max(
|
|
4060
|
-
256,
|
|
4061
|
-
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
4062
|
-
);
|
|
4063
|
-
if (config.tilePixelCapacity <= maxPixels) {
|
|
4064
|
-
return config.tileSize;
|
|
4000
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
4001
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
4065
4002
|
}
|
|
4066
|
-
return
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
usage
|
|
4073
|
-
});
|
|
4074
|
-
}
|
|
4075
|
-
function alignTo(value, alignment) {
|
|
4076
|
-
const resolvedAlignment = Math.max(1, alignment);
|
|
4077
|
-
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
4003
|
+
return {
|
|
4004
|
+
buffer: GPUBufferUsage,
|
|
4005
|
+
texture: GPUTextureUsage,
|
|
4006
|
+
shader: GPUShaderStage,
|
|
4007
|
+
map: typeof GPUMapMode === "undefined" ? null : GPUMapMode
|
|
4008
|
+
};
|
|
4078
4009
|
}
|
|
4079
|
-
function
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
let exponent = x >> 23 & 255;
|
|
4087
|
-
if (exponent === 255) {
|
|
4088
|
-
return sign | (mantissa ? 32256 : 31744);
|
|
4010
|
+
function resolveCanvas3(canvasOrSelector, documentRef = globalThis.document) {
|
|
4011
|
+
if (typeof canvasOrSelector === "string") {
|
|
4012
|
+
const resolved = documentRef?.querySelector?.(canvasOrSelector);
|
|
4013
|
+
if (!resolved) {
|
|
4014
|
+
throw new Error(`Unable to find canvas for selector: ${canvasOrSelector}`);
|
|
4015
|
+
}
|
|
4016
|
+
return resolved;
|
|
4089
4017
|
}
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
return sign | 31744;
|
|
4018
|
+
if (canvasOrSelector?.getContext) {
|
|
4019
|
+
return canvasOrSelector;
|
|
4093
4020
|
}
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
}
|
|
4098
|
-
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4099
|
-
return sign | mantissa + 4096 >> 13;
|
|
4021
|
+
const fallback = documentRef?.querySelector?.("canvas[data-plasius-wavefront-path-tracing]");
|
|
4022
|
+
if (!fallback) {
|
|
4023
|
+
throw new Error("A canvas is required for WebGPU wavefront path tracing.");
|
|
4100
4024
|
}
|
|
4101
|
-
return
|
|
4025
|
+
return fallback;
|
|
4102
4026
|
}
|
|
4103
|
-
function
|
|
4104
|
-
if (
|
|
4105
|
-
return
|
|
4027
|
+
async function getPipelineDiagnostics(shaderModule) {
|
|
4028
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4029
|
+
return "";
|
|
4106
4030
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4031
|
+
try {
|
|
4032
|
+
const info = await shaderModule.getCompilationInfo();
|
|
4033
|
+
const messages = info.messages ?? [];
|
|
4034
|
+
if (messages.length === 0) {
|
|
4035
|
+
return "";
|
|
4036
|
+
}
|
|
4037
|
+
return messages.map((message) => {
|
|
4038
|
+
const line = message.lineNum ?? "?";
|
|
4039
|
+
const column = message.linePos ?? "?";
|
|
4040
|
+
return `line ${line}:${column} ${message.message}`;
|
|
4041
|
+
}).join("\n");
|
|
4042
|
+
} catch {
|
|
4043
|
+
return "";
|
|
4109
4044
|
}
|
|
4110
|
-
return 1;
|
|
4111
4045
|
}
|
|
4112
|
-
function
|
|
4113
|
-
|
|
4114
|
-
|
|
4046
|
+
async function createComputePipeline(device, shaderModule, layout, entryPoint, label) {
|
|
4047
|
+
const descriptor = {
|
|
4048
|
+
label,
|
|
4049
|
+
layout,
|
|
4050
|
+
compute: {
|
|
4051
|
+
module: shaderModule,
|
|
4052
|
+
entryPoint
|
|
4053
|
+
}
|
|
4054
|
+
};
|
|
4055
|
+
try {
|
|
4056
|
+
if (typeof device.createComputePipelineAsync === "function") {
|
|
4057
|
+
return await device.createComputePipelineAsync(descriptor);
|
|
4058
|
+
}
|
|
4059
|
+
return device.createComputePipeline(descriptor);
|
|
4060
|
+
} catch (error) {
|
|
4061
|
+
const diagnostics = await getPipelineDiagnostics(shaderModule);
|
|
4062
|
+
const suffix = diagnostics ? `
|
|
4063
|
+
${diagnostics}` : "";
|
|
4064
|
+
throw new Error(`WGSL compilation failed for ${label}: ${error.message}${suffix}`, {
|
|
4065
|
+
cause: error
|
|
4066
|
+
});
|
|
4115
4067
|
}
|
|
4116
|
-
const width = Math.max(1, environmentMap.width ?? 1);
|
|
4117
|
-
const height = Math.max(1, environmentMap.height ?? 1);
|
|
4118
|
-
return environmentMap.data.length >= width * height * 4;
|
|
4119
4068
|
}
|
|
4120
|
-
function
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
const bytesPerRow = alignTo(width * 4, 256);
|
|
4124
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4125
|
-
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
4126
|
-
for (let y = 0; y < height; y += 1) {
|
|
4127
|
-
const sourceOffset = y * width * 4;
|
|
4128
|
-
const targetOffset = y * bytesPerRow;
|
|
4129
|
-
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
4069
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
4070
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4071
|
+
return;
|
|
4130
4072
|
}
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
});
|
|
4137
|
-
}
|
|
4138
|
-
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
4139
|
-
if (!data || index >= data.length) {
|
|
4140
|
-
return fallback;
|
|
4073
|
+
const info = await shaderModule.getCompilationInfo();
|
|
4074
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
4075
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
4076
|
+
if (errors.length <= 0) {
|
|
4077
|
+
return;
|
|
4141
4078
|
}
|
|
4142
|
-
const
|
|
4143
|
-
|
|
4079
|
+
const diagnostics = errors.map((message) => {
|
|
4080
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
4081
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
4082
|
+
return `line ${line}:${column} ${message.message}`;
|
|
4083
|
+
}).join("\n");
|
|
4084
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
4085
|
+
${diagnostics}`);
|
|
4144
4086
|
}
|
|
4145
|
-
function
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
if (cached) {
|
|
4149
|
-
return cached;
|
|
4150
|
-
}
|
|
4151
|
-
const width = Math.max(1, Math.trunc(size));
|
|
4152
|
-
const height = Math.max(1, Math.trunc(size));
|
|
4153
|
-
const rowBytes = width * 8;
|
|
4154
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4155
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4156
|
-
const view = new DataView(bytes.buffer);
|
|
4157
|
-
for (let y = 0; y < height; y += 1) {
|
|
4158
|
-
const roughness = (y + 0.5) / height;
|
|
4159
|
-
for (let x = 0; x < width; x += 1) {
|
|
4160
|
-
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
4161
|
-
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
4162
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4163
|
-
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
4164
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
4165
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
4166
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4170
|
-
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4171
|
-
return upload;
|
|
4172
|
-
}
|
|
4173
|
-
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
4174
|
-
const width = Math.max(1, environmentMap.width);
|
|
4175
|
-
const height = Math.max(1, environmentMap.height);
|
|
4176
|
-
const pixels = new Float32Array(width * height * 4);
|
|
4177
|
-
const data = environmentMap.data;
|
|
4178
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
4179
|
-
for (let index = 0; index < width * height; index += 1) {
|
|
4180
|
-
const sourceOffset = index * 4;
|
|
4181
|
-
const targetOffset = index * 4;
|
|
4182
|
-
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
4183
|
-
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
4184
|
-
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
4185
|
-
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
4087
|
+
async function createRenderPipeline(device, descriptor) {
|
|
4088
|
+
if (typeof device.createRenderPipelineAsync === "function") {
|
|
4089
|
+
return device.createRenderPipelineAsync(descriptor);
|
|
4186
4090
|
}
|
|
4187
|
-
return
|
|
4188
|
-
}
|
|
4189
|
-
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
4190
|
-
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
4191
|
-
const theta = v * Math.PI;
|
|
4192
|
-
const sinTheta = Math.sin(theta);
|
|
4193
|
-
return [
|
|
4194
|
-
Math.cos(angle) * sinTheta,
|
|
4195
|
-
Math.cos(theta),
|
|
4196
|
-
Math.sin(angle) * sinTheta
|
|
4197
|
-
];
|
|
4091
|
+
return device.createRenderPipeline(descriptor);
|
|
4198
4092
|
}
|
|
4199
|
-
function
|
|
4200
|
-
const
|
|
4201
|
-
const
|
|
4202
|
-
const
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
const y0 = clamp2(Math.floor(y), 0, height - 1);
|
|
4206
|
-
const x1 = (x0 + 1) % width;
|
|
4207
|
-
const y1 = clamp2(y0 + 1, 0, height - 1);
|
|
4208
|
-
const tx = x - Math.floor(x);
|
|
4209
|
-
const ty = y - Math.floor(y);
|
|
4210
|
-
const read = (px, py) => {
|
|
4211
|
-
const offset = (py * width + px) * 4;
|
|
4212
|
-
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
4093
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}, memory = null) {
|
|
4094
|
+
const descriptor = { ...options.deviceDescriptor ?? {} };
|
|
4095
|
+
const descriptorRequiredLimits = { ...descriptor.requiredLimits ?? {} };
|
|
4096
|
+
const requiredLimits = {
|
|
4097
|
+
...descriptorRequiredLimits,
|
|
4098
|
+
...options.requiredLimits ?? {}
|
|
4213
4099
|
};
|
|
4214
|
-
const
|
|
4215
|
-
const
|
|
4216
|
-
const
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
4223
|
-
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
4224
|
-
];
|
|
4225
|
-
}
|
|
4226
|
-
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
4227
|
-
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
4228
|
-
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
4229
|
-
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
4230
|
-
const v = Math.acos(clamp2(unitDirection[1], -1, 1)) / Math.PI;
|
|
4231
|
-
return [u, clamp2(v, 0, 1)];
|
|
4232
|
-
}
|
|
4233
|
-
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
4234
|
-
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
4235
|
-
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
4236
|
-
}
|
|
4237
|
-
function createFloat16RgbaUploadFromLevels(levels) {
|
|
4238
|
-
return levels.map((level) => {
|
|
4239
|
-
const rowBytes = level.width * 8;
|
|
4240
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4241
|
-
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
4242
|
-
const view = new DataView(bytes.buffer);
|
|
4243
|
-
for (let y = 0; y < level.height; y += 1) {
|
|
4244
|
-
for (let x = 0; x < level.width; x += 1) {
|
|
4245
|
-
const sourceOffset = (y * level.width + x) * 4;
|
|
4246
|
-
const targetOffset = y * bytesPerRow + x * 8;
|
|
4247
|
-
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
4248
|
-
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
4249
|
-
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
4250
|
-
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
4251
|
-
}
|
|
4100
|
+
const traceStorageBindings = TRACE_STORAGE_BUFFER_BINDINGS;
|
|
4101
|
+
const traceTextureBindings = TRACE_SAMPLED_TEXTURE_BINDINGS + (options.instances !== void 0 ? 1 : 0);
|
|
4102
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
4103
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
4104
|
+
if (exposedStorageBufferLimit < traceStorageBindings) {
|
|
4105
|
+
throw new Error(
|
|
4106
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${traceStorageBindings}, but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
4107
|
+
);
|
|
4252
4108
|
}
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
const
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
height: sourceHeight,
|
|
4265
|
-
data: sourcePixels
|
|
4266
|
-
})
|
|
4267
|
-
];
|
|
4268
|
-
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
4269
|
-
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
4270
|
-
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
4271
|
-
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
4272
|
-
const data = new Float32Array(width * height * 4);
|
|
4273
|
-
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
4274
|
-
for (let y = 0; y < height; y += 1) {
|
|
4275
|
-
for (let x = 0; x < width; x += 1) {
|
|
4276
|
-
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
4277
|
-
const normal = normalize(direction, [0, 1, 0]);
|
|
4278
|
-
const viewDirection = normal;
|
|
4279
|
-
let totalWeight = 0;
|
|
4280
|
-
const accum = [0, 0, 0];
|
|
4281
|
-
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
4282
|
-
const xi = hammersley(sampleIndex, sampleCount);
|
|
4283
|
-
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4284
|
-
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
4285
|
-
const lightDirection = normalize(
|
|
4286
|
-
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
4287
|
-
normal
|
|
4288
|
-
);
|
|
4289
|
-
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
4290
|
-
if (nDotL <= 1e-6) {
|
|
4291
|
-
continue;
|
|
4292
|
-
}
|
|
4293
|
-
const radiance = sampleEnvironmentRadiance(
|
|
4294
|
-
sourcePixels,
|
|
4295
|
-
sourceWidth,
|
|
4296
|
-
sourceHeight,
|
|
4297
|
-
lightDirection,
|
|
4298
|
-
environmentMap.rotationRadians
|
|
4299
|
-
);
|
|
4300
|
-
accum[0] += radiance[0] * nDotL;
|
|
4301
|
-
accum[1] += radiance[1] * nDotL;
|
|
4302
|
-
accum[2] += radiance[2] * nDotL;
|
|
4303
|
-
totalWeight += nDotL;
|
|
4304
|
-
}
|
|
4305
|
-
const offset = (y * width + x) * 4;
|
|
4306
|
-
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
4307
|
-
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
4308
|
-
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
4309
|
-
data[offset + 3] = 1;
|
|
4310
|
-
}
|
|
4109
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
4110
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
4111
|
+
traceStorageBindings
|
|
4112
|
+
);
|
|
4113
|
+
}
|
|
4114
|
+
const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
|
|
4115
|
+
if (Number.isFinite(exposedSampledTextureLimit)) {
|
|
4116
|
+
if (exposedSampledTextureLimit < traceTextureBindings) {
|
|
4117
|
+
throw new Error(
|
|
4118
|
+
`Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${traceTextureBindings}, but this adapter exposes ${exposedSampledTextureLimit}.`
|
|
4119
|
+
);
|
|
4311
4120
|
}
|
|
4312
|
-
|
|
4121
|
+
requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
|
|
4122
|
+
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4123
|
+
traceTextureBindings
|
|
4124
|
+
);
|
|
4313
4125
|
}
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
if (
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
hasImportanceData: false
|
|
4330
|
-
});
|
|
4126
|
+
const sceneStorageBufferBindingBytes = Number(
|
|
4127
|
+
memory?.sceneStorageBufferBindingBytes ?? 0
|
|
4128
|
+
);
|
|
4129
|
+
const requestedStorageBufferBindingSize = Math.max(
|
|
4130
|
+
readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
|
|
4131
|
+
readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
|
|
4132
|
+
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
|
|
4133
|
+
);
|
|
4134
|
+
if (requestedStorageBufferBindingSize > 0) {
|
|
4135
|
+
assertAdapterSupportsRequiredLimit(
|
|
4136
|
+
adapter,
|
|
4137
|
+
"maxStorageBufferBindingSize",
|
|
4138
|
+
requestedStorageBufferBindingSize
|
|
4139
|
+
);
|
|
4140
|
+
requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
|
|
4331
4141
|
}
|
|
4332
|
-
const
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
const theta = (y + 0.5) / height * Math.PI;
|
|
4342
|
-
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
4343
|
-
let rowWeight = 0;
|
|
4344
|
-
for (let x = 0; x < width; x += 1) {
|
|
4345
|
-
const offset = (y * width + x) * 4;
|
|
4346
|
-
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
4347
|
-
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
4348
|
-
pdf[y * width + x] = weight;
|
|
4349
|
-
rowWeight += weight;
|
|
4350
|
-
conditionalCdf[y * width + x] = rowWeight;
|
|
4351
|
-
}
|
|
4352
|
-
rowSums[y] = rowWeight;
|
|
4353
|
-
totalWeight += rowWeight;
|
|
4354
|
-
if (rowWeight > 0) {
|
|
4355
|
-
for (let x = 0; x < width; x += 1) {
|
|
4356
|
-
conditionalCdf[y * width + x] /= rowWeight;
|
|
4357
|
-
}
|
|
4358
|
-
} else {
|
|
4359
|
-
for (let x = 0; x < width; x += 1) {
|
|
4360
|
-
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
4361
|
-
}
|
|
4362
|
-
}
|
|
4363
|
-
marginalCdf[y] = totalWeight;
|
|
4142
|
+
const requestedMaxBufferSize = Math.max(
|
|
4143
|
+
readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
|
|
4144
|
+
readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
|
|
4145
|
+
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
|
|
4146
|
+
requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
|
|
4147
|
+
);
|
|
4148
|
+
if (requestedMaxBufferSize > 0) {
|
|
4149
|
+
assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
|
|
4150
|
+
requiredLimits.maxBufferSize = requestedMaxBufferSize;
|
|
4364
4151
|
}
|
|
4365
|
-
|
|
4366
|
-
|
|
4152
|
+
const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
|
|
4153
|
+
const adapterFeatures = adapter?.features;
|
|
4154
|
+
const timestampQuerySupported = typeof adapterFeatures?.has === "function" ? adapterFeatures.has("timestamp-query") : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function" ? Array.from(adapterFeatures).includes("timestamp-query") : false;
|
|
4155
|
+
if (options.gpuTimestamps !== false && timestampQuerySupported && !requiredFeatures.includes("timestamp-query")) {
|
|
4156
|
+
requiredFeatures.push("timestamp-query");
|
|
4367
4157
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4158
|
+
if (requiredFeatures.length > 0) {
|
|
4159
|
+
descriptor.requiredFeatures = requiredFeatures;
|
|
4370
4160
|
}
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
conditionalCdf,
|
|
4377
|
-
hasImportanceData: true
|
|
4378
|
-
});
|
|
4379
|
-
}
|
|
4380
|
-
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
4381
|
-
if (environmentMap.view) {
|
|
4382
|
-
return Object.freeze({
|
|
4383
|
-
view: environmentMap.view,
|
|
4384
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4385
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4386
|
-
addressModeU: "repeat",
|
|
4387
|
-
addressModeV: "clamp-to-edge",
|
|
4388
|
-
magFilter: "linear",
|
|
4389
|
-
minFilter: "linear",
|
|
4390
|
-
mipmapFilter: "linear"
|
|
4391
|
-
}),
|
|
4392
|
-
texture: null,
|
|
4393
|
-
ownsTexture: false,
|
|
4394
|
-
width: Math.max(1, environmentMap.width),
|
|
4395
|
-
height: Math.max(1, environmentMap.height),
|
|
4396
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4397
|
-
});
|
|
4161
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
4162
|
+
descriptor.requiredLimits = {
|
|
4163
|
+
...descriptor.requiredLimits ?? {},
|
|
4164
|
+
...requiredLimits
|
|
4165
|
+
};
|
|
4398
4166
|
}
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
addressModeV: "clamp-to-edge",
|
|
4406
|
-
magFilter: "linear",
|
|
4407
|
-
minFilter: "linear",
|
|
4408
|
-
mipmapFilter: "linear"
|
|
4409
|
-
}),
|
|
4410
|
-
texture: environmentMap.texture,
|
|
4411
|
-
ownsTexture: false,
|
|
4412
|
-
width: Math.max(1, environmentMap.width),
|
|
4413
|
-
height: Math.max(1, environmentMap.height),
|
|
4414
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4415
|
-
});
|
|
4167
|
+
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
4168
|
+
}
|
|
4169
|
+
function readGpuLimit(adapter, device, name) {
|
|
4170
|
+
const adapterValue = Number(adapter?.limits?.[name]);
|
|
4171
|
+
if (Number.isFinite(adapterValue)) {
|
|
4172
|
+
return adapterValue;
|
|
4416
4173
|
}
|
|
4417
|
-
const
|
|
4418
|
-
|
|
4419
|
-
const texture = device.createTexture({
|
|
4420
|
-
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
4421
|
-
size: { width: prefiltered.width, height: prefiltered.height },
|
|
4422
|
-
format: "rgba16float",
|
|
4423
|
-
mipLevelCount: prefiltered.mipLevelCount,
|
|
4424
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4425
|
-
});
|
|
4426
|
-
uploads.forEach((upload, mipLevel) => {
|
|
4427
|
-
device.queue.writeTexture(
|
|
4428
|
-
{ texture, mipLevel },
|
|
4429
|
-
upload.bytes,
|
|
4430
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4431
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4432
|
-
);
|
|
4433
|
-
});
|
|
4434
|
-
return Object.freeze({
|
|
4435
|
-
view: texture.createView(),
|
|
4436
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4437
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4438
|
-
addressModeU: "repeat",
|
|
4439
|
-
addressModeV: "clamp-to-edge",
|
|
4440
|
-
magFilter: "linear",
|
|
4441
|
-
minFilter: "linear",
|
|
4442
|
-
mipmapFilter: "linear"
|
|
4443
|
-
}),
|
|
4444
|
-
texture,
|
|
4445
|
-
ownsTexture: true,
|
|
4446
|
-
width: prefiltered.width,
|
|
4447
|
-
height: prefiltered.height,
|
|
4448
|
-
mipLevelCount: prefiltered.mipLevelCount
|
|
4449
|
-
});
|
|
4174
|
+
const deviceValue = Number(device?.limits?.[name]);
|
|
4175
|
+
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
4450
4176
|
}
|
|
4451
|
-
function
|
|
4452
|
-
const
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
4456
|
-
const view = new DataView(bytes.buffer);
|
|
4457
|
-
for (let y = 0; y < tables.height; y += 1) {
|
|
4458
|
-
for (let x = 0; x < tables.width; x += 1) {
|
|
4459
|
-
const probability = tables.pdf[y * tables.width + x];
|
|
4460
|
-
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
4461
|
-
const marginal = tables.marginalCdf[y];
|
|
4462
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4463
|
-
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
4464
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
4465
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
4466
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4467
|
-
}
|
|
4177
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
4178
|
+
const info = adapter?.info;
|
|
4179
|
+
if (!info || typeof info !== "object") {
|
|
4180
|
+
return null;
|
|
4468
4181
|
}
|
|
4469
|
-
const texture = device.createTexture({
|
|
4470
|
-
label: "plasius.wavefront.environmentSampling",
|
|
4471
|
-
size: { width: tables.width, height: tables.height },
|
|
4472
|
-
format: "rgba16float",
|
|
4473
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4474
|
-
});
|
|
4475
|
-
device.queue.writeTexture(
|
|
4476
|
-
{ texture },
|
|
4477
|
-
bytes,
|
|
4478
|
-
{ bytesPerRow, rowsPerImage: tables.height },
|
|
4479
|
-
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
4480
|
-
);
|
|
4481
4182
|
return Object.freeze({
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4183
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
4184
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
4185
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
4186
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
4486
4187
|
});
|
|
4487
4188
|
}
|
|
4488
|
-
function
|
|
4489
|
-
const upload = createBrdfLutUploadBytes(size);
|
|
4490
|
-
const texture = device.createTexture({
|
|
4491
|
-
label: "plasius.wavefront.brdfLut",
|
|
4492
|
-
size: { width: upload.width, height: upload.height },
|
|
4493
|
-
format: "rgba16float",
|
|
4494
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4495
|
-
});
|
|
4496
|
-
device.queue.writeTexture(
|
|
4497
|
-
{ texture },
|
|
4498
|
-
upload.bytes,
|
|
4499
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4500
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4501
|
-
);
|
|
4189
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
4502
4190
|
return Object.freeze({
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4191
|
+
physicalCoreCount: null,
|
|
4192
|
+
physicalCoreCountAvailable: false,
|
|
4193
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
4194
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
4195
|
+
adapterLimits: Object.freeze({
|
|
4196
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
4197
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
4198
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
4199
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
4200
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
4201
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
4202
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
|
|
4203
|
+
maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize")
|
|
4510
4204
|
}),
|
|
4511
|
-
|
|
4512
|
-
ownsTexture: true,
|
|
4513
|
-
width: upload.width,
|
|
4514
|
-
height: upload.height
|
|
4205
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
4515
4206
|
});
|
|
4516
4207
|
}
|
|
4517
|
-
function
|
|
4518
|
-
|
|
4519
|
-
|
|
4208
|
+
function createEnvironmentMapSnapshot(environmentMap) {
|
|
4209
|
+
return Object.freeze({
|
|
4210
|
+
enabled: environmentMap.enabled,
|
|
4211
|
+
width: environmentMap.width,
|
|
4212
|
+
height: environmentMap.height,
|
|
4213
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
4214
|
+
projection: environmentMap.projection,
|
|
4215
|
+
intensity: environmentMap.intensity,
|
|
4216
|
+
rotationRadians: environmentMap.rotationRadians,
|
|
4217
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
4218
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
function nowMs3() {
|
|
4222
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
4223
|
+
return performance.now();
|
|
4224
|
+
}
|
|
4225
|
+
return Date.now();
|
|
4226
|
+
}
|
|
4227
|
+
function estimateAccelerationBuildWaitFactor(config) {
|
|
4228
|
+
if (config?.gpuAccelerationBuildRequired !== true) {
|
|
4229
|
+
return 1;
|
|
4230
|
+
}
|
|
4231
|
+
const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
|
|
4232
|
+
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
4233
|
+
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
4234
|
+
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
4235
|
+
}
|
|
4236
|
+
function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
|
|
4237
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
4238
|
+
const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
4239
|
+
return Object.freeze({
|
|
4240
|
+
timeoutMs: overrideMs,
|
|
4241
|
+
maxWaitMs: overrideMs
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4244
|
+
const samplesPerPixel = Math.max(
|
|
4520
4245
|
1,
|
|
4521
|
-
|
|
4246
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
4522
4247
|
);
|
|
4523
|
-
const
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
label: "plasius.wavefront.mediumTable",
|
|
4544
|
-
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
4545
|
-
format: "rgba16float",
|
|
4546
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4547
|
-
});
|
|
4548
|
-
device.queue.writeTexture(
|
|
4549
|
-
{ texture },
|
|
4550
|
-
upload.bytes,
|
|
4551
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4552
|
-
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
4248
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
4249
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
4250
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
4251
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
4252
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
4253
|
+
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
4254
|
+
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
4255
|
+
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
4256
|
+
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
4257
|
+
const estimatedWindowMs = Math.round(
|
|
4258
|
+
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
4259
|
+
);
|
|
4260
|
+
const timeoutMs = Math.min(
|
|
4261
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4262
|
+
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
4263
|
+
);
|
|
4264
|
+
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
4265
|
+
const maxWaitMs = Math.min(
|
|
4266
|
+
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
4267
|
+
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
4553
4268
|
);
|
|
4554
4269
|
return Object.freeze({
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
ownsTexture: true,
|
|
4558
|
-
count: normalized.length,
|
|
4559
|
-
width
|
|
4270
|
+
timeoutMs,
|
|
4271
|
+
maxWaitMs
|
|
4560
4272
|
});
|
|
4561
4273
|
}
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
if (
|
|
4566
|
-
return
|
|
4274
|
+
|
|
4275
|
+
// src/wavefront-gpu-synchronization.js
|
|
4276
|
+
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
4277
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
4278
|
+
return true;
|
|
4567
4279
|
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4280
|
+
const timeoutMs = Math.max(
|
|
4281
|
+
1,
|
|
4282
|
+
Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
4283
|
+
);
|
|
4284
|
+
const maxWaitMs = Math.max(
|
|
4285
|
+
timeoutMs,
|
|
4286
|
+
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
4287
|
+
);
|
|
4288
|
+
const allowTimeout = options.allowTimeout !== false;
|
|
4289
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
4290
|
+
() => ({ status: "done" }),
|
|
4291
|
+
(error) => {
|
|
4292
|
+
throw error;
|
|
4576
4293
|
}
|
|
4577
|
-
|
|
4294
|
+
);
|
|
4295
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
4296
|
+
throw new Error(
|
|
4297
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
4298
|
+
);
|
|
4299
|
+
}) : null;
|
|
4300
|
+
const startedAtMs = nowMs3();
|
|
4301
|
+
while (true) {
|
|
4302
|
+
const elapsedMs = Math.max(0, nowMs3() - startedAtMs);
|
|
4303
|
+
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
4304
|
+
if (remainingMs <= 0) {
|
|
4305
|
+
if (!allowTimeout) {
|
|
4306
|
+
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
4307
|
+
}
|
|
4308
|
+
console.warn(
|
|
4309
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
4310
|
+
);
|
|
4578
4311
|
return false;
|
|
4579
4312
|
}
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4313
|
+
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
4314
|
+
let timeoutHandle = null;
|
|
4315
|
+
let resolveTimeoutPromise = null;
|
|
4316
|
+
let timeoutSettled = false;
|
|
4317
|
+
const settleTimeoutPromise = (value) => {
|
|
4318
|
+
if (timeoutSettled) {
|
|
4319
|
+
return;
|
|
4583
4320
|
}
|
|
4584
|
-
|
|
4585
|
-
|
|
4321
|
+
timeoutSettled = true;
|
|
4322
|
+
resolveTimeoutPromise?.(value);
|
|
4323
|
+
};
|
|
4324
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
4325
|
+
resolveTimeoutPromise = resolve;
|
|
4326
|
+
timeoutHandle = setTimeout(
|
|
4327
|
+
() => settleTimeoutPromise({ status: "timeout" }),
|
|
4328
|
+
waitWindowMs
|
|
4329
|
+
);
|
|
4330
|
+
});
|
|
4331
|
+
let result;
|
|
4332
|
+
try {
|
|
4333
|
+
result = await Promise.race(
|
|
4334
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
4335
|
+
);
|
|
4336
|
+
} finally {
|
|
4337
|
+
if (timeoutHandle !== null) {
|
|
4338
|
+
clearTimeout(timeoutHandle);
|
|
4339
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
if (result?.status === "done") {
|
|
4343
|
+
return true;
|
|
4344
|
+
}
|
|
4345
|
+
if (result?.status !== "timeout") {
|
|
4346
|
+
return true;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
// src/wavefront-upload.js
|
|
4352
|
+
async function uploadWavefrontBuffer(device, buffer, data, { chunkBytes = 8 * 1024 * 1024 } = {}) {
|
|
4353
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 4 || chunkBytes % 4 !== 0) throw new Error("GPU upload chunk size must be a positive multiple of four.");
|
|
4354
|
+
if (data.byteLength <= chunkBytes) {
|
|
4355
|
+
device.queue.writeBuffer(buffer, 0, data);
|
|
4356
|
+
return;
|
|
4357
|
+
}
|
|
4358
|
+
for (let offset = 0; offset < data.byteLength; offset += chunkBytes) {
|
|
4359
|
+
device.queue.writeBuffer(buffer, offset, data, offset, Math.min(chunkBytes, data.byteLength - offset));
|
|
4360
|
+
await waitForSubmittedGpuWork(device, { timeoutMs: 1e4, maxWaitMs: 1e4, allowTimeout: false });
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
// src/gpu-allocation-budget.js
|
|
4365
|
+
var formats = Object.freeze({
|
|
4366
|
+
r8unorm: 1,
|
|
4367
|
+
r8snorm: 1,
|
|
4368
|
+
r8uint: 1,
|
|
4369
|
+
r8sint: 1,
|
|
4370
|
+
rg8unorm: 2,
|
|
4371
|
+
rg8snorm: 2,
|
|
4372
|
+
rg8uint: 2,
|
|
4373
|
+
rg8sint: 2,
|
|
4374
|
+
r16float: 2,
|
|
4375
|
+
r16uint: 2,
|
|
4376
|
+
r16sint: 2,
|
|
4377
|
+
rgba8unorm: 4,
|
|
4378
|
+
"rgba8unorm-srgb": 4,
|
|
4379
|
+
bgra8unorm: 4,
|
|
4380
|
+
"bgra8unorm-srgb": 4,
|
|
4381
|
+
rgba8snorm: 4,
|
|
4382
|
+
rgba8uint: 4,
|
|
4383
|
+
rgba8sint: 4,
|
|
4384
|
+
rg16float: 4,
|
|
4385
|
+
rg16uint: 4,
|
|
4386
|
+
rg16sint: 4,
|
|
4387
|
+
r32float: 4,
|
|
4388
|
+
r32uint: 4,
|
|
4389
|
+
r32sint: 4,
|
|
4390
|
+
rgb10a2unorm: 4,
|
|
4391
|
+
rg11b10ufloat: 4,
|
|
4392
|
+
rgba16float: 8,
|
|
4393
|
+
rgba16uint: 8,
|
|
4394
|
+
rgba16sint: 8,
|
|
4395
|
+
rg32float: 8,
|
|
4396
|
+
rg32uint: 8,
|
|
4397
|
+
rg32sint: 8,
|
|
4398
|
+
rgba32float: 16,
|
|
4399
|
+
rgba32uint: 16,
|
|
4400
|
+
rgba32sint: 16
|
|
4401
|
+
});
|
|
4402
|
+
function integer(value, name, minimum = 1) {
|
|
4403
|
+
if (!Number.isSafeInteger(value) || value < minimum) throw new Error(`${name} must be a safe integer >= ${minimum}.`);
|
|
4404
|
+
return value;
|
|
4405
|
+
}
|
|
4406
|
+
function within(value, limit, name) {
|
|
4407
|
+
if (Number.isFinite(limit) && value > limit) throw new Error(`GPU ${name} limit exceeded: requested ${value}, available ${limit}.`);
|
|
4408
|
+
}
|
|
4409
|
+
function textureBytes(descriptor, limits) {
|
|
4410
|
+
const size = descriptor.size;
|
|
4411
|
+
const array = Array.isArray(size) || ArrayBuffer.isView(size);
|
|
4412
|
+
const width = integer(array ? size[0] : size?.width, "Texture width");
|
|
4413
|
+
const height = integer((array ? size[1] : size?.height) ?? 1, "Texture height");
|
|
4414
|
+
const layers = integer((array ? size[2] : size?.depthOrArrayLayers) ?? 1, "Texture depth/layers");
|
|
4415
|
+
const dimension2 = descriptor.dimension ?? "2d";
|
|
4416
|
+
if (!["1d", "2d", "3d"].includes(dimension2)) throw new Error("Unsupported GPU texture dimension.");
|
|
4417
|
+
const limit = limits[dimension2 === "3d" ? "maxTextureDimension3D" : dimension2 === "1d" ? "maxTextureDimension1D" : "maxTextureDimension2D"];
|
|
4418
|
+
within(Math.max(width, height, dimension2 === "3d" ? layers : 1), limit, "texture dimension");
|
|
4419
|
+
if (dimension2 !== "3d") within(layers, limits.maxTextureArrayLayers, "texture layers");
|
|
4420
|
+
const bpp = formats[descriptor.format];
|
|
4421
|
+
if (!bpp) throw new Error(`GPU memory accounting does not support texture format ${descriptor.format}.`);
|
|
4422
|
+
const levels = integer(descriptor.mipLevelCount ?? 1, "Mip count");
|
|
4423
|
+
const samples = integer(descriptor.sampleCount ?? 1, "Sample count");
|
|
4424
|
+
if (levels > Math.floor(Math.log2(Math.max(width, height, dimension2 === "3d" ? layers : 1))) + 1) throw new Error("Invalid texture mip count.");
|
|
4425
|
+
let bytes = 0;
|
|
4426
|
+
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;
|
|
4427
|
+
return integer(bytes, "Texture allocation");
|
|
4428
|
+
}
|
|
4429
|
+
function createGpuAllocationTracker(rawDevice, { budgetBytes, storageUsage = globalThis.GPUBufferUsage?.STORAGE ?? 128 } = {}) {
|
|
4430
|
+
if (budgetBytes !== void 0) integer(budgetBytes, "GPU memory budget");
|
|
4431
|
+
const records = /* @__PURE__ */ new Map();
|
|
4432
|
+
let allocatedBytes = 0, peakAllocatedBytes = 0;
|
|
4433
|
+
function allocate(kind, descriptor, bytes, create) {
|
|
4434
|
+
integer(bytes, "GPU allocation", 0);
|
|
4435
|
+
const total = integer(allocatedBytes + bytes, "GPU allocation total", 0);
|
|
4436
|
+
if (budgetBytes !== void 0 && total > budgetBytes) throw new Error(`GPU memory budget exceeded: ${total} requested bytes, budget ${budgetBytes}. Reduce quality or scene resources.`);
|
|
4437
|
+
const resource = create(descriptor), originalDestroy = resource.destroy.bind(resource);
|
|
4438
|
+
const release = () => {
|
|
4439
|
+
if (!records.has(resource)) return;
|
|
4440
|
+
originalDestroy();
|
|
4441
|
+
records.delete(resource);
|
|
4442
|
+
allocatedBytes -= bytes;
|
|
4443
|
+
};
|
|
4444
|
+
try {
|
|
4445
|
+
Object.defineProperty(resource, "destroy", { value: release, configurable: true });
|
|
4446
|
+
} catch (error) {
|
|
4447
|
+
originalDestroy();
|
|
4448
|
+
throw error;
|
|
4449
|
+
}
|
|
4450
|
+
records.set(resource, { kind, label: descriptor.label ?? "", bytes, release });
|
|
4451
|
+
allocatedBytes = total;
|
|
4452
|
+
peakAllocatedBytes = Math.max(total, peakAllocatedBytes);
|
|
4453
|
+
return resource;
|
|
4454
|
+
}
|
|
4455
|
+
const destroy = () => {
|
|
4456
|
+
for (const record of [...records.values()]) record.release();
|
|
4457
|
+
};
|
|
4458
|
+
const methods = {
|
|
4459
|
+
createBuffer(descriptor) {
|
|
4460
|
+
const bytes = integer(descriptor.size, "Buffer size", 0);
|
|
4461
|
+
within(bytes, rawDevice.limits?.maxBufferSize, "buffer size");
|
|
4462
|
+
if ((descriptor.usage ?? 0) & storageUsage) within(bytes, rawDevice.limits?.maxStorageBufferBindingSize, "storage binding size");
|
|
4463
|
+
return allocate("buffer", descriptor, bytes, (d) => rawDevice.createBuffer(d));
|
|
4464
|
+
},
|
|
4465
|
+
createTexture(descriptor) {
|
|
4466
|
+
return allocate("texture", descriptor, textureBytes(descriptor, rawDevice.limits ?? {}), (d) => rawDevice.createTexture(d));
|
|
4467
|
+
},
|
|
4468
|
+
createQuerySet(descriptor) {
|
|
4469
|
+
return allocate("query-set", descriptor, integer(descriptor.count, "Query count") * 8, (d) => rawDevice.createQuerySet(d));
|
|
4470
|
+
},
|
|
4471
|
+
destroy() {
|
|
4472
|
+
destroy();
|
|
4473
|
+
rawDevice.destroy();
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4476
|
+
const device = new Proxy(rawDevice, { get(target, key) {
|
|
4477
|
+
if (Object.hasOwn(methods, key)) return methods[key];
|
|
4478
|
+
const value = Reflect.get(target, key, target);
|
|
4479
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
4480
|
+
} });
|
|
4481
|
+
return Object.freeze({ device, destroy, snapshot: () => Object.freeze({
|
|
4482
|
+
accounting: "requested-resource-bytes",
|
|
4483
|
+
budgetBytes: budgetBytes ?? null,
|
|
4484
|
+
allocatedBytes,
|
|
4485
|
+
peakAllocatedBytes,
|
|
4486
|
+
resources: Object.freeze([...records.values()].map(({ kind, label, bytes }) => Object.freeze({ kind, label, bytes })))
|
|
4487
|
+
}) });
|
|
4488
|
+
}
|
|
4489
|
+
function openGpuAllocationErrorScopes(device) {
|
|
4490
|
+
if (typeof device.pushErrorScope !== "function" || typeof device.popErrorScope !== "function") return async () => {
|
|
4491
|
+
};
|
|
4492
|
+
let count = 0;
|
|
4493
|
+
try {
|
|
4494
|
+
device.pushErrorScope("out-of-memory");
|
|
4495
|
+
count++;
|
|
4496
|
+
device.pushErrorScope("validation");
|
|
4497
|
+
count++;
|
|
4498
|
+
} catch (error) {
|
|
4499
|
+
while (count-- > 0) Promise.resolve(device.popErrorScope()).catch(() => {
|
|
4500
|
+
});
|
|
4501
|
+
throw error;
|
|
4502
|
+
}
|
|
4503
|
+
return async () => {
|
|
4504
|
+
const completions = [];
|
|
4505
|
+
while (count > 0) {
|
|
4506
|
+
count--;
|
|
4507
|
+
try {
|
|
4508
|
+
completions.push(Promise.resolve(device.popErrorScope()));
|
|
4509
|
+
} catch (error) {
|
|
4510
|
+
completions.push(Promise.reject(error));
|
|
4586
4511
|
}
|
|
4587
4512
|
}
|
|
4588
|
-
|
|
4589
|
-
|
|
4513
|
+
if (!completions.length) return;
|
|
4514
|
+
let timer;
|
|
4515
|
+
try {
|
|
4516
|
+
const errors = await Promise.race([Promise.all(completions), new Promise((_, reject) => {
|
|
4517
|
+
timer = setTimeout(() => reject(new Error("GPU allocation validation timed out.")), 1e4);
|
|
4518
|
+
})]);
|
|
4519
|
+
const error = errors.find(Boolean);
|
|
4520
|
+
if (error) throw new Error(`GPU allocation failed: ${error.message ?? String(error)}`);
|
|
4521
|
+
} finally {
|
|
4522
|
+
clearTimeout(timer);
|
|
4523
|
+
}
|
|
4524
|
+
};
|
|
4590
4525
|
}
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4526
|
+
|
|
4527
|
+
// src/wavefront-sampling.js
|
|
4528
|
+
function buildOrthonormalBasis(normal) {
|
|
4529
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
4530
|
+
const tangent = normalize(cross2(tangentFallback, normal), [1, 0, 0]);
|
|
4531
|
+
const bitangent = normalize(cross2(normal, tangent), [0, 0, 1]);
|
|
4532
|
+
return { tangent, bitangent };
|
|
4533
|
+
}
|
|
4534
|
+
function localToWorld(local, normal) {
|
|
4535
|
+
const basis = buildOrthonormalBasis(normal);
|
|
4536
|
+
return normalize(
|
|
4537
|
+
add(
|
|
4538
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
4539
|
+
scale(normal, local[2])
|
|
4540
|
+
),
|
|
4541
|
+
normal
|
|
4604
4542
|
);
|
|
4605
|
-
return Object.freeze({
|
|
4606
|
-
texture,
|
|
4607
|
-
view: texture.createView(),
|
|
4608
|
-
ownsTexture: true
|
|
4609
|
-
});
|
|
4610
4543
|
}
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
}
|
|
4620
|
-
const value = Number(rawValue);
|
|
4621
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4622
|
-
throw new Error(`${name} must be a non-negative safe integer.`);
|
|
4623
|
-
}
|
|
4624
|
-
return value;
|
|
4544
|
+
function radicalInverseVdc(bits) {
|
|
4545
|
+
let value = bits >>> 0;
|
|
4546
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
4547
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
4548
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
4549
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
4550
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
4551
|
+
return value * 23283064365386963e-26;
|
|
4625
4552
|
}
|
|
4626
|
-
function
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4553
|
+
function hammersley(index, count) {
|
|
4554
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
4555
|
+
}
|
|
4556
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
4557
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
4558
|
+
const phi = 2 * Math.PI * sample[0];
|
|
4559
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
4560
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
4561
|
+
const halfVector = localToWorld(
|
|
4562
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
4563
|
+
normal
|
|
4564
|
+
);
|
|
4565
|
+
return normalize(halfVector, normal);
|
|
4566
|
+
}
|
|
4567
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
4568
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
4569
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
4570
|
+
}
|
|
4571
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
4572
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
4573
|
+
}
|
|
4574
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
4575
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
4576
|
+
const normal = [0, 0, 1];
|
|
4577
|
+
let scaleTerm = 0;
|
|
4578
|
+
let biasTerm = 0;
|
|
4579
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
4580
|
+
const xi = hammersley(index, sampleCount);
|
|
4581
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4582
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
4583
|
+
const lightDirection = normalize(
|
|
4584
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
4585
|
+
normal
|
|
4636
4586
|
);
|
|
4587
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
4588
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
4589
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
4590
|
+
continue;
|
|
4591
|
+
}
|
|
4592
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
4593
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
4594
|
+
const fresnel2 = (1 - vDotH) ** 5;
|
|
4595
|
+
scaleTerm += (1 - fresnel2) * visibility;
|
|
4596
|
+
biasTerm += fresnel2 * visibility;
|
|
4637
4597
|
}
|
|
4598
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4638
4599
|
}
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4600
|
+
|
|
4601
|
+
// src/wavefront-gpu-resources.js
|
|
4602
|
+
function clampTileSizeForDevice(config, device) {
|
|
4603
|
+
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
4604
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4605
|
+
return config.tileSize;
|
|
4642
4606
|
}
|
|
4643
|
-
|
|
4644
|
-
|
|
4607
|
+
const maxPixelsByRay = Math.floor(limit / RAY_RECORD_BYTES);
|
|
4608
|
+
const maxPixelsByHit = Math.floor(limit / HIT_RECORD_BYTES);
|
|
4609
|
+
const maxPixelsByPathVertex = Math.floor(
|
|
4610
|
+
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
4611
|
+
);
|
|
4612
|
+
const maxPixels = Math.max(
|
|
4613
|
+
256,
|
|
4614
|
+
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
4615
|
+
);
|
|
4616
|
+
if (config.tilePixelCapacity <= maxPixels) {
|
|
4617
|
+
return config.tileSize;
|
|
4645
4618
|
}
|
|
4646
|
-
return
|
|
4647
|
-
buffer: GPUBufferUsage,
|
|
4648
|
-
texture: GPUTextureUsage,
|
|
4649
|
-
shader: GPUShaderStage,
|
|
4650
|
-
map: typeof GPUMapMode === "undefined" ? null : GPUMapMode
|
|
4651
|
-
};
|
|
4619
|
+
return Math.max(16, Math.floor(Math.sqrt(maxPixels)));
|
|
4652
4620
|
}
|
|
4653
|
-
function
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4621
|
+
function createBuffer(device, usage, size, label) {
|
|
4622
|
+
return device.createBuffer({
|
|
4623
|
+
label,
|
|
4624
|
+
size: Math.max(4, size),
|
|
4625
|
+
usage
|
|
4626
|
+
});
|
|
4627
|
+
}
|
|
4628
|
+
function alignTo(value, alignment) {
|
|
4629
|
+
const resolvedAlignment = Math.max(1, alignment);
|
|
4630
|
+
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
4631
|
+
}
|
|
4632
|
+
function float32ToFloat16Bits(value) {
|
|
4633
|
+
const floatView = new Float32Array(1);
|
|
4634
|
+
const intView = new Uint32Array(floatView.buffer);
|
|
4635
|
+
floatView[0] = Number.isFinite(value) ? value : 0;
|
|
4636
|
+
const x = intView[0];
|
|
4637
|
+
const sign = x >> 16 & 32768;
|
|
4638
|
+
let mantissa = x & 8388607;
|
|
4639
|
+
let exponent = x >> 23 & 255;
|
|
4640
|
+
if (exponent === 255) {
|
|
4641
|
+
return sign | (mantissa ? 32256 : 31744);
|
|
4660
4642
|
}
|
|
4661
|
-
|
|
4662
|
-
|
|
4643
|
+
exponent = exponent - 127 + 15;
|
|
4644
|
+
if (exponent >= 31) {
|
|
4645
|
+
return sign | 31744;
|
|
4663
4646
|
}
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4647
|
+
if (exponent <= 0) {
|
|
4648
|
+
if (exponent < -10) {
|
|
4649
|
+
return sign;
|
|
4650
|
+
}
|
|
4651
|
+
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4652
|
+
return sign | mantissa + 4096 >> 13;
|
|
4667
4653
|
}
|
|
4668
|
-
return
|
|
4654
|
+
return sign | exponent << 10 | mantissa + 4096 >> 13;
|
|
4669
4655
|
}
|
|
4670
|
-
|
|
4671
|
-
if (
|
|
4672
|
-
return
|
|
4656
|
+
function environmentMapIntegerScale(data) {
|
|
4657
|
+
if (data instanceof Uint8Array) {
|
|
4658
|
+
return 1 / 255;
|
|
4673
4659
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
const messages = info.messages ?? [];
|
|
4677
|
-
if (messages.length === 0) {
|
|
4678
|
-
return "";
|
|
4679
|
-
}
|
|
4680
|
-
return messages.map((message) => {
|
|
4681
|
-
const line = message.lineNum ?? "?";
|
|
4682
|
-
const column = message.linePos ?? "?";
|
|
4683
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4684
|
-
}).join("\n");
|
|
4685
|
-
} catch {
|
|
4686
|
-
return "";
|
|
4660
|
+
if (data instanceof Uint16Array) {
|
|
4661
|
+
return 1 / 65535;
|
|
4687
4662
|
}
|
|
4663
|
+
return 1;
|
|
4688
4664
|
}
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
const
|
|
4705
|
-
const
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4665
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
4666
|
+
if (!environmentMap || !environmentMap.data) {
|
|
4667
|
+
return false;
|
|
4668
|
+
}
|
|
4669
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
4670
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
4671
|
+
return environmentMap.data.length >= width * height * 4;
|
|
4672
|
+
}
|
|
4673
|
+
function createRgba8TextureUpload(source) {
|
|
4674
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
4675
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
4676
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
4677
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4678
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
4679
|
+
for (let y = 0; y < height; y += 1) {
|
|
4680
|
+
const sourceOffset = y * width * 4;
|
|
4681
|
+
const targetOffset = y * bytesPerRow;
|
|
4682
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
4683
|
+
}
|
|
4684
|
+
return Object.freeze({
|
|
4685
|
+
bytes,
|
|
4686
|
+
bytesPerRow,
|
|
4687
|
+
width,
|
|
4688
|
+
height
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
4692
|
+
if (!data || index >= data.length) {
|
|
4693
|
+
return fallback;
|
|
4710
4694
|
}
|
|
4695
|
+
const value = Number(data[index]);
|
|
4696
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
4711
4697
|
}
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4698
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
4699
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
4700
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
4701
|
+
if (cached) {
|
|
4702
|
+
return cached;
|
|
4715
4703
|
}
|
|
4716
|
-
const
|
|
4717
|
-
const
|
|
4718
|
-
const
|
|
4719
|
-
|
|
4720
|
-
|
|
4704
|
+
const width = Math.max(1, Math.trunc(size));
|
|
4705
|
+
const height = Math.max(1, Math.trunc(size));
|
|
4706
|
+
const rowBytes = width * 8;
|
|
4707
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4708
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4709
|
+
const view = new DataView(bytes.buffer);
|
|
4710
|
+
for (let y = 0; y < height; y += 1) {
|
|
4711
|
+
const roughness = (y + 0.5) / height;
|
|
4712
|
+
for (let x = 0; x < width; x += 1) {
|
|
4713
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
4714
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
4715
|
+
const offset = y * bytesPerRow + x * 8;
|
|
4716
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
4717
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
4718
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
4719
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4720
|
+
}
|
|
4721
4721
|
}
|
|
4722
|
-
const
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4726
|
-
}).join("\n");
|
|
4727
|
-
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
4728
|
-
${diagnostics}`);
|
|
4722
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4723
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4724
|
+
return upload;
|
|
4729
4725
|
}
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4726
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
4727
|
+
const width = Math.max(1, environmentMap.width);
|
|
4728
|
+
const height = Math.max(1, environmentMap.height);
|
|
4729
|
+
const pixels = new Float32Array(width * height * 4);
|
|
4730
|
+
const data = environmentMap.data;
|
|
4731
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
4732
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
4733
|
+
const sourceOffset = index * 4;
|
|
4734
|
+
const targetOffset = index * 4;
|
|
4735
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
4736
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
4737
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
4738
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
4733
4739
|
}
|
|
4734
|
-
return
|
|
4740
|
+
return pixels;
|
|
4735
4741
|
}
|
|
4736
|
-
function
|
|
4737
|
-
const
|
|
4738
|
-
const
|
|
4739
|
-
const
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
4743
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
4744
|
+
const theta = v * Math.PI;
|
|
4745
|
+
const sinTheta = Math.sin(theta);
|
|
4746
|
+
return [
|
|
4747
|
+
Math.cos(angle) * sinTheta,
|
|
4748
|
+
Math.cos(theta),
|
|
4749
|
+
Math.sin(angle) * sinTheta
|
|
4750
|
+
];
|
|
4751
|
+
}
|
|
4752
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
4753
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
4754
|
+
const clampedV = clamp2(v, 0, 1);
|
|
4755
|
+
const x = wrappedU * width - 0.5;
|
|
4756
|
+
const y = clampedV * height - 0.5;
|
|
4757
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
4758
|
+
const y0 = clamp2(Math.floor(y), 0, height - 1);
|
|
4759
|
+
const x1 = (x0 + 1) % width;
|
|
4760
|
+
const y1 = clamp2(y0 + 1, 0, height - 1);
|
|
4761
|
+
const tx = x - Math.floor(x);
|
|
4762
|
+
const ty = y - Math.floor(y);
|
|
4763
|
+
const read = (px, py) => {
|
|
4764
|
+
const offset = (py * width + px) * 4;
|
|
4765
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
4742
4766
|
};
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4767
|
+
const a = read(x0, y0);
|
|
4768
|
+
const b = read(x1, y0);
|
|
4769
|
+
const c = read(x0, y1);
|
|
4770
|
+
const d = read(x1, y1);
|
|
4771
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
4772
|
+
return [
|
|
4773
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
4774
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
4775
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
4776
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
4777
|
+
];
|
|
4778
|
+
}
|
|
4779
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
4780
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
4781
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
4782
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
4783
|
+
const v = Math.acos(clamp2(unitDirection[1], -1, 1)) / Math.PI;
|
|
4784
|
+
return [u, clamp2(v, 0, 1)];
|
|
4785
|
+
}
|
|
4786
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
4787
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
4788
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
4789
|
+
}
|
|
4790
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
4791
|
+
return levels.map((level) => {
|
|
4792
|
+
const rowBytes = level.width * 8;
|
|
4793
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4794
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
4795
|
+
const view = new DataView(bytes.buffer);
|
|
4796
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
4797
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
4798
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
4799
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
4800
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
4801
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
4802
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
4803
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
4804
|
+
}
|
|
4749
4805
|
}
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
const
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4806
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
4810
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
4811
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
4812
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
4813
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
4814
|
+
const levels = [
|
|
4815
|
+
Object.freeze({
|
|
4816
|
+
width: sourceWidth,
|
|
4817
|
+
height: sourceHeight,
|
|
4818
|
+
data: sourcePixels
|
|
4819
|
+
})
|
|
4820
|
+
];
|
|
4821
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
4822
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
4823
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
4824
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
4825
|
+
const data = new Float32Array(width * height * 4);
|
|
4826
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
4827
|
+
for (let y = 0; y < height; y += 1) {
|
|
4828
|
+
for (let x = 0; x < width; x += 1) {
|
|
4829
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
4830
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
4831
|
+
const viewDirection = normal;
|
|
4832
|
+
let totalWeight = 0;
|
|
4833
|
+
const accum = [0, 0, 0];
|
|
4834
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
4835
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
4836
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4837
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
4838
|
+
const lightDirection = normalize(
|
|
4839
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
4840
|
+
normal
|
|
4841
|
+
);
|
|
4842
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
4843
|
+
if (nDotL <= 1e-6) {
|
|
4844
|
+
continue;
|
|
4845
|
+
}
|
|
4846
|
+
const radiance = sampleEnvironmentRadiance(
|
|
4847
|
+
sourcePixels,
|
|
4848
|
+
sourceWidth,
|
|
4849
|
+
sourceHeight,
|
|
4850
|
+
lightDirection,
|
|
4851
|
+
environmentMap.rotationRadians
|
|
4852
|
+
);
|
|
4853
|
+
accum[0] += radiance[0] * nDotL;
|
|
4854
|
+
accum[1] += radiance[1] * nDotL;
|
|
4855
|
+
accum[2] += radiance[2] * nDotL;
|
|
4856
|
+
totalWeight += nDotL;
|
|
4857
|
+
}
|
|
4858
|
+
const offset = (y * width + x) * 4;
|
|
4859
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
4860
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
4861
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
4862
|
+
data[offset + 3] = 1;
|
|
4863
|
+
}
|
|
4761
4864
|
}
|
|
4762
|
-
|
|
4763
|
-
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4764
|
-
TRACE_SAMPLED_TEXTURE_BINDINGS
|
|
4765
|
-
);
|
|
4766
|
-
}
|
|
4767
|
-
const sceneStorageBufferBindingBytes = Number(
|
|
4768
|
-
memory?.sceneStorageBufferBindingBytes ?? 0
|
|
4769
|
-
);
|
|
4770
|
-
const requestedStorageBufferBindingSize = Math.max(
|
|
4771
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
|
|
4772
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
|
|
4773
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
|
|
4774
|
-
);
|
|
4775
|
-
if (requestedStorageBufferBindingSize > 0) {
|
|
4776
|
-
assertAdapterSupportsRequiredLimit(
|
|
4777
|
-
adapter,
|
|
4778
|
-
"maxStorageBufferBindingSize",
|
|
4779
|
-
requestedStorageBufferBindingSize
|
|
4780
|
-
);
|
|
4781
|
-
requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
|
|
4782
|
-
}
|
|
4783
|
-
const requestedMaxBufferSize = Math.max(
|
|
4784
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
|
|
4785
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
|
|
4786
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
|
|
4787
|
-
requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
|
|
4788
|
-
);
|
|
4789
|
-
if (requestedMaxBufferSize > 0) {
|
|
4790
|
-
assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
|
|
4791
|
-
requiredLimits.maxBufferSize = requestedMaxBufferSize;
|
|
4865
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
4792
4866
|
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4867
|
+
return Object.freeze({
|
|
4868
|
+
levels,
|
|
4869
|
+
mipLevelCount,
|
|
4870
|
+
width: sourceWidth,
|
|
4871
|
+
height: sourceHeight
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4874
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
4875
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
4876
|
+
return Object.freeze({
|
|
4877
|
+
width: 1,
|
|
4878
|
+
height: 1,
|
|
4879
|
+
pdf: new Float32Array([1]),
|
|
4880
|
+
marginalCdf: new Float32Array([1]),
|
|
4881
|
+
conditionalCdf: new Float32Array([1]),
|
|
4882
|
+
hasImportanceData: false
|
|
4883
|
+
});
|
|
4798
4884
|
}
|
|
4799
|
-
|
|
4800
|
-
|
|
4885
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
4886
|
+
const width = Math.max(1, environmentMap.width);
|
|
4887
|
+
const height = Math.max(1, environmentMap.height);
|
|
4888
|
+
const pdf = new Float32Array(width * height);
|
|
4889
|
+
const marginalCdf = new Float32Array(height);
|
|
4890
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
4891
|
+
const rowSums = new Float32Array(height);
|
|
4892
|
+
let totalWeight = 0;
|
|
4893
|
+
for (let y = 0; y < height; y += 1) {
|
|
4894
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
4895
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
4896
|
+
let rowWeight = 0;
|
|
4897
|
+
for (let x = 0; x < width; x += 1) {
|
|
4898
|
+
const offset = (y * width + x) * 4;
|
|
4899
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
4900
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
4901
|
+
pdf[y * width + x] = weight;
|
|
4902
|
+
rowWeight += weight;
|
|
4903
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
4904
|
+
}
|
|
4905
|
+
rowSums[y] = rowWeight;
|
|
4906
|
+
totalWeight += rowWeight;
|
|
4907
|
+
if (rowWeight > 0) {
|
|
4908
|
+
for (let x = 0; x < width; x += 1) {
|
|
4909
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
4910
|
+
}
|
|
4911
|
+
} else {
|
|
4912
|
+
for (let x = 0; x < width; x += 1) {
|
|
4913
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
marginalCdf[y] = totalWeight;
|
|
4801
4917
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
...descriptor.requiredLimits ?? {},
|
|
4805
|
-
...requiredLimits
|
|
4806
|
-
};
|
|
4918
|
+
for (let y = 0; y < height; y += 1) {
|
|
4919
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
4807
4920
|
}
|
|
4808
|
-
|
|
4921
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
4922
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
4923
|
+
}
|
|
4924
|
+
return Object.freeze({
|
|
4925
|
+
width,
|
|
4926
|
+
height,
|
|
4927
|
+
pdf,
|
|
4928
|
+
marginalCdf,
|
|
4929
|
+
conditionalCdf,
|
|
4930
|
+
hasImportanceData: true
|
|
4931
|
+
});
|
|
4809
4932
|
}
|
|
4810
|
-
function
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4933
|
+
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
4934
|
+
if (environmentMap.view) {
|
|
4935
|
+
return Object.freeze({
|
|
4936
|
+
view: environmentMap.view,
|
|
4937
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4938
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
4939
|
+
addressModeU: "repeat",
|
|
4940
|
+
addressModeV: "clamp-to-edge",
|
|
4941
|
+
magFilter: "linear",
|
|
4942
|
+
minFilter: "linear",
|
|
4943
|
+
mipmapFilter: "linear"
|
|
4944
|
+
}),
|
|
4945
|
+
texture: null,
|
|
4946
|
+
ownsTexture: false,
|
|
4947
|
+
width: Math.max(1, environmentMap.width),
|
|
4948
|
+
height: Math.max(1, environmentMap.height),
|
|
4949
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4950
|
+
});
|
|
4814
4951
|
}
|
|
4815
|
-
|
|
4816
|
-
|
|
4952
|
+
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
4953
|
+
return Object.freeze({
|
|
4954
|
+
view: environmentMap.texture.createView(),
|
|
4955
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4956
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
4957
|
+
addressModeU: "repeat",
|
|
4958
|
+
addressModeV: "clamp-to-edge",
|
|
4959
|
+
magFilter: "linear",
|
|
4960
|
+
minFilter: "linear",
|
|
4961
|
+
mipmapFilter: "linear"
|
|
4962
|
+
}),
|
|
4963
|
+
texture: environmentMap.texture,
|
|
4964
|
+
ownsTexture: false,
|
|
4965
|
+
width: Math.max(1, environmentMap.width),
|
|
4966
|
+
height: Math.max(1, environmentMap.height),
|
|
4967
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4968
|
+
});
|
|
4969
|
+
}
|
|
4970
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
4971
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
4972
|
+
const texture = device.createTexture({
|
|
4973
|
+
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
4974
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
4975
|
+
format: "rgba16float",
|
|
4976
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
4977
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4978
|
+
});
|
|
4979
|
+
uploads.forEach((upload, mipLevel) => {
|
|
4980
|
+
device.queue.writeTexture(
|
|
4981
|
+
{ texture, mipLevel },
|
|
4982
|
+
upload.bytes,
|
|
4983
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4984
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4985
|
+
);
|
|
4986
|
+
});
|
|
4987
|
+
return Object.freeze({
|
|
4988
|
+
view: texture.createView(),
|
|
4989
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4990
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
4991
|
+
addressModeU: "repeat",
|
|
4992
|
+
addressModeV: "clamp-to-edge",
|
|
4993
|
+
magFilter: "linear",
|
|
4994
|
+
minFilter: "linear",
|
|
4995
|
+
mipmapFilter: "linear"
|
|
4996
|
+
}),
|
|
4997
|
+
texture,
|
|
4998
|
+
ownsTexture: true,
|
|
4999
|
+
width: prefiltered.width,
|
|
5000
|
+
height: prefiltered.height,
|
|
5001
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
5002
|
+
});
|
|
4817
5003
|
}
|
|
4818
|
-
function
|
|
4819
|
-
const
|
|
4820
|
-
|
|
4821
|
-
|
|
5004
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
5005
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
5006
|
+
const rowBytes = tables.width * 8;
|
|
5007
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
5008
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
5009
|
+
const view = new DataView(bytes.buffer);
|
|
5010
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
5011
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
5012
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
5013
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
5014
|
+
const marginal = tables.marginalCdf[y];
|
|
5015
|
+
const offset = y * bytesPerRow + x * 8;
|
|
5016
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
5017
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
5018
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
5019
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
5020
|
+
}
|
|
4822
5021
|
}
|
|
5022
|
+
const texture = device.createTexture({
|
|
5023
|
+
label: "plasius.wavefront.environmentSampling",
|
|
5024
|
+
size: { width: tables.width, height: tables.height },
|
|
5025
|
+
format: "rgba16float",
|
|
5026
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5027
|
+
});
|
|
5028
|
+
device.queue.writeTexture(
|
|
5029
|
+
{ texture },
|
|
5030
|
+
bytes,
|
|
5031
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
5032
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
5033
|
+
);
|
|
4823
5034
|
return Object.freeze({
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
5035
|
+
view: texture.createView(),
|
|
5036
|
+
texture,
|
|
5037
|
+
ownsTexture: true,
|
|
5038
|
+
hasImportanceData: tables.hasImportanceData
|
|
4828
5039
|
});
|
|
4829
5040
|
}
|
|
4830
|
-
function
|
|
5041
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
5042
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
5043
|
+
const texture = device.createTexture({
|
|
5044
|
+
label: "plasius.wavefront.brdfLut",
|
|
5045
|
+
size: { width: upload.width, height: upload.height },
|
|
5046
|
+
format: "rgba16float",
|
|
5047
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5048
|
+
});
|
|
5049
|
+
device.queue.writeTexture(
|
|
5050
|
+
{ texture },
|
|
5051
|
+
upload.bytes,
|
|
5052
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5053
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
5054
|
+
);
|
|
4831
5055
|
return Object.freeze({
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
4840
|
-
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
4841
|
-
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
4842
|
-
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
4843
|
-
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
|
|
4844
|
-
maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize")
|
|
5056
|
+
view: texture.createView(),
|
|
5057
|
+
sampler: device.createSampler({
|
|
5058
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
5059
|
+
addressModeU: "clamp-to-edge",
|
|
5060
|
+
addressModeV: "clamp-to-edge",
|
|
5061
|
+
magFilter: "linear",
|
|
5062
|
+
minFilter: "linear"
|
|
4845
5063
|
}),
|
|
4846
|
-
|
|
5064
|
+
texture,
|
|
5065
|
+
ownsTexture: true,
|
|
5066
|
+
width: upload.width,
|
|
5067
|
+
height: upload.height
|
|
4847
5068
|
});
|
|
4848
5069
|
}
|
|
4849
|
-
function
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
5070
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
5071
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
5072
|
+
const width = Math.max(
|
|
5073
|
+
1,
|
|
5074
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
5075
|
+
);
|
|
5076
|
+
const level = {
|
|
5077
|
+
width,
|
|
5078
|
+
height: MEDIUM_TABLE_ROWS,
|
|
5079
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
5080
|
+
};
|
|
5081
|
+
for (const medium of normalized) {
|
|
5082
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
5083
|
+
const absorptionOffset = mediumId * 4;
|
|
5084
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
5085
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
5086
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
5087
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
5088
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
5089
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
5090
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
5091
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
5092
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
5093
|
+
}
|
|
5094
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
5095
|
+
const texture = device.createTexture({
|
|
5096
|
+
label: "plasius.wavefront.mediumTable",
|
|
5097
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
5098
|
+
format: "rgba16float",
|
|
5099
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5100
|
+
});
|
|
5101
|
+
device.queue.writeTexture(
|
|
5102
|
+
{ texture },
|
|
5103
|
+
upload.bytes,
|
|
5104
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5105
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
5106
|
+
);
|
|
5107
|
+
return Object.freeze({
|
|
5108
|
+
texture,
|
|
5109
|
+
view: texture.createView(),
|
|
5110
|
+
ownsTexture: true,
|
|
5111
|
+
count: normalized.length,
|
|
5112
|
+
width
|
|
4860
5113
|
});
|
|
4861
5114
|
}
|
|
4862
|
-
function
|
|
4863
|
-
|
|
4864
|
-
|
|
5115
|
+
function mediumTablesEqual(left, right) {
|
|
5116
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
5117
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
5118
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
5119
|
+
return false;
|
|
4865
5120
|
}
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
5121
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
5122
|
+
const leftMedium = leftMediums[index];
|
|
5123
|
+
const rightMedium = rightMediums[index];
|
|
5124
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
5125
|
+
return false;
|
|
5126
|
+
}
|
|
5127
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
5128
|
+
return false;
|
|
5129
|
+
}
|
|
5130
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
5131
|
+
return false;
|
|
5132
|
+
}
|
|
5133
|
+
for (let component = 0; component < 3; component += 1) {
|
|
5134
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
5135
|
+
return false;
|
|
5136
|
+
}
|
|
5137
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
5138
|
+
return false;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
4871
5141
|
}
|
|
4872
|
-
|
|
4873
|
-
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
4874
|
-
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
4875
|
-
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
5142
|
+
return true;
|
|
4876
5143
|
}
|
|
4877
|
-
function
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
4891
|
-
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
4892
|
-
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
4893
|
-
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
4894
|
-
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
4895
|
-
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
4896
|
-
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
4897
|
-
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
4898
|
-
const estimatedWindowMs = Math.round(
|
|
4899
|
-
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
4900
|
-
);
|
|
4901
|
-
const timeoutMs = Math.min(
|
|
4902
|
-
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4903
|
-
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
4904
|
-
);
|
|
4905
|
-
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
4906
|
-
const maxWaitMs = Math.min(
|
|
4907
|
-
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
4908
|
-
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
5144
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
5145
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
5146
|
+
const texture = device.createTexture({
|
|
5147
|
+
label,
|
|
5148
|
+
size: { width: upload.width, height: upload.height },
|
|
5149
|
+
format: "rgba8unorm",
|
|
5150
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
5151
|
+
});
|
|
5152
|
+
device.queue.writeTexture(
|
|
5153
|
+
{ texture },
|
|
5154
|
+
upload.bytes,
|
|
5155
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
5156
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4909
5157
|
);
|
|
4910
5158
|
return Object.freeze({
|
|
4911
|
-
|
|
4912
|
-
|
|
5159
|
+
texture,
|
|
5160
|
+
view: texture.createView(),
|
|
5161
|
+
ownsTexture: true
|
|
4913
5162
|
});
|
|
4914
5163
|
}
|
|
4915
5164
|
|
|
5165
|
+
// src/wavefront-instance-shader.js
|
|
5166
|
+
var WAVEFRONT_INSTANCE_SHADER_WGSL = `
|
|
5167
|
+
struct MeshInstance {
|
|
5168
|
+
world: mat4x4<f32>,
|
|
5169
|
+
inverse: mat4x4<f32>,
|
|
5170
|
+
objectId: u32,
|
|
5171
|
+
root: u32,
|
|
5172
|
+
handedness: f32,
|
|
5173
|
+
_pad: u32,
|
|
5174
|
+
};
|
|
5175
|
+
@group(0) @binding(47) var meshInstances: texture_2d<u32>;
|
|
5176
|
+
fn read_mesh_instance(index: u32) -> MeshInstance {
|
|
5177
|
+
let y = i32(index);
|
|
5178
|
+
let metadata = textureLoad(meshInstances, vec2<i32>(8, y), 0);
|
|
5179
|
+
return MeshInstance(
|
|
5180
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(0, y), 0)),
|
|
5181
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(1, y), 0)),
|
|
5182
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(2, y), 0)),
|
|
5183
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(3, y), 0))),
|
|
5184
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(4, y), 0)),
|
|
5185
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(5, y), 0)),
|
|
5186
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(6, y), 0)),
|
|
5187
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(7, y), 0))),
|
|
5188
|
+
metadata.x, metadata.y, bitcast<f32>(metadata.z), 0u);
|
|
5189
|
+
}
|
|
5190
|
+
|
|
5191
|
+
fn instance_normal(instance: MeshInstance, normal: vec3<f32>) -> vec3<f32> {
|
|
5192
|
+
let normalMatrix = transpose(mat3x3<f32>(instance.inverse[0].xyz, instance.inverse[1].xyz, instance.inverse[2].xyz));
|
|
5193
|
+
return safe_normalize(normalMatrix * normal, vec3<f32>(0.0, 1.0, 0.0));
|
|
5194
|
+
}
|
|
5195
|
+
|
|
5196
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
5197
|
+
var triangle = triangles[candidate.triangleIndex];
|
|
5198
|
+
let instance = read_mesh_instance(candidate.instanceIndex);
|
|
5199
|
+
triangle.v0 = instance.world * vec4<f32>(triangle.v0.xyz, 1.0);
|
|
5200
|
+
triangle.v1 = instance.world * vec4<f32>(triangle.v1.xyz, 1.0);
|
|
5201
|
+
triangle.v2 = instance.world * vec4<f32>(triangle.v2.xyz, 1.0);
|
|
5202
|
+
triangle.meshId = instance.objectId;
|
|
5203
|
+
return triangle;
|
|
5204
|
+
}
|
|
5205
|
+
|
|
5206
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
5207
|
+
var nearest = initialNearest;
|
|
5208
|
+
var best = no_candidate();
|
|
5209
|
+
var stack = array<u32, 64>();
|
|
5210
|
+
var stackSize = 1u;
|
|
5211
|
+
stack[0] = 0u;
|
|
5212
|
+
loop {
|
|
5213
|
+
if (stackSize == 0u) { break; }
|
|
5214
|
+
stackSize -= 1u;
|
|
5215
|
+
let node = bvhNodes[stack[stackSize]];
|
|
5216
|
+
if (!intersect_bounds(ray, node.boundsMin.xyz, node.boundsMax.xyz, nearest)) { continue; }
|
|
5217
|
+
if (node.triangleCount == 0u) {
|
|
5218
|
+
stack[stackSize] = node.childOrFirst;
|
|
5219
|
+
stack[stackSize + 1u] = node.rightChild;
|
|
5220
|
+
stackSize += 2u;
|
|
5221
|
+
} else {
|
|
5222
|
+
let instanceIndex = node.childOrFirst;
|
|
5223
|
+
let instance = read_mesh_instance(instanceIndex);
|
|
5224
|
+
var localRay = ray;
|
|
5225
|
+
localRay.origin = instance.inverse * vec4<f32>(ray.origin.xyz, 1.0);
|
|
5226
|
+
// Keep this direction unnormalized so t remains the world-space ray parameter.
|
|
5227
|
+
localRay.direction = instance.inverse * vec4<f32>(ray.direction.xyz, 0.0);
|
|
5228
|
+
var candidate = intersect_geometry_bvh(localRay, nearest, instance.root);
|
|
5229
|
+
if (candidate.hit == 1u && candidate.distance < nearest) {
|
|
5230
|
+
candidate.geometricNormal = instance_normal(instance, candidate.geometricNormal);
|
|
5231
|
+
candidate.shadingNormal = repair_shading_normal(candidate.geometricNormal,
|
|
5232
|
+
instance_normal(instance, candidate.shadingNormal));
|
|
5233
|
+
if (instance.handedness < 0.0) { candidate.frontFace = 1u - candidate.frontFace; }
|
|
5234
|
+
candidate.instanceIndex = instanceIndex;
|
|
5235
|
+
nearest = candidate.distance;
|
|
5236
|
+
best = candidate;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
return best;
|
|
5241
|
+
}
|
|
5242
|
+
`;
|
|
5243
|
+
var WAVEFRONT_UNINSTANCED_SHADER_WGSL = `
|
|
5244
|
+
fn triangle_for_candidate(candidate: Candidate) -> TriangleRecord {
|
|
5245
|
+
return triangles[candidate.triangleIndex];
|
|
5246
|
+
}
|
|
5247
|
+
fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
5248
|
+
return intersect_geometry_bvh(ray, initialNearest, 0u);
|
|
5249
|
+
}
|
|
5250
|
+
`;
|
|
5251
|
+
|
|
4916
5252
|
// src/wavefront-shader-bvh.js
|
|
4917
5253
|
var WAVEFRONT_SHADER_BVH_WGSL = `
|
|
4918
5254
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -5375,6 +5711,7 @@ fn no_candidate() -> Candidate {
|
|
|
5375
5711
|
1u,
|
|
5376
5712
|
0xffffffffu,
|
|
5377
5713
|
0xffffffffu,
|
|
5714
|
+
0xffffffffu,
|
|
5378
5715
|
0u,
|
|
5379
5716
|
0u
|
|
5380
5717
|
);
|
|
@@ -5401,6 +5738,7 @@ fn surface_candidate(
|
|
|
5401
5738
|
uv,
|
|
5402
5739
|
frontFace,
|
|
5403
5740
|
triangleIndex,
|
|
5741
|
+
0xffffffffu,
|
|
5404
5742
|
primitiveId,
|
|
5405
5743
|
materialRefId,
|
|
5406
5744
|
mediumRefId
|
|
@@ -5462,7 +5800,7 @@ fn intersect_triangle(ray: RayRecord, triangle: TriangleRecord, triangleIndex: u
|
|
|
5462
5800
|
);
|
|
5463
5801
|
}
|
|
5464
5802
|
|
|
5465
|
-
fn
|
|
5803
|
+
fn intersect_geometry_bvh(ray: RayRecord, initialNearest: f32, root: u32) -> Candidate {
|
|
5466
5804
|
var nearest = initialNearest;
|
|
5467
5805
|
var best = no_candidate();
|
|
5468
5806
|
if (config.bvhNodeCount == 0u || config.triangleCount == 0u) {
|
|
@@ -5471,7 +5809,7 @@ fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
|
5471
5809
|
|
|
5472
5810
|
var stack = array<u32, 64>();
|
|
5473
5811
|
var stackSize = 1u;
|
|
5474
|
-
stack[0] =
|
|
5812
|
+
stack[0] = root;
|
|
5475
5813
|
|
|
5476
5814
|
loop {
|
|
5477
5815
|
if (stackSize == 0u) {
|
|
@@ -5762,7 +6100,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5762
6100
|
if (meshCandidate.hit == 1u && meshCandidate.distance < nearest) {
|
|
5763
6101
|
nearest = meshCandidate.distance;
|
|
5764
6102
|
candidate = meshCandidate;
|
|
5765
|
-
hitTriangle =
|
|
6103
|
+
hitTriangle = triangle_for_candidate(meshCandidate);
|
|
5766
6104
|
}
|
|
5767
6105
|
|
|
5768
6106
|
if (candidate.hit == 0u) {
|
|
@@ -6916,6 +7254,7 @@ struct Candidate {
|
|
|
6916
7254
|
uv: vec2<f32>,
|
|
6917
7255
|
frontFace: u32,
|
|
6918
7256
|
triangleIndex: u32,
|
|
7257
|
+
instanceIndex: u32,
|
|
6919
7258
|
primitiveId: u32,
|
|
6920
7259
|
materialRefId: u32,
|
|
6921
7260
|
mediumRefId: u32,
|
|
@@ -8513,6 +8852,7 @@ var WAVEFRONT_COMPUTE_WGSL = [
|
|
|
8513
8852
|
WAVEFRONT_SHADER_MATERIALS_WGSL,
|
|
8514
8853
|
WAVEFRONT_SHADER_LIGHTING_WGSL,
|
|
8515
8854
|
WAVEFRONT_SHADER_BVH_WGSL,
|
|
8855
|
+
WAVEFRONT_UNINSTANCED_SHADER_WGSL,
|
|
8516
8856
|
WAVEFRONT_SHADER_KERNELS_WGSL
|
|
8517
8857
|
].join("\n");
|
|
8518
8858
|
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
@@ -8524,7 +8864,7 @@ function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
|
8524
8864
|
if (workgroupSize !== WAVEFRONT_COMPUTE_WORKGROUP_SIZE) {
|
|
8525
8865
|
throw new Error(`wavefront mesh compute currently requires workgroupSize=${WAVEFRONT_COMPUTE_WORKGROUP_SIZE}`);
|
|
8526
8866
|
}
|
|
8527
|
-
return WAVEFRONT_COMPUTE_WGSL;
|
|
8867
|
+
return options.instanced === true ? WAVEFRONT_COMPUTE_WGSL.replace(WAVEFRONT_UNINSTANCED_SHADER_WGSL, WAVEFRONT_INSTANCE_SHADER_WGSL) : WAVEFRONT_COMPUTE_WGSL;
|
|
8528
8868
|
}
|
|
8529
8869
|
|
|
8530
8870
|
// src/wavefront-progressive-shader.js
|
|
@@ -9528,7 +9868,7 @@ function chooseSplitAxis(triangles) {
|
|
|
9528
9868
|
}
|
|
9529
9869
|
return extent[1] >= extent[2] ? 1 : 2;
|
|
9530
9870
|
}
|
|
9531
|
-
function
|
|
9871
|
+
function buildWavefrontBoundsBvh(triangles, maxLeafTriangles = 4) {
|
|
9532
9872
|
if (triangles.length === 0) {
|
|
9533
9873
|
return Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
9534
9874
|
}
|
|
@@ -9580,7 +9920,7 @@ function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null)
|
|
|
9580
9920
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
9581
9921
|
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
9582
9922
|
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
9583
|
-
return
|
|
9923
|
+
return buildWavefrontBoundsBvh(triangles);
|
|
9584
9924
|
}
|
|
9585
9925
|
function estimateMeshSourceShape(meshes) {
|
|
9586
9926
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
@@ -10398,6 +10738,145 @@ function dispatchWavefrontGpuAccelerationBuild({
|
|
|
10398
10738
|
return true;
|
|
10399
10739
|
}
|
|
10400
10740
|
|
|
10741
|
+
// src/wavefront-instances.js
|
|
10742
|
+
var WAVEFRONT_INSTANCE_BYTES = 144;
|
|
10743
|
+
var freeze = Object.freeze;
|
|
10744
|
+
var uint = (value) => Number.isInteger(value) && value >= 0 && value < 4294967295;
|
|
10745
|
+
function affineTransform(value) {
|
|
10746
|
+
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) {
|
|
10747
|
+
throw new Error("Instance transform must be a finite affine column-major matrix.");
|
|
10748
|
+
}
|
|
10749
|
+
const m = Array.from(value, Math.fround);
|
|
10750
|
+
const [a, b, c, , d, e, f, , g, h, i] = m;
|
|
10751
|
+
const determinant = a * (e * i - f * h) - d * (b * i - c * h) + g * (b * f - c * e);
|
|
10752
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-12) {
|
|
10753
|
+
throw new Error("Instance transform must be invertible.");
|
|
10754
|
+
}
|
|
10755
|
+
const inv = [
|
|
10756
|
+
(e * i - f * h) / determinant,
|
|
10757
|
+
(c * h - b * i) / determinant,
|
|
10758
|
+
(b * f - c * e) / determinant,
|
|
10759
|
+
0,
|
|
10760
|
+
(f * g - d * i) / determinant,
|
|
10761
|
+
(a * i - c * g) / determinant,
|
|
10762
|
+
(c * d - a * f) / determinant,
|
|
10763
|
+
0,
|
|
10764
|
+
(d * h - e * g) / determinant,
|
|
10765
|
+
(b * g - a * h) / determinant,
|
|
10766
|
+
(a * e - b * d) / determinant,
|
|
10767
|
+
0,
|
|
10768
|
+
0,
|
|
10769
|
+
0,
|
|
10770
|
+
0,
|
|
10771
|
+
1
|
|
10772
|
+
];
|
|
10773
|
+
for (let r = 0; r < 3; r++) inv[12 + r] = -(inv[r] * m[12] + inv[4 + r] * m[13] + inv[8 + r] * m[14]);
|
|
10774
|
+
if (!inv.every((n) => Number.isFinite(n) && Math.abs(n) <= 1e12)) throw new Error("Instance inverse transform is out of range.");
|
|
10775
|
+
return { transform: freeze(m), inverse: freeze(inv.map((n) => n === 0 ? 0 : n)), handedness: Math.sign(determinant) };
|
|
10776
|
+
}
|
|
10777
|
+
function transformInstancePoint(m, p) {
|
|
10778
|
+
return [0, 1, 2].map((r) => m[r] * p[0] + m[4 + r] * p[1] + m[8 + r] * p[2] + m[12 + r]);
|
|
10779
|
+
}
|
|
10780
|
+
function meshBounds(mesh) {
|
|
10781
|
+
const localMin = [Infinity, Infinity, Infinity], localMax = [-Infinity, -Infinity, -Infinity];
|
|
10782
|
+
for (let i = 0; i < mesh.positions.length; i += 3) for (let a = 0; a < 3; a++) {
|
|
10783
|
+
localMin[a] = Math.min(localMin[a], Math.fround(mesh.positions[i + a]));
|
|
10784
|
+
localMax[a] = Math.max(localMax[a], Math.fround(mesh.positions[i + a]));
|
|
10785
|
+
}
|
|
10786
|
+
return { min: localMin, max: localMax };
|
|
10787
|
+
}
|
|
10788
|
+
function boundsOf(local, transform) {
|
|
10789
|
+
const localMin = local.min, localMax = local.max;
|
|
10790
|
+
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
|
|
10791
|
+
for (let corner = 0; corner < 8; corner++) {
|
|
10792
|
+
const p = transformInstancePoint(transform, [0, 1, 2].map((a) => corner & 1 << a ? localMax[a] : localMin[a]));
|
|
10793
|
+
for (let a = 0; a < 3; a++) {
|
|
10794
|
+
min[a] = Math.min(min[a], p[a]);
|
|
10795
|
+
max[a] = Math.max(max[a], p[a]);
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
10798
|
+
if (![...min, ...max].every(Number.isFinite)) throw new Error("Instance mesh has invalid bounds.");
|
|
10799
|
+
return freeze({ min: freeze(min), max: freeze(max) });
|
|
10800
|
+
}
|
|
10801
|
+
function prepareWavefrontInstances(meshes, instances) {
|
|
10802
|
+
if (!Array.isArray(instances) || instances.length === 0) throw new Error("Instances must be a nonempty array.");
|
|
10803
|
+
const byId = /* @__PURE__ */ new Map(), localBounds = /* @__PURE__ */ new Map();
|
|
10804
|
+
for (const mesh of meshes) {
|
|
10805
|
+
if (!uint(mesh.id) || byId.has(mesh.id)) throw new Error("Instanced mesh IDs must be unique unsigned integers.");
|
|
10806
|
+
if (mesh.materialKind === MATERIAL_EMISSIVE || emissionPower(mesh.emission ?? [0, 0, 0, 1]) > 1e-4) {
|
|
10807
|
+
throw new Error("Instanced emissive meshes require per-instance light sampling, which is not supported yet.");
|
|
10808
|
+
}
|
|
10809
|
+
byId.set(mesh.id, mesh);
|
|
10810
|
+
localBounds.set(mesh.id, meshBounds(mesh));
|
|
10811
|
+
}
|
|
10812
|
+
const ids = /* @__PURE__ */ new Set(), used = /* @__PURE__ */ new Set();
|
|
10813
|
+
const records = instances.map((instance) => {
|
|
10814
|
+
if (!instance || !uint(instance.id) || ids.has(instance.id)) throw new Error("Instance IDs must be unique unsigned integers.");
|
|
10815
|
+
const mesh = byId.get(instance.meshId);
|
|
10816
|
+
if (!mesh) throw new Error("Instance references an unknown mesh.");
|
|
10817
|
+
ids.add(instance.id);
|
|
10818
|
+
used.add(mesh.id);
|
|
10819
|
+
const affine = affineTransform(instance.transform);
|
|
10820
|
+
const bounds = boundsOf(localBounds.get(mesh.id), affine.transform);
|
|
10821
|
+
return freeze({
|
|
10822
|
+
...affine,
|
|
10823
|
+
id: instance.id,
|
|
10824
|
+
meshId: mesh.id,
|
|
10825
|
+
bounds,
|
|
10826
|
+
centroid: freeze(bounds.min.map((v, a) => (v + bounds.max[a]) / 2))
|
|
10827
|
+
});
|
|
10828
|
+
});
|
|
10829
|
+
if (used.size !== meshes.length) throw new Error("Every instanced mesh must have at least one instance.");
|
|
10830
|
+
return freeze({ records: freeze(records), geometryCount: meshes.length });
|
|
10831
|
+
}
|
|
10832
|
+
function buildWavefrontInstances(meshes, plan, materialSource) {
|
|
10833
|
+
const tlas = buildWavefrontBoundsBvh(plan.records, 1);
|
|
10834
|
+
const nodes = tlas.nodes.map((node) => {
|
|
10835
|
+
const pad = node.bounds.min.map((v, a) => Math.max(1, Math.abs(v), Math.abs(node.bounds.max[a])) * 1e-6);
|
|
10836
|
+
return freeze({ ...node, bounds: freeze({
|
|
10837
|
+
min: freeze(node.bounds.min.map((v, a) => v - pad[a])),
|
|
10838
|
+
max: freeze(node.bounds.max.map((v, a) => v + pad[a]))
|
|
10839
|
+
}) });
|
|
10840
|
+
}), triangles = [], roots = /* @__PURE__ */ new Map();
|
|
10841
|
+
for (const [meshIndex, mesh] of meshes.entries()) {
|
|
10842
|
+
const blas = createWavefrontMeshAcceleration([mesh], materialSource);
|
|
10843
|
+
const nodeOffset = nodes.length, triangleOffset = triangles.length;
|
|
10844
|
+
roots.set(mesh.id, nodeOffset);
|
|
10845
|
+
for (const node of blas.nodes) nodes.push(freeze({
|
|
10846
|
+
...node,
|
|
10847
|
+
firstTriangle: node.firstTriangle + (node.triangleCount > 0 ? triangleOffset : nodeOffset),
|
|
10848
|
+
leftChild: node.leftChild + nodeOffset,
|
|
10849
|
+
rightChild: node.rightChild + nodeOffset
|
|
10850
|
+
}));
|
|
10851
|
+
for (const triangle of blas.triangles) triangles.push(freeze({
|
|
10852
|
+
...triangle,
|
|
10853
|
+
triangleId: triangle.triangleId + triangleOffset,
|
|
10854
|
+
materialSlot: meshIndex
|
|
10855
|
+
}));
|
|
10856
|
+
}
|
|
10857
|
+
const records = tlas.triangles.map((record) => freeze({ ...record, root: roots.get(record.meshId) }));
|
|
10858
|
+
const buffer = new ArrayBuffer(records.length * WAVEFRONT_INSTANCE_BYTES);
|
|
10859
|
+
const floats = new Float32Array(buffer), uints = new Uint32Array(buffer);
|
|
10860
|
+
records.forEach((record, index) => {
|
|
10861
|
+
const offset = index * 36;
|
|
10862
|
+
floats.set(record.transform, offset);
|
|
10863
|
+
floats.set(record.inverse, offset + 16);
|
|
10864
|
+
uints[offset + 32] = record.id;
|
|
10865
|
+
uints[offset + 33] = record.root;
|
|
10866
|
+
floats[offset + 34] = record.handedness;
|
|
10867
|
+
});
|
|
10868
|
+
return freeze({
|
|
10869
|
+
records: freeze(records),
|
|
10870
|
+
buffer,
|
|
10871
|
+
instanceCount: records.length,
|
|
10872
|
+
geometryCount: meshes.length,
|
|
10873
|
+
instanceBytes: buffer.byteLength,
|
|
10874
|
+
tlasNodeCount: tlas.nodes.length,
|
|
10875
|
+
blasNodeCount: nodes.length - tlas.nodes.length,
|
|
10876
|
+
acceleration: freeze({ nodes: freeze(nodes), triangles: freeze(triangles) })
|
|
10877
|
+
});
|
|
10878
|
+
}
|
|
10879
|
+
|
|
10401
10880
|
// src/wavefront-config.js
|
|
10402
10881
|
var WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 256;
|
|
10403
10882
|
function allocatedRecordBytes(count, recordBytes) {
|
|
@@ -10671,6 +11150,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10671
11150
|
const progressiveBytes = options.progressive === true ? width * height * 16 + 16 : 0;
|
|
10672
11151
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
10673
11152
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
11153
|
+
const instanceBytes = readNonNegativeInteger2("instanceBytes", options.instanceBytes, 0);
|
|
10674
11154
|
const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
|
|
10675
11155
|
const materialTableBytes = 0;
|
|
10676
11156
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -10726,6 +11206,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10726
11206
|
pathVertexBytes,
|
|
10727
11207
|
sceneObjectBytes,
|
|
10728
11208
|
triangleBytes,
|
|
11209
|
+
instanceBytes,
|
|
10729
11210
|
materialTableBytes,
|
|
10730
11211
|
bvhNodeBytes,
|
|
10731
11212
|
bvhCombinedBytes,
|
|
@@ -10755,6 +11236,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10755
11236
|
maxDepth: config.maxDepth,
|
|
10756
11237
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
10757
11238
|
triangleCapacity: config.triangleCapacity,
|
|
11239
|
+
instanceBytes: config.instancing?.instanceBytes ?? 0,
|
|
10758
11240
|
bvhNodeCapacity: config.bvhNodeCapacity,
|
|
10759
11241
|
bvhLeafSortCapacity: config.bvhLeafSortCapacity,
|
|
10760
11242
|
emissiveTriangleCapacity: config.emissiveTriangleCapacity,
|
|
@@ -10773,6 +11255,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10773
11255
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
10774
11256
|
assertAnalyticDisplayQualityPolicy(options);
|
|
10775
11257
|
const accelerationBuildMode = resolveAccelerationBuildMode(options);
|
|
11258
|
+
if (options.instances !== void 0 && accelerationBuildMode === "gpu") throw new Error("Instance BLAS construction currently requires cpu-upload acceleration.");
|
|
10776
11259
|
const canvas = options.canvas;
|
|
10777
11260
|
const width = readPositiveInteger2("width", options.width, getCanvasDimension(canvas, "width", DEFAULT_WIDTH));
|
|
10778
11261
|
const height = readPositiveInteger2("height", options.height, getCanvasDimension(canvas, "height", DEFAULT_HEIGHT));
|
|
@@ -10803,9 +11286,16 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10803
11286
|
);
|
|
10804
11287
|
const meshes = normalizeMeshes(options);
|
|
10805
11288
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
11289
|
+
if (options.memoryBudgetBytes !== void 0) {
|
|
11290
|
+
if (!Number.isSafeInteger(options.memoryBudgetBytes) || options.memoryBudgetBytes <= 0) throw new Error("GPU memory budget must be a positive safe integer.");
|
|
11291
|
+
const geometryMinimumBytes = meshSourceShape.triangleCount * TRIANGLE_RECORD_BYTES + (options.instances?.length ?? 0) * 144;
|
|
11292
|
+
if (!Number.isSafeInteger(geometryMinimumBytes) || geometryMinimumBytes > options.memoryBudgetBytes) throw new Error("Unique geometry exceeds the GPU memory budget before acceleration construction.");
|
|
11293
|
+
}
|
|
11294
|
+
const instancePlan = options.instances === void 0 ? null : prepareWavefrontInstances(meshes, options.instances);
|
|
10806
11295
|
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
10807
11296
|
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
10808
|
-
const
|
|
11297
|
+
const instancing = instancePlan ? buildWavefrontInstances(meshes, instancePlan, gpuMaterialSource) : null;
|
|
11298
|
+
const meshAcceleration = instancing?.acceleration ?? (accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) }));
|
|
10809
11299
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
10810
11300
|
meshes,
|
|
10811
11301
|
options.emissiveTriangleCapacity
|
|
@@ -10883,6 +11373,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10883
11373
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
10884
11374
|
gpuMeshSource,
|
|
10885
11375
|
gpuMaterialSource,
|
|
11376
|
+
instancing,
|
|
10886
11377
|
meshAcceleration,
|
|
10887
11378
|
emissiveTriangleIndices,
|
|
10888
11379
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -10921,6 +11412,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10921
11412
|
tilePixelCapacity,
|
|
10922
11413
|
maxDepth,
|
|
10923
11414
|
sceneObjectCapacity,
|
|
11415
|
+
instanceBytes: instancing?.instanceBytes ?? 0,
|
|
10924
11416
|
triangleCapacity,
|
|
10925
11417
|
bvhNodeCapacity,
|
|
10926
11418
|
bvhLeafSortCapacity,
|
|
@@ -10954,6 +11446,7 @@ function createWavefrontTraceBindGroup({
|
|
|
10954
11446
|
counterBuffer,
|
|
10955
11447
|
outputView,
|
|
10956
11448
|
triangleBuffer,
|
|
11449
|
+
instanceView,
|
|
10957
11450
|
bvhNodeBuffer,
|
|
10958
11451
|
radianceView,
|
|
10959
11452
|
environmentPortalBuffer,
|
|
@@ -11011,7 +11504,8 @@ function createWavefrontTraceBindGroup({
|
|
|
11011
11504
|
{ binding: 41, resource: extensionAtlasResources.specularColor.view },
|
|
11012
11505
|
{ binding: 42, resource: extensionAtlasResources.iridescence.view },
|
|
11013
11506
|
{ binding: 43, resource: extensionAtlasResources.iridescenceThickness.view },
|
|
11014
|
-
{ binding: 44, resource: extensionAtlasResources.anisotropy.view }
|
|
11507
|
+
{ binding: 44, resource: extensionAtlasResources.anisotropy.view },
|
|
11508
|
+
...instanceView ? [{ binding: 47, resource: instanceView }] : []
|
|
11015
11509
|
]
|
|
11016
11510
|
});
|
|
11017
11511
|
}
|
|
@@ -11109,115 +11603,39 @@ function createWavefrontDenoiseBindGroups({
|
|
|
11109
11603
|
inputView: radianceView,
|
|
11110
11604
|
targetView: denoiseScratchView,
|
|
11111
11605
|
label: "plasius.wavefront.bind.denoise.radianceToScratch"
|
|
11112
|
-
}),
|
|
11113
|
-
resolve: createWavefrontDenoiseResolveBindGroup({
|
|
11114
|
-
device,
|
|
11115
|
-
denoiseResolveBindGroupLayout,
|
|
11116
|
-
configBuffer,
|
|
11117
|
-
inputView: denoiseScratchView,
|
|
11118
|
-
targetView: outputView,
|
|
11119
|
-
label: "plasius.wavefront.bind.denoise.scratchToOutput"
|
|
11120
|
-
}),
|
|
11121
|
-
directResolve: createWavefrontDenoiseResolveBindGroup({
|
|
11122
|
-
device,
|
|
11123
|
-
denoiseResolveBindGroupLayout,
|
|
11124
|
-
configBuffer,
|
|
11125
|
-
inputView: radianceView,
|
|
11126
|
-
targetView: outputView,
|
|
11127
|
-
label: "plasius.wavefront.bind.denoise.radianceToOutput"
|
|
11128
|
-
})
|
|
11129
|
-
});
|
|
11130
|
-
}
|
|
11131
|
-
function createWavefrontPresentBindGroup({
|
|
11132
|
-
device,
|
|
11133
|
-
presentBindGroupLayout,
|
|
11134
|
-
outputView,
|
|
11135
|
-
sampler
|
|
11136
|
-
}) {
|
|
11137
|
-
return device.createBindGroup({
|
|
11138
|
-
label: "plasius.wavefront.presentBindGroup",
|
|
11139
|
-
layout: presentBindGroupLayout,
|
|
11140
|
-
entries: [
|
|
11141
|
-
{ binding: 0, resource: outputView },
|
|
11142
|
-
{ binding: 1, resource: sampler }
|
|
11143
|
-
]
|
|
11144
|
-
});
|
|
11145
|
-
}
|
|
11146
|
-
|
|
11147
|
-
// src/wavefront-gpu-synchronization.js
|
|
11148
|
-
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
11149
|
-
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
11150
|
-
return true;
|
|
11151
|
-
}
|
|
11152
|
-
const timeoutMs = Math.max(
|
|
11153
|
-
1,
|
|
11154
|
-
Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
11155
|
-
);
|
|
11156
|
-
const maxWaitMs = Math.max(
|
|
11157
|
-
timeoutMs,
|
|
11158
|
-
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
11159
|
-
);
|
|
11160
|
-
const allowTimeout = options.allowTimeout !== false;
|
|
11161
|
-
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
11162
|
-
() => ({ status: "done" }),
|
|
11163
|
-
(error) => {
|
|
11164
|
-
throw error;
|
|
11165
|
-
}
|
|
11166
|
-
);
|
|
11167
|
-
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
11168
|
-
throw new Error(
|
|
11169
|
-
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
11170
|
-
);
|
|
11171
|
-
}) : null;
|
|
11172
|
-
const startedAtMs = nowMs3();
|
|
11173
|
-
while (true) {
|
|
11174
|
-
const elapsedMs = Math.max(0, nowMs3() - startedAtMs);
|
|
11175
|
-
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
11176
|
-
if (remainingMs <= 0) {
|
|
11177
|
-
if (!allowTimeout) {
|
|
11178
|
-
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
11179
|
-
}
|
|
11180
|
-
console.warn(
|
|
11181
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
11182
|
-
);
|
|
11183
|
-
return false;
|
|
11184
|
-
}
|
|
11185
|
-
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
11186
|
-
let timeoutHandle = null;
|
|
11187
|
-
let resolveTimeoutPromise = null;
|
|
11188
|
-
let timeoutSettled = false;
|
|
11189
|
-
const settleTimeoutPromise = (value) => {
|
|
11190
|
-
if (timeoutSettled) {
|
|
11191
|
-
return;
|
|
11192
|
-
}
|
|
11193
|
-
timeoutSettled = true;
|
|
11194
|
-
resolveTimeoutPromise?.(value);
|
|
11195
|
-
};
|
|
11196
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
11197
|
-
resolveTimeoutPromise = resolve;
|
|
11198
|
-
timeoutHandle = setTimeout(
|
|
11199
|
-
() => settleTimeoutPromise({ status: "timeout" }),
|
|
11200
|
-
waitWindowMs
|
|
11201
|
-
);
|
|
11202
|
-
});
|
|
11203
|
-
let result;
|
|
11204
|
-
try {
|
|
11205
|
-
result = await Promise.race(
|
|
11206
|
-
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
11207
|
-
);
|
|
11208
|
-
} finally {
|
|
11209
|
-
if (timeoutHandle !== null) {
|
|
11210
|
-
clearTimeout(timeoutHandle);
|
|
11211
|
-
settleTimeoutPromise({ status: "cancelled" });
|
|
11212
|
-
}
|
|
11213
|
-
}
|
|
11214
|
-
if (result?.status === "done") {
|
|
11215
|
-
return true;
|
|
11216
|
-
}
|
|
11217
|
-
if (result?.status !== "timeout") {
|
|
11218
|
-
return true;
|
|
11219
|
-
}
|
|
11220
|
-
}
|
|
11606
|
+
}),
|
|
11607
|
+
resolve: createWavefrontDenoiseResolveBindGroup({
|
|
11608
|
+
device,
|
|
11609
|
+
denoiseResolveBindGroupLayout,
|
|
11610
|
+
configBuffer,
|
|
11611
|
+
inputView: denoiseScratchView,
|
|
11612
|
+
targetView: outputView,
|
|
11613
|
+
label: "plasius.wavefront.bind.denoise.scratchToOutput"
|
|
11614
|
+
}),
|
|
11615
|
+
directResolve: createWavefrontDenoiseResolveBindGroup({
|
|
11616
|
+
device,
|
|
11617
|
+
denoiseResolveBindGroupLayout,
|
|
11618
|
+
configBuffer,
|
|
11619
|
+
inputView: radianceView,
|
|
11620
|
+
targetView: outputView,
|
|
11621
|
+
label: "plasius.wavefront.bind.denoise.radianceToOutput"
|
|
11622
|
+
})
|
|
11623
|
+
});
|
|
11624
|
+
}
|
|
11625
|
+
function createWavefrontPresentBindGroup({
|
|
11626
|
+
device,
|
|
11627
|
+
presentBindGroupLayout,
|
|
11628
|
+
outputView,
|
|
11629
|
+
sampler
|
|
11630
|
+
}) {
|
|
11631
|
+
return device.createBindGroup({
|
|
11632
|
+
label: "plasius.wavefront.presentBindGroup",
|
|
11633
|
+
layout: presentBindGroupLayout,
|
|
11634
|
+
entries: [
|
|
11635
|
+
{ binding: 0, resource: outputView },
|
|
11636
|
+
{ binding: 1, resource: sampler }
|
|
11637
|
+
]
|
|
11638
|
+
});
|
|
11221
11639
|
}
|
|
11222
11640
|
|
|
11223
11641
|
// src/wavefront-frame-telemetry.js
|
|
@@ -12044,7 +12462,7 @@ function dispatchWavefrontFrameAwaitingGpu({
|
|
|
12044
12462
|
}
|
|
12045
12463
|
|
|
12046
12464
|
// src/wavefront-pipelines.js
|
|
12047
|
-
function createWavefrontBindGroupLayouts(device, constants) {
|
|
12465
|
+
function createWavefrontBindGroupLayouts(device, constants, instanced = false) {
|
|
12048
12466
|
const trace = device.createBindGroupLayout({
|
|
12049
12467
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
12050
12468
|
entries: [
|
|
@@ -12096,7 +12514,8 @@ function createWavefrontBindGroupLayouts(device, constants) {
|
|
|
12096
12514
|
{ binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12097
12515
|
{ binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12098
12516
|
{ binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12099
|
-
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
12517
|
+
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12518
|
+
...instanced ? [{ binding: 47, visibility: constants.shader.COMPUTE, texture: { sampleType: "uint" } }] : []
|
|
12100
12519
|
]
|
|
12101
12520
|
});
|
|
12102
12521
|
const acceleration = device.createBindGroupLayout({
|
|
@@ -12291,12 +12710,12 @@ async function createWavefrontPresentPipeline(device, presentBindGroupLayout, fo
|
|
|
12291
12710
|
primitive: { topology: "triangle-list" }
|
|
12292
12711
|
});
|
|
12293
12712
|
}
|
|
12294
|
-
async function createWavefrontPipelineResources({ device, constants, format }) {
|
|
12295
|
-
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants);
|
|
12713
|
+
async function createWavefrontPipelineResources({ device, constants, format, instanced = false }) {
|
|
12714
|
+
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants, instanced);
|
|
12296
12715
|
const pipelineLayouts = createWavefrontPipelineLayouts(device, bindGroupLayouts);
|
|
12297
12716
|
const computeShader = device.createShaderModule({
|
|
12298
12717
|
label: "plasius.wavefront.computeShader",
|
|
12299
|
-
code:
|
|
12718
|
+
code: createWavefrontPathTracingComputeShaderSource({ instanced })
|
|
12300
12719
|
});
|
|
12301
12720
|
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
12302
12721
|
return Object.freeze({
|
|
@@ -12729,890 +13148,911 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
12729
13148
|
if (!adapter) {
|
|
12730
13149
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
12731
13150
|
}
|
|
12732
|
-
const
|
|
13151
|
+
const rawDevice = await adapter.requestDevice(
|
|
12733
13152
|
createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
|
|
12734
13153
|
);
|
|
12735
|
-
const
|
|
12736
|
-
const
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
const accumulationBuffer = createBuffer(
|
|
12773
|
-
device,
|
|
12774
|
-
bufferUsage,
|
|
12775
|
-
memory.accumulationBytes,
|
|
12776
|
-
"plasius.wavefront.accumulation"
|
|
12777
|
-
);
|
|
12778
|
-
const pathVertexBuffer = createBuffer(
|
|
12779
|
-
device,
|
|
12780
|
-
bufferUsage,
|
|
12781
|
-
memory.pathVertexBytes,
|
|
12782
|
-
"plasius.wavefront.pathVertices"
|
|
12783
|
-
);
|
|
12784
|
-
const sceneObjectBuffer = createBuffer(
|
|
12785
|
-
device,
|
|
12786
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12787
|
-
memory.sceneObjectBytes,
|
|
12788
|
-
"plasius.wavefront.sceneObjects"
|
|
12789
|
-
);
|
|
12790
|
-
const triangleBuffer = createBuffer(
|
|
12791
|
-
device,
|
|
12792
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12793
|
-
memory.triangleBytes,
|
|
12794
|
-
"plasius.wavefront.triangles"
|
|
12795
|
-
);
|
|
12796
|
-
const bvhNodeBuffer = createBuffer(
|
|
12797
|
-
device,
|
|
12798
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12799
|
-
memory.bvhCombinedBytes,
|
|
12800
|
-
"plasius.wavefront.bvhNodes"
|
|
12801
|
-
);
|
|
12802
|
-
const meshVertexBuffer = createBuffer(
|
|
12803
|
-
device,
|
|
12804
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12805
|
-
memory.meshVertexBytes,
|
|
12806
|
-
"plasius.wavefront.meshVertices"
|
|
12807
|
-
);
|
|
12808
|
-
const meshIndexBuffer = createBuffer(
|
|
12809
|
-
device,
|
|
12810
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12811
|
-
memory.meshIndexBytes,
|
|
12812
|
-
"plasius.wavefront.meshIndices"
|
|
12813
|
-
);
|
|
12814
|
-
const meshRangeBuffer = createBuffer(
|
|
12815
|
-
device,
|
|
12816
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12817
|
-
memory.meshRangeBytes,
|
|
12818
|
-
"plasius.wavefront.meshRanges"
|
|
12819
|
-
);
|
|
12820
|
-
const environmentPortalBuffer = createBuffer(
|
|
12821
|
-
device,
|
|
12822
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12823
|
-
memory.environmentPortalBytes,
|
|
12824
|
-
"plasius.wavefront.environmentPortals"
|
|
12825
|
-
);
|
|
12826
|
-
const bvhLeafRefBuffer = createBuffer(
|
|
12827
|
-
device,
|
|
12828
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12829
|
-
memory.bvhLeafReferenceBytes,
|
|
12830
|
-
"plasius.wavefront.bvhLeafRefs"
|
|
12831
|
-
);
|
|
12832
|
-
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
12833
|
-
const configBufferStride = memory.configBufferStride;
|
|
12834
|
-
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
12835
|
-
const frameConfigSlotCount = Math.max(
|
|
12836
|
-
1,
|
|
12837
|
-
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise || config.progressive ? 1 : 0)
|
|
12838
|
-
);
|
|
12839
|
-
const configBuffer = createBuffer(
|
|
12840
|
-
device,
|
|
12841
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12842
|
-
memory.frameConfigBytes,
|
|
12843
|
-
"plasius.wavefront.frameConfig"
|
|
12844
|
-
);
|
|
12845
|
-
const bvhBuildConfigBuffer = createBuffer(
|
|
12846
|
-
device,
|
|
12847
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12848
|
-
memory.bvhBuildConfigBytes,
|
|
12849
|
-
"plasius.wavefront.bvhBuildConfig"
|
|
12850
|
-
);
|
|
12851
|
-
const counterBuffer = createBuffer(
|
|
12852
|
-
device,
|
|
12853
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
12854
|
-
memory.counterBytes,
|
|
12855
|
-
"plasius.wavefront.counters"
|
|
12856
|
-
);
|
|
12857
|
-
const activeDispatchBuffer = createBuffer(
|
|
12858
|
-
device,
|
|
12859
|
-
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
12860
|
-
memory.indirectDispatchBytes,
|
|
12861
|
-
"plasius.wavefront.activeDispatchArgs"
|
|
12862
|
-
);
|
|
12863
|
-
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
12864
|
-
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
12865
|
-
const packedTriangles = packWavefrontTriangles(
|
|
12866
|
-
config.meshAcceleration.triangles,
|
|
12867
|
-
Math.max(1, config.triangleCapacity)
|
|
12868
|
-
);
|
|
12869
|
-
const packedBvhNodes = packWavefrontBvhNodes(
|
|
12870
|
-
config.meshAcceleration.nodes,
|
|
12871
|
-
Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity)
|
|
12872
|
-
);
|
|
12873
|
-
const packedBvhNodeUints = new Uint32Array(packedBvhNodes.buffer);
|
|
12874
|
-
const packedBvhNodeFloats = new Float32Array(packedBvhNodes.buffer);
|
|
12875
|
-
const orderedEmissiveTriangleIndices = resolveOrderedEmissiveTriangleIndices(config);
|
|
12876
|
-
const emissiveWeights = orderedEmissiveTriangleIndices.map(
|
|
12877
|
-
(triangleIndex) => emissiveTriangleWeight(config.meshAcceleration.triangles[triangleIndex])
|
|
12878
|
-
);
|
|
12879
|
-
const totalEmissiveWeight = emissiveWeights.reduce((total, weight) => total + weight, 0);
|
|
12880
|
-
let cumulativeEmissiveWeight = 0;
|
|
12881
|
-
orderedEmissiveTriangleIndices.forEach((triangleIndex, index) => {
|
|
12882
|
-
const nodeOffset = (config.bvhNodeCapacity + index) * (BVH_NODE_RECORD_BYTES / 4);
|
|
12883
|
-
cumulativeEmissiveWeight += emissiveWeights[index] ?? 0;
|
|
12884
|
-
packedBvhNodeFloats[nodeOffset] = cumulativeEmissiveWeight;
|
|
12885
|
-
packedBvhNodeFloats[nodeOffset + 1] = emissiveWeights[index] ?? 0;
|
|
12886
|
-
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
12887
|
-
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
12888
|
-
});
|
|
12889
|
-
device.queue.writeBuffer(triangleBuffer, 0, packedTriangles.buffer);
|
|
12890
|
-
device.queue.writeBuffer(bvhNodeBuffer, 0, packedBvhNodes.buffer);
|
|
12891
|
-
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
12892
|
-
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
12893
|
-
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
12894
|
-
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
12895
|
-
config.environmentPortals,
|
|
12896
|
-
Math.max(1, config.environmentPortalCapacity)
|
|
12897
|
-
);
|
|
12898
|
-
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
12899
|
-
const radianceTexture = device.createTexture({
|
|
12900
|
-
label: "plasius.wavefront.radiance",
|
|
12901
|
-
size: { width: config.width, height: config.height },
|
|
12902
|
-
format: "rgba16float",
|
|
12903
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
12904
|
-
});
|
|
12905
|
-
const radianceView = radianceTexture.createView();
|
|
12906
|
-
const denoiseScratchTexture = device.createTexture({
|
|
12907
|
-
label: "plasius.wavefront.denoiseScratch",
|
|
12908
|
-
size: { width: config.width, height: config.height },
|
|
12909
|
-
format: "rgba16float",
|
|
12910
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING
|
|
12911
|
-
});
|
|
12912
|
-
const denoiseScratchView = denoiseScratchTexture.createView();
|
|
12913
|
-
const outputTexture = device.createTexture({
|
|
12914
|
-
label: "plasius.wavefront.output",
|
|
12915
|
-
size: { width: config.width, height: config.height },
|
|
12916
|
-
format: "rgba8unorm",
|
|
12917
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_SRC
|
|
12918
|
-
});
|
|
12919
|
-
const outputView = outputTexture.createView();
|
|
12920
|
-
const sampler = device.createSampler({
|
|
12921
|
-
label: "plasius.wavefront.presentSampler",
|
|
12922
|
-
magFilter: "nearest",
|
|
12923
|
-
minFilter: "nearest"
|
|
12924
|
-
});
|
|
12925
|
-
const environmentMapResource = createEnvironmentMapResource(
|
|
12926
|
-
device,
|
|
12927
|
-
constants,
|
|
12928
|
-
config.environmentMap,
|
|
12929
|
-
config.environmentColor
|
|
12930
|
-
);
|
|
12931
|
-
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
12932
|
-
device,
|
|
12933
|
-
constants,
|
|
12934
|
-
config.environmentMap,
|
|
12935
|
-
config.environmentColor
|
|
12936
|
-
);
|
|
12937
|
-
let mediumTextureResource = createMediumTextureResource(
|
|
12938
|
-
device,
|
|
12939
|
-
constants,
|
|
12940
|
-
config.mediums
|
|
12941
|
-
);
|
|
12942
|
-
config = Object.freeze({
|
|
12943
|
-
...config,
|
|
12944
|
-
environmentMap: Object.freeze({
|
|
12945
|
-
...config.environmentMap,
|
|
12946
|
-
width: environmentMapResource.width,
|
|
12947
|
-
height: environmentMapResource.height,
|
|
12948
|
-
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
12949
|
-
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
12950
|
-
})
|
|
12951
|
-
});
|
|
12952
|
-
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
12953
|
-
const baseColorAtlasResource = createAtlasTextureResource(
|
|
12954
|
-
device,
|
|
12955
|
-
constants,
|
|
12956
|
-
config.gpuMaterialSource.baseColorAtlas,
|
|
12957
|
-
"plasius.wavefront.materialAtlas.baseColor"
|
|
12958
|
-
);
|
|
12959
|
-
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
12960
|
-
device,
|
|
12961
|
-
constants,
|
|
12962
|
-
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
12963
|
-
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
12964
|
-
);
|
|
12965
|
-
const normalAtlasResource = createAtlasTextureResource(
|
|
12966
|
-
device,
|
|
12967
|
-
constants,
|
|
12968
|
-
config.gpuMaterialSource.normalAtlas,
|
|
12969
|
-
"plasius.wavefront.materialAtlas.normal"
|
|
12970
|
-
);
|
|
12971
|
-
const occlusionAtlasResource = createAtlasTextureResource(
|
|
12972
|
-
device,
|
|
12973
|
-
constants,
|
|
12974
|
-
config.gpuMaterialSource.occlusionAtlas,
|
|
12975
|
-
"plasius.wavefront.materialAtlas.occlusion"
|
|
12976
|
-
);
|
|
12977
|
-
const emissiveAtlasResource = createAtlasTextureResource(
|
|
12978
|
-
device,
|
|
12979
|
-
constants,
|
|
12980
|
-
config.gpuMaterialSource.emissiveAtlas,
|
|
12981
|
-
"plasius.wavefront.materialAtlas.emissive"
|
|
12982
|
-
);
|
|
12983
|
-
const extensionAtlasNames = [
|
|
12984
|
-
"clearcoat",
|
|
12985
|
-
"clearcoatRoughness",
|
|
12986
|
-
"clearcoatNormal",
|
|
12987
|
-
"transmission",
|
|
12988
|
-
"thickness",
|
|
12989
|
-
"sheenColor",
|
|
12990
|
-
"sheenRoughness",
|
|
12991
|
-
"specular",
|
|
12992
|
-
"specularColor",
|
|
12993
|
-
"iridescence",
|
|
12994
|
-
"iridescenceThickness",
|
|
12995
|
-
"anisotropy"
|
|
12996
|
-
];
|
|
12997
|
-
const extensionAtlasResources = Object.freeze(
|
|
12998
|
-
Object.fromEntries(
|
|
12999
|
-
extensionAtlasNames.map((name) => [
|
|
13000
|
-
name,
|
|
13001
|
-
createAtlasTextureResource(
|
|
13154
|
+
const allocationTracker = createGpuAllocationTracker(rawDevice, { budgetBytes: options.memoryBudgetBytes, storageUsage: constants.buffer.STORAGE });
|
|
13155
|
+
const device = allocationTracker.device;
|
|
13156
|
+
let context, finishAllocationScopes;
|
|
13157
|
+
try {
|
|
13158
|
+
let createTraceBindGroups = function() {
|
|
13159
|
+
return createWavefrontTraceBindGroups({
|
|
13160
|
+
device,
|
|
13161
|
+
traceBindGroupLayout,
|
|
13162
|
+
activeQueue,
|
|
13163
|
+
nextQueue,
|
|
13164
|
+
frameConfigBuffer: configBuffer,
|
|
13165
|
+
hitBuffer,
|
|
13166
|
+
accumulationBuffer,
|
|
13167
|
+
sceneObjectBuffer,
|
|
13168
|
+
counterBuffer,
|
|
13169
|
+
outputView,
|
|
13170
|
+
triangleBuffer,
|
|
13171
|
+
instanceView,
|
|
13172
|
+
bvhNodeBuffer,
|
|
13173
|
+
radianceView,
|
|
13174
|
+
environmentPortalBuffer,
|
|
13175
|
+
environmentMapResource,
|
|
13176
|
+
pathVertexBuffer,
|
|
13177
|
+
baseColorAtlasResource,
|
|
13178
|
+
metallicRoughnessAtlasResource,
|
|
13179
|
+
normalAtlasResource,
|
|
13180
|
+
occlusionAtlasResource,
|
|
13181
|
+
emissiveAtlasResource,
|
|
13182
|
+
materialAtlasSampler,
|
|
13183
|
+
brdfLutResource,
|
|
13184
|
+
environmentSamplingResource,
|
|
13185
|
+
mediumTextureResource,
|
|
13186
|
+
extensionAtlasResources
|
|
13187
|
+
});
|
|
13188
|
+
}, ensureFrameTelemetryResources = function() {
|
|
13189
|
+
if (!frameTelemetryResources) {
|
|
13190
|
+
frameTelemetryResources = createWavefrontFrameTelemetryResources({
|
|
13002
13191
|
device,
|
|
13003
13192
|
constants,
|
|
13004
|
-
config.
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
)
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
})
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
bvhLeafRefBuffer
|
|
13069
|
-
});
|
|
13070
|
-
const {
|
|
13071
|
-
radiance: denoiseRadianceBindGroup,
|
|
13072
|
-
resolve: denoiseResolveBindGroup,
|
|
13073
|
-
directResolve: denoiseDirectResolveBindGroup
|
|
13074
|
-
} = createWavefrontDenoiseBindGroups({
|
|
13075
|
-
device,
|
|
13076
|
-
denoiseRadianceBindGroupLayout,
|
|
13077
|
-
denoiseResolveBindGroupLayout,
|
|
13078
|
-
configBuffer,
|
|
13079
|
-
radianceView,
|
|
13080
|
-
denoiseScratchView,
|
|
13081
|
-
outputView
|
|
13082
|
-
});
|
|
13083
|
-
const presentBindGroup = createWavefrontPresentBindGroup({
|
|
13084
|
-
device,
|
|
13085
|
-
presentBindGroupLayout,
|
|
13086
|
-
outputView,
|
|
13087
|
-
sampler
|
|
13088
|
-
});
|
|
13089
|
-
const progressive = await createWavefrontProgressiveResources({ device, constants, config, configBuffer, radianceTexture, radianceView, outputView });
|
|
13090
|
-
let activeFrameTelemetry = null;
|
|
13091
|
-
let frameTelemetryResources = null;
|
|
13092
|
-
const frameEncoder = createWavefrontFrameEncoder({
|
|
13093
|
-
getConfig: () => config,
|
|
13094
|
-
progressive,
|
|
13095
|
-
getBindGroups: () => bindGroups,
|
|
13096
|
-
pipelines,
|
|
13097
|
-
counterBuffer,
|
|
13098
|
-
activeDispatchBuffer,
|
|
13099
|
-
denoiseRadianceBindGroup,
|
|
13100
|
-
denoiseResolveBindGroup,
|
|
13101
|
-
denoiseDirectResolveBindGroup,
|
|
13102
|
-
presentPipeline,
|
|
13103
|
-
presentBindGroup,
|
|
13104
|
-
context,
|
|
13105
|
-
getFrameTelemetry: () => activeFrameTelemetry
|
|
13106
|
-
});
|
|
13107
|
-
let frame = 0;
|
|
13108
|
-
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
13109
|
-
let accelerationBuildCount = 0;
|
|
13110
|
-
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA2;
|
|
13111
|
-
let lastCompletedFrameTimeMs = null;
|
|
13112
|
-
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
13113
|
-
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
13114
|
-
gpuAdapterParallelism,
|
|
13115
|
-
createGpuParallelismCounters()
|
|
13116
|
-
);
|
|
13117
|
-
const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
|
|
13118
|
-
function ensureFrameTelemetryResources() {
|
|
13119
|
-
if (!frameTelemetryResources) {
|
|
13120
|
-
frameTelemetryResources = createWavefrontFrameTelemetryResources({
|
|
13193
|
+
maxRayCountRecords: tiles.length * config.samplesPerPixel * config.maxDepth
|
|
13194
|
+
});
|
|
13195
|
+
}
|
|
13196
|
+
return frameTelemetryResources;
|
|
13197
|
+
}, resolveRenderedSamplesPerPixel = function(renderOptions = {}, awaitGPUCompletion = true) {
|
|
13198
|
+
return resolveWavefrontRenderedSamplesPerPixel({
|
|
13199
|
+
config,
|
|
13200
|
+
renderOptions,
|
|
13201
|
+
awaitGPUCompletion,
|
|
13202
|
+
lastCompletedFrameTimeMs,
|
|
13203
|
+
lastCompletedSamplesPerPixel
|
|
13204
|
+
});
|
|
13205
|
+
}, createFrameStats = function({
|
|
13206
|
+
frameIndex,
|
|
13207
|
+
accelerationBuildSubmitted,
|
|
13208
|
+
frameSubmissionCount,
|
|
13209
|
+
parallelismCounters,
|
|
13210
|
+
renderedSamplesPerPixel,
|
|
13211
|
+
targetSamplesPerPixel,
|
|
13212
|
+
frameTimeBudgetMs,
|
|
13213
|
+
budgetConstrained
|
|
13214
|
+
}) {
|
|
13215
|
+
const stats = createWavefrontFrameStats({
|
|
13216
|
+
config,
|
|
13217
|
+
tiles,
|
|
13218
|
+
frame,
|
|
13219
|
+
frameIndex,
|
|
13220
|
+
accelerationBuilt,
|
|
13221
|
+
accelerationBuildCount,
|
|
13222
|
+
accelerationBuildSubmitted,
|
|
13223
|
+
frameSubmissionCount,
|
|
13224
|
+
frameConfigSlotCount,
|
|
13225
|
+
gpuAdapterParallelism,
|
|
13226
|
+
parallelismCounters,
|
|
13227
|
+
renderedSamplesPerPixel,
|
|
13228
|
+
targetSamplesPerPixel,
|
|
13229
|
+
frameTimeBudgetMs,
|
|
13230
|
+
budgetConstrained
|
|
13231
|
+
});
|
|
13232
|
+
lastGpuParallelism = stats.gpuParallelism;
|
|
13233
|
+
return stats.frameStats;
|
|
13234
|
+
}, writeFrameConfigSlot = function(slot, tile, frameIndex, buildRange = {}) {
|
|
13235
|
+
if (slot >= frameConfigSlotCount) {
|
|
13236
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
13237
|
+
}
|
|
13238
|
+
const offset = slot * configBufferStride;
|
|
13239
|
+
device.queue.writeBuffer(
|
|
13240
|
+
configBuffer,
|
|
13241
|
+
offset,
|
|
13242
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
13243
|
+
);
|
|
13244
|
+
return offset;
|
|
13245
|
+
}, createFrameConfigWriter = function(frameIndex) {
|
|
13246
|
+
let slot = 0;
|
|
13247
|
+
return (tile, buildRange = {}) => {
|
|
13248
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
13249
|
+
slot += 1;
|
|
13250
|
+
return offset;
|
|
13251
|
+
};
|
|
13252
|
+
}, dispatchGpuAccelerationBuild = function(frameIndex, parallelism) {
|
|
13253
|
+
const submitted = dispatchWavefrontGpuAccelerationBuild({
|
|
13254
|
+
config,
|
|
13255
|
+
accelerationBuilt,
|
|
13256
|
+
tiles,
|
|
13121
13257
|
device,
|
|
13122
|
-
|
|
13123
|
-
|
|
13258
|
+
bvhBuildConfigBuffer,
|
|
13259
|
+
configBufferStride,
|
|
13260
|
+
bvhBuildBindGroup,
|
|
13261
|
+
pipelines,
|
|
13262
|
+
parallelism,
|
|
13263
|
+
frameIndex
|
|
13264
|
+
});
|
|
13265
|
+
if (submitted) {
|
|
13266
|
+
accelerationBuilt = true;
|
|
13267
|
+
accelerationBuildCount += 1;
|
|
13268
|
+
}
|
|
13269
|
+
return submitted;
|
|
13270
|
+
}, dispatchFrame = function(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
13271
|
+
return dispatchWavefrontFrame({
|
|
13272
|
+
config,
|
|
13273
|
+
tiles,
|
|
13274
|
+
device,
|
|
13275
|
+
frameIndex,
|
|
13276
|
+
parallelism,
|
|
13277
|
+
renderedSamplesPerPixel,
|
|
13278
|
+
frameEncoder,
|
|
13279
|
+
createFrameConfigWriter
|
|
13280
|
+
});
|
|
13281
|
+
}, renderOnce = function(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
13282
|
+
const frameStartTimeMs = nowMs3();
|
|
13283
|
+
frame += 1;
|
|
13284
|
+
const frameIndex = frame + config.frameIndex;
|
|
13285
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
13286
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
13287
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13288
|
+
const frameSubmissionCount = dispatchFrame(
|
|
13289
|
+
frameIndex,
|
|
13290
|
+
parallelismCounters,
|
|
13291
|
+
samplingPlan.renderedSamplesPerPixel
|
|
13292
|
+
);
|
|
13293
|
+
const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
|
|
13294
|
+
return Object.freeze({
|
|
13295
|
+
...createFrameStats({
|
|
13296
|
+
frameIndex,
|
|
13297
|
+
accelerationBuildSubmitted,
|
|
13298
|
+
frameSubmissionCount,
|
|
13299
|
+
parallelismCounters,
|
|
13300
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13301
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13302
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13303
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
13304
|
+
}),
|
|
13305
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
13306
|
+
lastGpuParallelism,
|
|
13307
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
13308
|
+
frameTimeMs,
|
|
13309
|
+
false
|
|
13310
|
+
),
|
|
13311
|
+
timings: createWavefrontFrameTimingTelemetry({
|
|
13312
|
+
status: "not-requested",
|
|
13313
|
+
source: "cpu-submit",
|
|
13314
|
+
timestampQueryStatus: "not-recorded",
|
|
13315
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
13316
|
+
})
|
|
13317
|
+
});
|
|
13318
|
+
}, dispatchFrameAwaitingGpu = function(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
|
|
13319
|
+
return dispatchWavefrontFrameAwaitingGpu({
|
|
13320
|
+
config,
|
|
13321
|
+
tiles,
|
|
13322
|
+
device,
|
|
13323
|
+
frameIndex,
|
|
13324
|
+
parallelism,
|
|
13325
|
+
renderedSamplesPerPixel,
|
|
13326
|
+
frameEncoder,
|
|
13327
|
+
writeFrameConfigSlot,
|
|
13328
|
+
optionsForFrame
|
|
13329
|
+
});
|
|
13330
|
+
}, rebuildLiveConfig = function(overrides = {}) {
|
|
13331
|
+
return createWavefrontPathTracingComputeConfig({
|
|
13332
|
+
...options,
|
|
13333
|
+
canvas,
|
|
13334
|
+
width: config.width,
|
|
13335
|
+
height: config.height,
|
|
13336
|
+
maxDepth: config.maxDepth,
|
|
13337
|
+
tileSize: config.tileSize,
|
|
13338
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
13339
|
+
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
13340
|
+
sceneObjects: packedScene.objects,
|
|
13341
|
+
camera: activeCameraOptions,
|
|
13342
|
+
environmentMap: {
|
|
13343
|
+
...config.environmentMap
|
|
13344
|
+
},
|
|
13345
|
+
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13346
|
+
featureFlags: options.featureFlags,
|
|
13347
|
+
frameIndex: config.frameIndex,
|
|
13348
|
+
...overrides
|
|
13349
|
+
});
|
|
13350
|
+
}, rebuildMediumResources = function(nextConfig) {
|
|
13351
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
13352
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
13353
|
+
bindGroups = createTraceBindGroups();
|
|
13354
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
13355
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
13356
|
+
}
|
|
13357
|
+
}, updateSceneObjects = function(sceneObjects) {
|
|
13358
|
+
progressive?.reset();
|
|
13359
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
13360
|
+
packedScene = nextPackedScene;
|
|
13361
|
+
const nextConfig = rebuildLiveConfig();
|
|
13362
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
13363
|
+
rebuildMediumResources(nextConfig);
|
|
13364
|
+
}
|
|
13365
|
+
config = nextConfig;
|
|
13366
|
+
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
13367
|
+
return config;
|
|
13368
|
+
}, updateCamera = function(cameraOptions = {}) {
|
|
13369
|
+
progressive?.reset();
|
|
13370
|
+
activeCameraOptions = cameraOptions;
|
|
13371
|
+
config = updateWavefrontCameraConfig(config, cameraOptions);
|
|
13372
|
+
return config;
|
|
13373
|
+
}, getSnapshot = function() {
|
|
13374
|
+
return Object.freeze({
|
|
13375
|
+
frame,
|
|
13376
|
+
accumulatedSamplesPerPixel: progressive?.samples ?? 0,
|
|
13377
|
+
width: config.width,
|
|
13378
|
+
height: config.height,
|
|
13379
|
+
maxDepth: config.maxDepth,
|
|
13380
|
+
tiles: tiles.length,
|
|
13381
|
+
tileSize: config.tileSize,
|
|
13382
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
13383
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
13384
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
13385
|
+
triangleCount: config.triangleCount,
|
|
13386
|
+
instanceCount: config.instancing?.instanceCount ?? 0,
|
|
13387
|
+
geometryCount: config.instancing?.geometryCount ?? config.gpuMeshSource.meshes.count,
|
|
13388
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
13389
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
13390
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
13391
|
+
mediumCount: config.mediumCount,
|
|
13392
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
13393
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
13394
|
+
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13395
|
+
transportExperiments: config.transportExperiments,
|
|
13396
|
+
transportExperimentFlags: config.transportExperimentFlags,
|
|
13397
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
13398
|
+
displayQuality: config.displayQuality,
|
|
13399
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
13400
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
13401
|
+
accelerationBuilt,
|
|
13402
|
+
accelerationBuildCount,
|
|
13403
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
13404
|
+
gpuParallelism: lastGpuParallelism,
|
|
13405
|
+
memory: config.memory,
|
|
13406
|
+
allocations: allocationTracker.snapshot()
|
|
13407
|
+
});
|
|
13408
|
+
}, destroy = function() {
|
|
13409
|
+
progressive?.destroy();
|
|
13410
|
+
frameTelemetryResources?.destroy?.();
|
|
13411
|
+
activeQueue.destroy?.();
|
|
13412
|
+
nextQueue.destroy?.();
|
|
13413
|
+
hitBuffer.destroy?.();
|
|
13414
|
+
accumulationBuffer.destroy?.();
|
|
13415
|
+
pathVertexBuffer.destroy?.();
|
|
13416
|
+
sceneObjectBuffer.destroy?.();
|
|
13417
|
+
triangleBuffer.destroy?.();
|
|
13418
|
+
instanceTexture?.destroy?.();
|
|
13419
|
+
bvhNodeBuffer.destroy?.();
|
|
13420
|
+
meshVertexBuffer.destroy?.();
|
|
13421
|
+
meshIndexBuffer.destroy?.();
|
|
13422
|
+
meshRangeBuffer.destroy?.();
|
|
13423
|
+
environmentPortalBuffer.destroy?.();
|
|
13424
|
+
bvhLeafRefBuffer.destroy?.();
|
|
13425
|
+
configBuffer.destroy?.();
|
|
13426
|
+
bvhBuildConfigBuffer.destroy?.();
|
|
13427
|
+
counterBuffer.destroy?.();
|
|
13428
|
+
activeDispatchBuffer.destroy?.();
|
|
13429
|
+
radianceTexture.destroy?.();
|
|
13430
|
+
denoiseScratchTexture.destroy?.();
|
|
13431
|
+
outputTexture.destroy?.();
|
|
13432
|
+
if (environmentMapResource.ownsTexture) {
|
|
13433
|
+
environmentMapResource.texture?.destroy?.();
|
|
13434
|
+
}
|
|
13435
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
13436
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
13437
|
+
}
|
|
13438
|
+
if (mediumTextureResource.ownsTexture) {
|
|
13439
|
+
mediumTextureResource.texture?.destroy?.();
|
|
13440
|
+
}
|
|
13441
|
+
brdfLutResource.texture?.destroy?.();
|
|
13442
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
13443
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
13444
|
+
}
|
|
13445
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
13446
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
13447
|
+
}
|
|
13448
|
+
if (normalAtlasResource.ownsTexture) {
|
|
13449
|
+
normalAtlasResource.texture?.destroy?.();
|
|
13450
|
+
}
|
|
13451
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
13452
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
13453
|
+
}
|
|
13454
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
13455
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
13456
|
+
}
|
|
13457
|
+
Object.values(extensionAtlasResources).forEach((resource) => {
|
|
13458
|
+
if (resource.ownsTexture) {
|
|
13459
|
+
resource.texture?.destroy?.();
|
|
13460
|
+
}
|
|
13461
|
+
});
|
|
13462
|
+
context.unconfigure?.();
|
|
13463
|
+
allocationTracker.destroy();
|
|
13464
|
+
};
|
|
13465
|
+
finishAllocationScopes = openGpuAllocationErrorScopes(rawDevice);
|
|
13466
|
+
const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
|
|
13467
|
+
context = canvas.getContext("webgpu");
|
|
13468
|
+
if (!context || typeof context.configure !== "function") {
|
|
13469
|
+
throw new Error("Canvas WebGPU context does not support configure().");
|
|
13470
|
+
}
|
|
13471
|
+
const format = options.format ?? (typeof navigatorRef.gpu.getPreferredCanvasFormat === "function" ? navigatorRef.gpu.getPreferredCanvasFormat() : "bgra8unorm");
|
|
13472
|
+
let config = initialConfig;
|
|
13473
|
+
const safeTileSize = clampTileSizeForDevice(config, device);
|
|
13474
|
+
if (safeTileSize !== config.tileSize) {
|
|
13475
|
+
config = createWavefrontPathTracingComputeConfig({
|
|
13476
|
+
...options,
|
|
13477
|
+
canvas,
|
|
13478
|
+
width: config.width,
|
|
13479
|
+
height: config.height,
|
|
13480
|
+
tileSize: safeTileSize,
|
|
13481
|
+
tilePixelCapacity: Math.min(
|
|
13482
|
+
config.tilePixelCapacity,
|
|
13483
|
+
safeTileSize * safeTileSize
|
|
13484
|
+
)
|
|
13124
13485
|
});
|
|
13125
13486
|
}
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13487
|
+
config = Object.freeze({
|
|
13488
|
+
...config,
|
|
13489
|
+
memory: estimateWavefrontPathTracingMemoryForConfig(config, device.limits)
|
|
13490
|
+
});
|
|
13491
|
+
canvas.width = config.width;
|
|
13492
|
+
canvas.height = config.height;
|
|
13493
|
+
context.configure({
|
|
13494
|
+
device: rawDevice,
|
|
13495
|
+
format,
|
|
13496
|
+
alphaMode: options.alpha === true ? "premultiplied" : "opaque"
|
|
13497
|
+
});
|
|
13498
|
+
const bufferUsage = constants.buffer.STORAGE | constants.buffer.COPY_DST;
|
|
13499
|
+
const memory = config.memory;
|
|
13500
|
+
let instanceTexture = null, instanceView = null;
|
|
13501
|
+
if (config.instancing) {
|
|
13502
|
+
const count = config.instancing.instanceCount;
|
|
13503
|
+
instanceTexture = device.createTexture({
|
|
13504
|
+
label: "plasius.wavefront.instances",
|
|
13505
|
+
size: { width: 9, height: count },
|
|
13506
|
+
format: "rgba32uint",
|
|
13507
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
13508
|
+
});
|
|
13509
|
+
const upload = new Uint8Array(count * 256), source = new Uint8Array(config.instancing.buffer);
|
|
13510
|
+
for (let i = 0; i < count; i++) upload.set(source.subarray(i * 144, (i + 1) * 144), i * 256);
|
|
13511
|
+
device.queue.writeTexture({ texture: instanceTexture }, upload, { bytesPerRow: 256, rowsPerImage: count }, { width: 9, height: count, depthOrArrayLayers: 1 });
|
|
13512
|
+
instanceView = instanceTexture.createView();
|
|
13513
|
+
}
|
|
13514
|
+
const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
|
|
13515
|
+
const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
|
|
13516
|
+
const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
|
|
13517
|
+
const accumulationBuffer = createBuffer(
|
|
13518
|
+
device,
|
|
13519
|
+
bufferUsage,
|
|
13520
|
+
memory.accumulationBytes,
|
|
13521
|
+
"plasius.wavefront.accumulation"
|
|
13522
|
+
);
|
|
13523
|
+
const pathVertexBuffer = createBuffer(
|
|
13524
|
+
device,
|
|
13525
|
+
bufferUsage,
|
|
13526
|
+
memory.pathVertexBytes,
|
|
13527
|
+
"plasius.wavefront.pathVertices"
|
|
13528
|
+
);
|
|
13529
|
+
const sceneObjectBuffer = createBuffer(
|
|
13530
|
+
device,
|
|
13531
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13532
|
+
memory.sceneObjectBytes,
|
|
13533
|
+
"plasius.wavefront.sceneObjects"
|
|
13534
|
+
);
|
|
13535
|
+
const triangleBuffer = createBuffer(
|
|
13536
|
+
device,
|
|
13537
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13538
|
+
memory.triangleBytes,
|
|
13539
|
+
"plasius.wavefront.triangles"
|
|
13540
|
+
);
|
|
13541
|
+
const bvhNodeBuffer = createBuffer(
|
|
13542
|
+
device,
|
|
13543
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13544
|
+
memory.bvhCombinedBytes,
|
|
13545
|
+
"plasius.wavefront.bvhNodes"
|
|
13546
|
+
);
|
|
13547
|
+
const meshVertexBuffer = createBuffer(
|
|
13548
|
+
device,
|
|
13549
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13550
|
+
memory.meshVertexBytes,
|
|
13551
|
+
"plasius.wavefront.meshVertices"
|
|
13552
|
+
);
|
|
13553
|
+
const meshIndexBuffer = createBuffer(
|
|
13554
|
+
device,
|
|
13555
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13556
|
+
memory.meshIndexBytes,
|
|
13557
|
+
"plasius.wavefront.meshIndices"
|
|
13558
|
+
);
|
|
13559
|
+
const meshRangeBuffer = createBuffer(
|
|
13560
|
+
device,
|
|
13561
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13562
|
+
memory.meshRangeBytes,
|
|
13563
|
+
"plasius.wavefront.meshRanges"
|
|
13564
|
+
);
|
|
13565
|
+
const environmentPortalBuffer = createBuffer(
|
|
13566
|
+
device,
|
|
13567
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13568
|
+
memory.environmentPortalBytes,
|
|
13569
|
+
"plasius.wavefront.environmentPortals"
|
|
13570
|
+
);
|
|
13571
|
+
const bvhLeafRefBuffer = createBuffer(
|
|
13572
|
+
device,
|
|
13573
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
13574
|
+
memory.bvhLeafReferenceBytes,
|
|
13575
|
+
"plasius.wavefront.bvhLeafRefs"
|
|
13576
|
+
);
|
|
13577
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
13578
|
+
const configBufferStride = memory.configBufferStride;
|
|
13579
|
+
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
13580
|
+
const frameConfigSlotCount = Math.max(
|
|
13581
|
+
1,
|
|
13582
|
+
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise || config.progressive ? 1 : 0)
|
|
13583
|
+
);
|
|
13584
|
+
const configBuffer = createBuffer(
|
|
13585
|
+
device,
|
|
13586
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
13587
|
+
memory.frameConfigBytes,
|
|
13588
|
+
"plasius.wavefront.frameConfig"
|
|
13589
|
+
);
|
|
13590
|
+
const bvhBuildConfigBuffer = createBuffer(
|
|
13591
|
+
device,
|
|
13592
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
13593
|
+
memory.bvhBuildConfigBytes,
|
|
13594
|
+
"plasius.wavefront.bvhBuildConfig"
|
|
13595
|
+
);
|
|
13596
|
+
const counterBuffer = createBuffer(
|
|
13597
|
+
device,
|
|
13598
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
13599
|
+
memory.counterBytes,
|
|
13600
|
+
"plasius.wavefront.counters"
|
|
13601
|
+
);
|
|
13602
|
+
const activeDispatchBuffer = createBuffer(
|
|
13603
|
+
device,
|
|
13604
|
+
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
13605
|
+
memory.indirectDispatchBytes,
|
|
13606
|
+
"plasius.wavefront.activeDispatchArgs"
|
|
13607
|
+
);
|
|
13608
|
+
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
13609
|
+
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
13610
|
+
const packedTriangles = packWavefrontTriangles(
|
|
13611
|
+
config.meshAcceleration.triangles,
|
|
13612
|
+
Math.max(1, config.triangleCapacity)
|
|
13613
|
+
);
|
|
13614
|
+
const packedBvhNodes = packWavefrontBvhNodes(
|
|
13615
|
+
config.meshAcceleration.nodes,
|
|
13616
|
+
Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity)
|
|
13617
|
+
);
|
|
13618
|
+
const packedBvhNodeUints = new Uint32Array(packedBvhNodes.buffer);
|
|
13619
|
+
const packedBvhNodeFloats = new Float32Array(packedBvhNodes.buffer);
|
|
13620
|
+
const orderedEmissiveTriangleIndices = resolveOrderedEmissiveTriangleIndices(config);
|
|
13621
|
+
const emissiveWeights = orderedEmissiveTriangleIndices.map(
|
|
13622
|
+
(triangleIndex) => emissiveTriangleWeight(config.meshAcceleration.triangles[triangleIndex])
|
|
13623
|
+
);
|
|
13624
|
+
const totalEmissiveWeight = emissiveWeights.reduce((total, weight) => total + weight, 0);
|
|
13625
|
+
let cumulativeEmissiveWeight = 0;
|
|
13626
|
+
orderedEmissiveTriangleIndices.forEach((triangleIndex, index) => {
|
|
13627
|
+
const nodeOffset = (config.bvhNodeCapacity + index) * (BVH_NODE_RECORD_BYTES / 4);
|
|
13628
|
+
cumulativeEmissiveWeight += emissiveWeights[index] ?? 0;
|
|
13629
|
+
packedBvhNodeFloats[nodeOffset] = cumulativeEmissiveWeight;
|
|
13630
|
+
packedBvhNodeFloats[nodeOffset + 1] = emissiveWeights[index] ?? 0;
|
|
13631
|
+
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
13632
|
+
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
13135
13633
|
});
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
config,
|
|
13149
|
-
|
|
13150
|
-
|
|
13151
|
-
frameIndex,
|
|
13152
|
-
accelerationBuilt,
|
|
13153
|
-
accelerationBuildCount,
|
|
13154
|
-
accelerationBuildSubmitted,
|
|
13155
|
-
frameSubmissionCount,
|
|
13156
|
-
frameConfigSlotCount,
|
|
13157
|
-
gpuAdapterParallelism,
|
|
13158
|
-
parallelismCounters,
|
|
13159
|
-
renderedSamplesPerPixel,
|
|
13160
|
-
targetSamplesPerPixel,
|
|
13161
|
-
frameTimeBudgetMs,
|
|
13162
|
-
budgetConstrained
|
|
13634
|
+
await uploadWavefrontBuffer(device, triangleBuffer, packedTriangles.buffer);
|
|
13635
|
+
await uploadWavefrontBuffer(device, bvhNodeBuffer, packedBvhNodes.buffer);
|
|
13636
|
+
await uploadWavefrontBuffer(device, meshVertexBuffer, config.gpuMeshSource.vertices.buffer);
|
|
13637
|
+
await uploadWavefrontBuffer(device, meshIndexBuffer, config.gpuMeshSource.indices.buffer);
|
|
13638
|
+
await uploadWavefrontBuffer(device, meshRangeBuffer, config.gpuMeshSource.meshes.buffer);
|
|
13639
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
13640
|
+
config.environmentPortals,
|
|
13641
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
13642
|
+
);
|
|
13643
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
13644
|
+
const radianceTexture = device.createTexture({
|
|
13645
|
+
label: "plasius.wavefront.radiance",
|
|
13646
|
+
size: { width: config.width, height: config.height },
|
|
13647
|
+
format: "rgba16float",
|
|
13648
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
13163
13649
|
});
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13650
|
+
const radianceView = radianceTexture.createView();
|
|
13651
|
+
const denoiseScratchTexture = device.createTexture({
|
|
13652
|
+
label: "plasius.wavefront.denoiseScratch",
|
|
13653
|
+
size: { width: config.width, height: config.height },
|
|
13654
|
+
format: "rgba16float",
|
|
13655
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING
|
|
13656
|
+
});
|
|
13657
|
+
const denoiseScratchView = denoiseScratchTexture.createView();
|
|
13658
|
+
const outputTexture = device.createTexture({
|
|
13659
|
+
label: "plasius.wavefront.output",
|
|
13660
|
+
size: { width: config.width, height: config.height },
|
|
13661
|
+
format: "rgba8unorm",
|
|
13662
|
+
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_SRC
|
|
13663
|
+
});
|
|
13664
|
+
const outputView = outputTexture.createView();
|
|
13665
|
+
const sampler = device.createSampler({
|
|
13666
|
+
label: "plasius.wavefront.presentSampler",
|
|
13667
|
+
magFilter: "nearest",
|
|
13668
|
+
minFilter: "nearest"
|
|
13669
|
+
});
|
|
13670
|
+
const environmentMapResource = createEnvironmentMapResource(
|
|
13169
13671
|
device,
|
|
13170
13672
|
constants,
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
});
|
|
13174
|
-
}
|
|
13175
|
-
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
13176
|
-
if (slot >= frameConfigSlotCount) {
|
|
13177
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
13178
|
-
}
|
|
13179
|
-
const offset = slot * configBufferStride;
|
|
13180
|
-
device.queue.writeBuffer(
|
|
13181
|
-
configBuffer,
|
|
13182
|
-
offset,
|
|
13183
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
13673
|
+
config.environmentMap,
|
|
13674
|
+
config.environmentColor
|
|
13184
13675
|
);
|
|
13185
|
-
|
|
13186
|
-
}
|
|
13187
|
-
function createFrameConfigWriter(frameIndex) {
|
|
13188
|
-
let slot = 0;
|
|
13189
|
-
return (tile, buildRange = {}) => {
|
|
13190
|
-
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
13191
|
-
slot += 1;
|
|
13192
|
-
return offset;
|
|
13193
|
-
};
|
|
13194
|
-
}
|
|
13195
|
-
function dispatchGpuAccelerationBuild(frameIndex, parallelism) {
|
|
13196
|
-
const submitted = dispatchWavefrontGpuAccelerationBuild({
|
|
13197
|
-
config,
|
|
13198
|
-
accelerationBuilt,
|
|
13199
|
-
tiles,
|
|
13676
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
13200
13677
|
device,
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
frameIndex
|
|
13207
|
-
});
|
|
13208
|
-
if (submitted) {
|
|
13209
|
-
accelerationBuilt = true;
|
|
13210
|
-
accelerationBuildCount += 1;
|
|
13211
|
-
}
|
|
13212
|
-
return submitted;
|
|
13213
|
-
}
|
|
13214
|
-
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
13215
|
-
return dispatchWavefrontFrame({
|
|
13216
|
-
config,
|
|
13217
|
-
tiles,
|
|
13678
|
+
constants,
|
|
13679
|
+
config.environmentMap,
|
|
13680
|
+
config.environmentColor
|
|
13681
|
+
);
|
|
13682
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
13218
13683
|
device,
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
renderedSamplesPerPixel,
|
|
13222
|
-
frameEncoder,
|
|
13223
|
-
createFrameConfigWriter
|
|
13224
|
-
});
|
|
13225
|
-
}
|
|
13226
|
-
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
13227
|
-
const frameStartTimeMs = nowMs3();
|
|
13228
|
-
frame += 1;
|
|
13229
|
-
const frameIndex = frame + config.frameIndex;
|
|
13230
|
-
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
13231
|
-
const parallelismCounters = createGpuParallelismCounters();
|
|
13232
|
-
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13233
|
-
const frameSubmissionCount = dispatchFrame(
|
|
13234
|
-
frameIndex,
|
|
13235
|
-
parallelismCounters,
|
|
13236
|
-
samplingPlan.renderedSamplesPerPixel
|
|
13684
|
+
constants,
|
|
13685
|
+
config.mediums
|
|
13237
13686
|
);
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13247
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13248
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13249
|
-
}),
|
|
13250
|
-
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
13251
|
-
lastGpuParallelism,
|
|
13252
|
-
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
13253
|
-
frameTimeMs,
|
|
13254
|
-
false
|
|
13255
|
-
),
|
|
13256
|
-
timings: createWavefrontFrameTimingTelemetry({
|
|
13257
|
-
status: "not-requested",
|
|
13258
|
-
source: "cpu-submit",
|
|
13259
|
-
timestampQueryStatus: "not-recorded",
|
|
13260
|
-
totalRenderJobTimeMs: frameTimeMs
|
|
13687
|
+
config = Object.freeze({
|
|
13688
|
+
...config,
|
|
13689
|
+
environmentMap: Object.freeze({
|
|
13690
|
+
...config.environmentMap,
|
|
13691
|
+
width: environmentMapResource.width,
|
|
13692
|
+
height: environmentMapResource.height,
|
|
13693
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
13694
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
13261
13695
|
})
|
|
13262
13696
|
});
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
return dispatchWavefrontFrameAwaitingGpu({
|
|
13266
|
-
config,
|
|
13267
|
-
tiles,
|
|
13697
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
13698
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
13268
13699
|
device,
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
optionsForFrame
|
|
13275
|
-
});
|
|
13276
|
-
}
|
|
13277
|
-
async function readOutputProbe(optionsForProbe = {}) {
|
|
13278
|
-
return readWavefrontOutputProbe({
|
|
13700
|
+
constants,
|
|
13701
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
13702
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
13703
|
+
);
|
|
13704
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
13279
13705
|
device,
|
|
13280
13706
|
constants,
|
|
13281
|
-
config,
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13707
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
13708
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
13709
|
+
);
|
|
13710
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
13711
|
+
device,
|
|
13712
|
+
constants,
|
|
13713
|
+
config.gpuMaterialSource.normalAtlas,
|
|
13714
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
13715
|
+
);
|
|
13716
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
13717
|
+
device,
|
|
13718
|
+
constants,
|
|
13719
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
13720
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
13721
|
+
);
|
|
13722
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
13723
|
+
device,
|
|
13724
|
+
constants,
|
|
13725
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
13726
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
13727
|
+
);
|
|
13728
|
+
const extensionAtlasNames = [
|
|
13729
|
+
"clearcoat",
|
|
13730
|
+
"clearcoatRoughness",
|
|
13731
|
+
"clearcoatNormal",
|
|
13732
|
+
"transmission",
|
|
13733
|
+
"thickness",
|
|
13734
|
+
"sheenColor",
|
|
13735
|
+
"sheenRoughness",
|
|
13736
|
+
"specular",
|
|
13737
|
+
"specularColor",
|
|
13738
|
+
"iridescence",
|
|
13739
|
+
"iridescenceThickness",
|
|
13740
|
+
"anisotropy"
|
|
13741
|
+
];
|
|
13742
|
+
const extensionAtlasResources = Object.freeze(
|
|
13743
|
+
Object.fromEntries(
|
|
13744
|
+
extensionAtlasNames.map((name) => [
|
|
13745
|
+
name,
|
|
13746
|
+
createAtlasTextureResource(
|
|
13747
|
+
device,
|
|
13748
|
+
constants,
|
|
13749
|
+
config.gpuMaterialSource.extensionAtlases[name],
|
|
13750
|
+
`plasius.wavefront.materialAtlas.${name}`
|
|
13751
|
+
)
|
|
13752
|
+
])
|
|
13753
|
+
)
|
|
13754
|
+
);
|
|
13755
|
+
const materialAtlasSampler = device.createSampler({
|
|
13756
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
13757
|
+
addressModeU: "clamp-to-edge",
|
|
13758
|
+
addressModeV: "clamp-to-edge",
|
|
13759
|
+
magFilter: "linear",
|
|
13760
|
+
minFilter: "linear"
|
|
13285
13761
|
});
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
const
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13762
|
+
const {
|
|
13763
|
+
bindGroupLayouts: {
|
|
13764
|
+
trace: traceBindGroupLayout,
|
|
13765
|
+
acceleration: accelerationBindGroupLayout,
|
|
13766
|
+
denoiseRadiance: denoiseRadianceBindGroupLayout,
|
|
13767
|
+
denoiseResolve: denoiseResolveBindGroupLayout,
|
|
13768
|
+
present: presentBindGroupLayout
|
|
13769
|
+
},
|
|
13770
|
+
computePipelines: pipelines,
|
|
13771
|
+
presentPipeline
|
|
13772
|
+
} = await createWavefrontPipelineResources({ device, constants, format, instanced: !!config.instancing });
|
|
13773
|
+
let bindGroups = createTraceBindGroups();
|
|
13774
|
+
const bvhBuildBindGroup = createWavefrontBvhBuildBindGroup({
|
|
13775
|
+
device,
|
|
13776
|
+
accelerationBindGroupLayout,
|
|
13777
|
+
bvhBuildConfigBuffer,
|
|
13778
|
+
triangleBuffer,
|
|
13779
|
+
bvhNodeBuffer,
|
|
13780
|
+
meshVertexBuffer,
|
|
13781
|
+
meshIndexBuffer,
|
|
13782
|
+
meshRangeBuffer,
|
|
13783
|
+
bvhLeafRefBuffer
|
|
13784
|
+
});
|
|
13785
|
+
const {
|
|
13786
|
+
radiance: denoiseRadianceBindGroup,
|
|
13787
|
+
resolve: denoiseResolveBindGroup,
|
|
13788
|
+
directResolve: denoiseDirectResolveBindGroup
|
|
13789
|
+
} = createWavefrontDenoiseBindGroups({
|
|
13790
|
+
device,
|
|
13791
|
+
denoiseRadianceBindGroupLayout,
|
|
13792
|
+
denoiseResolveBindGroupLayout,
|
|
13793
|
+
configBuffer,
|
|
13794
|
+
radianceView,
|
|
13795
|
+
denoiseScratchView,
|
|
13796
|
+
outputView
|
|
13797
|
+
});
|
|
13798
|
+
const presentBindGroup = createWavefrontPresentBindGroup({
|
|
13799
|
+
device,
|
|
13800
|
+
presentBindGroupLayout,
|
|
13801
|
+
outputView,
|
|
13802
|
+
sampler
|
|
13803
|
+
});
|
|
13804
|
+
const progressive = await createWavefrontProgressiveResources({ device, constants, config, configBuffer, radianceTexture, radianceView, outputView });
|
|
13805
|
+
let activeFrameTelemetry = null;
|
|
13806
|
+
let frameTelemetryResources = null;
|
|
13807
|
+
const frameEncoder = createWavefrontFrameEncoder({
|
|
13808
|
+
getConfig: () => config,
|
|
13809
|
+
progressive,
|
|
13810
|
+
getBindGroups: () => bindGroups,
|
|
13811
|
+
pipelines,
|
|
13812
|
+
counterBuffer,
|
|
13813
|
+
activeDispatchBuffer,
|
|
13814
|
+
denoiseRadianceBindGroup,
|
|
13815
|
+
denoiseResolveBindGroup,
|
|
13816
|
+
denoiseDirectResolveBindGroup,
|
|
13817
|
+
presentPipeline,
|
|
13818
|
+
presentBindGroup,
|
|
13819
|
+
context,
|
|
13820
|
+
getFrameTelemetry: () => activeFrameTelemetry
|
|
13821
|
+
});
|
|
13822
|
+
let frame = 0;
|
|
13823
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
13824
|
+
let accelerationBuildCount = 0;
|
|
13825
|
+
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA2;
|
|
13826
|
+
let lastCompletedFrameTimeMs = null;
|
|
13827
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
13828
|
+
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
13829
|
+
gpuAdapterParallelism,
|
|
13830
|
+
createGpuParallelismCounters()
|
|
13831
|
+
);
|
|
13832
|
+
const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
|
|
13833
|
+
async function readTerminationMetrics() {
|
|
13834
|
+
return readWavefrontTerminationMetrics({
|
|
13835
|
+
device,
|
|
13836
|
+
constants,
|
|
13837
|
+
counterBuffer,
|
|
13838
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2
|
|
13839
|
+
});
|
|
13840
|
+
}
|
|
13841
|
+
async function readOutputProbe(optionsForProbe = {}) {
|
|
13842
|
+
return readWavefrontOutputProbe({
|
|
13843
|
+
device,
|
|
13844
|
+
constants,
|
|
13845
|
+
config,
|
|
13846
|
+
outputTexture,
|
|
13847
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2,
|
|
13848
|
+
optionsForProbe
|
|
13849
|
+
});
|
|
13850
|
+
}
|
|
13851
|
+
async function renderFrame(renderOptions = {}) {
|
|
13852
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
13853
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
13854
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
13855
|
+
const telemetryRequested = renderOptions.readStats === true;
|
|
13856
|
+
const telemetryCanRecord = telemetryRequested && awaitGPUCompletion;
|
|
13857
|
+
const telemetryResourcesForFrame = telemetryCanRecord ? ensureFrameTelemetryResources() : null;
|
|
13858
|
+
if (telemetryResourcesForFrame?.available) {
|
|
13859
|
+
telemetryResourcesForFrame.beginFrame();
|
|
13860
|
+
activeFrameTelemetry = telemetryResourcesForFrame;
|
|
13861
|
+
}
|
|
13862
|
+
const frameStartTimeMs = nowMs3();
|
|
13863
|
+
let frameStats;
|
|
13864
|
+
try {
|
|
13865
|
+
if (useThrottledHighSamplePath) {
|
|
13866
|
+
frame += 1;
|
|
13867
|
+
const frameIndex = frame + config.frameIndex;
|
|
13868
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
13869
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13870
|
+
let frameSubmissionCount = 0;
|
|
13871
|
+
let frameConfigSlot = 0;
|
|
13872
|
+
if (accelerationBuildSubmitted) {
|
|
13873
|
+
const accelerationWaitOptions = {
|
|
13874
|
+
...estimateSubmittedGpuWorkTiming(
|
|
13875
|
+
{ ...config, renderedSamplesPerPixel: 1 },
|
|
13876
|
+
1,
|
|
13877
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
13878
|
+
{ includeAccelerationBuild: true }
|
|
13879
|
+
),
|
|
13880
|
+
allowTimeout: false
|
|
13881
|
+
};
|
|
13882
|
+
await waitForSubmittedGpuWork2(accelerationWaitOptions);
|
|
13883
|
+
}
|
|
13884
|
+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
|
|
13885
|
+
const tileRangeDispatch = dispatchFrameAwaitingGpu(
|
|
13886
|
+
frameIndex,
|
|
13887
|
+
parallelismCounters,
|
|
13888
|
+
samplingPlan.renderedSamplesPerPixel,
|
|
13343
13889
|
{
|
|
13344
|
-
|
|
13890
|
+
sampleRangeStart: 0,
|
|
13891
|
+
sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
|
|
13892
|
+
tileStartIndex: tileIndex,
|
|
13893
|
+
tileEndIndex: tileIndex + 1,
|
|
13894
|
+
startingSubmissionCount: frameSubmissionCount,
|
|
13895
|
+
startingSlot: frameConfigSlot,
|
|
13896
|
+
includeDenoise: tileIndex + 1 >= tiles.length,
|
|
13345
13897
|
includePresent: tileIndex + 1 >= tiles.length
|
|
13346
13898
|
}
|
|
13347
|
-
)
|
|
13899
|
+
);
|
|
13900
|
+
frameSubmissionCount = tileRangeDispatch.submissionCount;
|
|
13901
|
+
frameConfigSlot = tileRangeDispatch.slot;
|
|
13902
|
+
const tileWaitOptions = {
|
|
13903
|
+
...estimateSubmittedGpuWorkTiming(
|
|
13904
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13905
|
+
1,
|
|
13906
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
13907
|
+
{
|
|
13908
|
+
includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
|
|
13909
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
13910
|
+
}
|
|
13911
|
+
),
|
|
13912
|
+
allowTimeout: false
|
|
13913
|
+
};
|
|
13914
|
+
await waitForSubmittedGpuWork2(tileWaitOptions);
|
|
13915
|
+
}
|
|
13916
|
+
frameStats = createFrameStats({
|
|
13917
|
+
frameIndex,
|
|
13918
|
+
accelerationBuildSubmitted,
|
|
13919
|
+
frameSubmissionCount,
|
|
13920
|
+
parallelismCounters,
|
|
13921
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13922
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13923
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13924
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
13925
|
+
});
|
|
13926
|
+
} else {
|
|
13927
|
+
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
13928
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13929
|
+
tiles.length,
|
|
13930
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
13931
|
+
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
13932
|
+
);
|
|
13933
|
+
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
13934
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13935
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
13348
13936
|
allowTimeout: false
|
|
13937
|
+
} : {
|
|
13938
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13939
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13349
13940
|
};
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
accelerationBuildSubmitted,
|
|
13355
|
-
frameSubmissionCount,
|
|
13356
|
-
parallelismCounters,
|
|
13357
|
-
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13358
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13359
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13360
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13361
|
-
});
|
|
13362
|
-
} else {
|
|
13363
|
-
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
13364
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13365
|
-
tiles.length,
|
|
13366
|
-
renderOptions.submittedWorkTimeoutMs,
|
|
13367
|
-
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
13368
|
-
);
|
|
13369
|
-
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
13370
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13371
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
13372
|
-
allowTimeout: false
|
|
13373
|
-
} : {
|
|
13374
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13375
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13376
|
-
};
|
|
13377
|
-
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
13378
|
-
if (awaitGPUCompletion) {
|
|
13379
|
-
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
13941
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
13942
|
+
if (awaitGPUCompletion) {
|
|
13943
|
+
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
13944
|
+
}
|
|
13380
13945
|
}
|
|
13946
|
+
} finally {
|
|
13947
|
+
activeFrameTelemetry = null;
|
|
13381
13948
|
}
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
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
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
)
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13949
|
+
const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
|
|
13950
|
+
if (awaitGPUCompletion) {
|
|
13951
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
13952
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
13953
|
+
}
|
|
13954
|
+
const expectedPrimaryRays = config.width * config.height * samplingPlan.renderedSamplesPerPixel;
|
|
13955
|
+
const expectedRayCounts = tiles.length * samplingPlan.renderedSamplesPerPixel * config.maxDepth;
|
|
13956
|
+
const telemetryReadback = telemetryCanRecord ? await telemetryResourcesForFrame.readFrame({
|
|
13957
|
+
expectedPrimaryRays,
|
|
13958
|
+
expectedRayCounts,
|
|
13959
|
+
waitForSubmittedGpuWork: waitForSubmittedGpuWork2
|
|
13960
|
+
}) : null;
|
|
13961
|
+
const rayCounts = !telemetryRequested ? createWavefrontRayCountTelemetry() : !awaitGPUCompletion ? createWavefrontRayCountTelemetry({
|
|
13962
|
+
status: "unavailable",
|
|
13963
|
+
expectedPrimaryRays,
|
|
13964
|
+
expectedRayCounts,
|
|
13965
|
+
reason: "gpu-work-not-awaited"
|
|
13966
|
+
}) : telemetryReadback.rayCounts;
|
|
13967
|
+
const timings = !telemetryRequested ? createWavefrontFrameTimingTelemetry({
|
|
13968
|
+
status: "not-requested",
|
|
13969
|
+
source: awaitGPUCompletion ? "queue-completion" : "cpu-submit",
|
|
13970
|
+
timestampQueryStatus: "not-recorded",
|
|
13971
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
13972
|
+
}) : !awaitGPUCompletion ? createWavefrontFrameTimingTelemetry({
|
|
13973
|
+
status: "unavailable",
|
|
13974
|
+
source: "cpu-submit",
|
|
13975
|
+
timestampQueryStatus: "not-recorded",
|
|
13976
|
+
totalRenderJobTimeMs: frameTimeMs,
|
|
13977
|
+
reason: "gpu-work-not-awaited"
|
|
13978
|
+
}) : telemetryReadback.timestampQueryStatus === "available" ? createWavefrontFrameTimingTelemetry({
|
|
13979
|
+
status: "available",
|
|
13980
|
+
source: "timestamp-query",
|
|
13981
|
+
timestampQueryStatus: "available",
|
|
13982
|
+
totalGpuTimeMs: telemetryReadback.totalGpuTimeMs,
|
|
13983
|
+
totalRenderJobTimeMs: frameTimeMs
|
|
13984
|
+
}) : createWavefrontFrameTimingTelemetry({
|
|
13985
|
+
status: "fallback",
|
|
13986
|
+
source: "queue-completion",
|
|
13987
|
+
timestampQueryStatus: telemetryReadback.timestampQueryStatus,
|
|
13988
|
+
totalRenderJobTimeMs: frameTimeMs,
|
|
13989
|
+
reason: telemetryReadback.reason
|
|
13990
|
+
});
|
|
13991
|
+
const deviceLossStatus = awaitGPUCompletion ? "not-detected" : typeof device.lost?.then === "function" ? "pending" : "not-exposed";
|
|
13992
|
+
frameStats = Object.freeze({
|
|
13993
|
+
...frameStats,
|
|
13994
|
+
deviceLossStatus,
|
|
13995
|
+
secondaryRays: rayCounts.status === "available" ? rayCounts.secondaryRays : null,
|
|
13996
|
+
totalPathSegments: rayCounts.status === "available" ? rayCounts.totalPathSegments : null,
|
|
13997
|
+
rayCounts,
|
|
13998
|
+
timings,
|
|
13999
|
+
telemetryMemoryBytes: telemetryResourcesForFrame?.memoryBytes ?? 0,
|
|
14000
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
14001
|
+
frameStats.gpuParallelism,
|
|
14002
|
+
frameStats.commandSubmissions,
|
|
14003
|
+
frameTimeMs,
|
|
14004
|
+
awaitGPUCompletion
|
|
14005
|
+
)
|
|
14006
|
+
});
|
|
14007
|
+
const terminationMetrics = awaitGPUCompletion && renderOptions.readStats === true ? await readTerminationMetrics() : EMPTY_TERMINATION_METRICS;
|
|
14008
|
+
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
14009
|
+
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
14010
|
+
const completedFrame = Object.freeze({
|
|
14011
|
+
...frameStats,
|
|
14012
|
+
outputProbe: probe ? Object.freeze({
|
|
14013
|
+
...probe,
|
|
14014
|
+
sampledPixels: 1,
|
|
14015
|
+
nonZeroSamples: maxChannel > 0 ? 1 : 0,
|
|
14016
|
+
maxChannel
|
|
14017
|
+
}) : null,
|
|
14018
|
+
bounces: [],
|
|
14019
|
+
termination: terminationMetrics.termination,
|
|
14020
|
+
terminalRadiance: terminationMetrics.terminalRadiance,
|
|
14021
|
+
radianceDiagnostics: terminationMetrics.radianceDiagnostics,
|
|
14022
|
+
transportContributions: terminationMetrics.transportContributions,
|
|
14023
|
+
queueOverflow: terminationMetrics.queueOverflow
|
|
14024
|
+
});
|
|
14025
|
+
return Object.freeze({
|
|
14026
|
+
...completedFrame,
|
|
14027
|
+
transportGuardrails: createWavefrontTransportGuardrailSummary(completedFrame)
|
|
14028
|
+
});
|
|
14029
|
+
}
|
|
14030
|
+
await finishAllocationScopes();
|
|
13461
14031
|
return Object.freeze({
|
|
13462
|
-
...completedFrame,
|
|
13463
|
-
transportGuardrails: createWavefrontTransportGuardrailSummary(completedFrame)
|
|
13464
|
-
});
|
|
13465
|
-
}
|
|
13466
|
-
function rebuildLiveConfig(overrides = {}) {
|
|
13467
|
-
return createWavefrontPathTracingComputeConfig({
|
|
13468
|
-
...options,
|
|
13469
14032
|
canvas,
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
featureFlags: options.featureFlags,
|
|
13483
|
-
frameIndex: config.frameIndex,
|
|
13484
|
-
...overrides
|
|
14033
|
+
context,
|
|
14034
|
+
device: rawDevice,
|
|
14035
|
+
format,
|
|
14036
|
+
config,
|
|
14037
|
+
renderOnce,
|
|
14038
|
+
renderFrame,
|
|
14039
|
+
readOutputProbe,
|
|
14040
|
+
updateSceneObjects,
|
|
14041
|
+
updateCamera,
|
|
14042
|
+
resetAccumulation: () => progressive?.reset(),
|
|
14043
|
+
getSnapshot,
|
|
14044
|
+
destroy
|
|
13485
14045
|
});
|
|
13486
|
-
}
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
bindGroups = createTraceBindGroups();
|
|
13491
|
-
if (previousMediumTextureResource?.ownsTexture) {
|
|
13492
|
-
previousMediumTextureResource.texture?.destroy?.();
|
|
13493
|
-
}
|
|
13494
|
-
}
|
|
13495
|
-
function updateSceneObjects(sceneObjects) {
|
|
13496
|
-
progressive?.reset();
|
|
13497
|
-
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
13498
|
-
packedScene = nextPackedScene;
|
|
13499
|
-
const nextConfig = rebuildLiveConfig();
|
|
13500
|
-
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
13501
|
-
rebuildMediumResources(nextConfig);
|
|
14046
|
+
} catch (error) {
|
|
14047
|
+
try {
|
|
14048
|
+
await finishAllocationScopes?.();
|
|
14049
|
+
} catch {
|
|
13502
14050
|
}
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
function updateCamera(cameraOptions = {}) {
|
|
13508
|
-
progressive?.reset();
|
|
13509
|
-
activeCameraOptions = cameraOptions;
|
|
13510
|
-
config = updateWavefrontCameraConfig(config, cameraOptions);
|
|
13511
|
-
return config;
|
|
13512
|
-
}
|
|
13513
|
-
function getSnapshot() {
|
|
13514
|
-
return Object.freeze({
|
|
13515
|
-
frame,
|
|
13516
|
-
accumulatedSamplesPerPixel: progressive?.samples ?? 0,
|
|
13517
|
-
width: config.width,
|
|
13518
|
-
height: config.height,
|
|
13519
|
-
maxDepth: config.maxDepth,
|
|
13520
|
-
tiles: tiles.length,
|
|
13521
|
-
tileSize: config.tileSize,
|
|
13522
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
13523
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
13524
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
13525
|
-
triangleCount: config.triangleCount,
|
|
13526
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
13527
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
13528
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
13529
|
-
mediumCount: config.mediumCount,
|
|
13530
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
13531
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
13532
|
-
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13533
|
-
transportExperiments: config.transportExperiments,
|
|
13534
|
-
transportExperimentFlags: config.transportExperimentFlags,
|
|
13535
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
13536
|
-
displayQuality: config.displayQuality,
|
|
13537
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
13538
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
13539
|
-
accelerationBuilt,
|
|
13540
|
-
accelerationBuildCount,
|
|
13541
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
13542
|
-
gpuParallelism: lastGpuParallelism,
|
|
13543
|
-
memory: config.memory
|
|
13544
|
-
});
|
|
13545
|
-
}
|
|
13546
|
-
function destroy() {
|
|
13547
|
-
progressive?.destroy();
|
|
13548
|
-
frameTelemetryResources?.destroy?.();
|
|
13549
|
-
activeQueue.destroy?.();
|
|
13550
|
-
nextQueue.destroy?.();
|
|
13551
|
-
hitBuffer.destroy?.();
|
|
13552
|
-
accumulationBuffer.destroy?.();
|
|
13553
|
-
pathVertexBuffer.destroy?.();
|
|
13554
|
-
sceneObjectBuffer.destroy?.();
|
|
13555
|
-
triangleBuffer.destroy?.();
|
|
13556
|
-
bvhNodeBuffer.destroy?.();
|
|
13557
|
-
meshVertexBuffer.destroy?.();
|
|
13558
|
-
meshIndexBuffer.destroy?.();
|
|
13559
|
-
meshRangeBuffer.destroy?.();
|
|
13560
|
-
environmentPortalBuffer.destroy?.();
|
|
13561
|
-
bvhLeafRefBuffer.destroy?.();
|
|
13562
|
-
configBuffer.destroy?.();
|
|
13563
|
-
bvhBuildConfigBuffer.destroy?.();
|
|
13564
|
-
counterBuffer.destroy?.();
|
|
13565
|
-
activeDispatchBuffer.destroy?.();
|
|
13566
|
-
radianceTexture.destroy?.();
|
|
13567
|
-
denoiseScratchTexture.destroy?.();
|
|
13568
|
-
outputTexture.destroy?.();
|
|
13569
|
-
if (environmentMapResource.ownsTexture) {
|
|
13570
|
-
environmentMapResource.texture?.destroy?.();
|
|
13571
|
-
}
|
|
13572
|
-
if (environmentSamplingResource.ownsTexture) {
|
|
13573
|
-
environmentSamplingResource.texture?.destroy?.();
|
|
13574
|
-
}
|
|
13575
|
-
if (mediumTextureResource.ownsTexture) {
|
|
13576
|
-
mediumTextureResource.texture?.destroy?.();
|
|
13577
|
-
}
|
|
13578
|
-
brdfLutResource.texture?.destroy?.();
|
|
13579
|
-
if (baseColorAtlasResource.ownsTexture) {
|
|
13580
|
-
baseColorAtlasResource.texture?.destroy?.();
|
|
13581
|
-
}
|
|
13582
|
-
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
13583
|
-
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
13584
|
-
}
|
|
13585
|
-
if (normalAtlasResource.ownsTexture) {
|
|
13586
|
-
normalAtlasResource.texture?.destroy?.();
|
|
13587
|
-
}
|
|
13588
|
-
if (occlusionAtlasResource.ownsTexture) {
|
|
13589
|
-
occlusionAtlasResource.texture?.destroy?.();
|
|
13590
|
-
}
|
|
13591
|
-
if (emissiveAtlasResource.ownsTexture) {
|
|
13592
|
-
emissiveAtlasResource.texture?.destroy?.();
|
|
13593
|
-
}
|
|
13594
|
-
Object.values(extensionAtlasResources).forEach((resource) => {
|
|
13595
|
-
if (resource.ownsTexture) {
|
|
13596
|
-
resource.texture?.destroy?.();
|
|
13597
|
-
}
|
|
13598
|
-
});
|
|
13599
|
-
context.unconfigure?.();
|
|
14051
|
+
context?.unconfigure?.();
|
|
14052
|
+
allocationTracker.destroy();
|
|
14053
|
+
rawDevice.destroy?.();
|
|
14054
|
+
throw error;
|
|
13600
14055
|
}
|
|
13601
|
-
return Object.freeze({
|
|
13602
|
-
canvas,
|
|
13603
|
-
context,
|
|
13604
|
-
device,
|
|
13605
|
-
format,
|
|
13606
|
-
config,
|
|
13607
|
-
renderOnce,
|
|
13608
|
-
renderFrame,
|
|
13609
|
-
readOutputProbe,
|
|
13610
|
-
updateSceneObjects,
|
|
13611
|
-
updateCamera,
|
|
13612
|
-
resetAccumulation: () => progressive?.reset(),
|
|
13613
|
-
getSnapshot,
|
|
13614
|
-
destroy
|
|
13615
|
-
});
|
|
13616
14056
|
}
|
|
13617
14057
|
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
13618
14058
|
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|