@plasius/gpu-renderer 0.2.56 → 0.2.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +48 -0
- package/dist/index.cjs +2256 -1811
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2256 -1811
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/gpu-allocation-budget.js +92 -0
- package/src/index.d.ts +30 -0
- package/src/wavefront-bind-groups.js +2 -0
- package/src/wavefront-compute.js +41 -10
- package/src/wavefront-config.js +16 -2
- package/src/wavefront-instance-shader.js +86 -0
- package/src/wavefront-instances.js +107 -0
- package/src/wavefront-mesh-sources.js +2 -2
- package/src/wavefront-pipelines.js +6 -5
- package/src/wavefront-runtime-support.js +8 -6
- package/src/wavefront-shader-bvh.js +4 -2
- package/src/wavefront-shader-kernels.js +1 -1
- package/src/wavefront-shader-layout.js +1 -0
- package/src/wavefront-shaders.js +5 -1
- package/src/wavefront-upload.js +12 -0
package/dist/index.js
CHANGED
|
@@ -3966,948 +3966,1289 @@ function boundsCentroid(bounds) {
|
|
|
3966
3966
|
];
|
|
3967
3967
|
}
|
|
3968
3968
|
|
|
3969
|
-
// src/wavefront-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
const
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
const
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
),
|
|
3983
|
-
normal
|
|
3984
|
-
);
|
|
3985
|
-
}
|
|
3986
|
-
function radicalInverseVdc(bits) {
|
|
3987
|
-
let value = bits >>> 0;
|
|
3988
|
-
value = (value << 16 | value >>> 16) >>> 0;
|
|
3989
|
-
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
3990
|
-
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
3991
|
-
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
3992
|
-
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
3993
|
-
return value * 23283064365386963e-26;
|
|
3994
|
-
}
|
|
3995
|
-
function hammersley(index, count) {
|
|
3996
|
-
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
3997
|
-
}
|
|
3998
|
-
function importanceSampleGgx(sample, roughness, normal) {
|
|
3999
|
-
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
4000
|
-
const phi = 2 * Math.PI * sample[0];
|
|
4001
|
-
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
4002
|
-
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
4003
|
-
const halfVector = localToWorld(
|
|
4004
|
-
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
4005
|
-
normal
|
|
4006
|
-
);
|
|
4007
|
-
return normalize(halfVector, normal);
|
|
4008
|
-
}
|
|
4009
|
-
function geometrySchlickGgx(nDotV, roughness) {
|
|
4010
|
-
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
4011
|
-
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
4012
|
-
}
|
|
4013
|
-
function geometrySmith(nDotV, nDotL, roughness) {
|
|
4014
|
-
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;
|
|
4015
3982
|
}
|
|
4016
|
-
function
|
|
4017
|
-
const
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
4027
|
-
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.`
|
|
4028
3993
|
);
|
|
4029
|
-
const nDotL = Math.max(lightDirection[2], 0);
|
|
4030
|
-
const nDotH = Math.max(halfVector[2], 0);
|
|
4031
|
-
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
4032
|
-
continue;
|
|
4033
|
-
}
|
|
4034
|
-
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
4035
|
-
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
4036
|
-
const fresnel2 = (1 - vDotH) ** 5;
|
|
4037
|
-
scaleTerm += (1 - fresnel2) * visibility;
|
|
4038
|
-
biasTerm += fresnel2 * visibility;
|
|
4039
3994
|
}
|
|
4040
|
-
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4041
3995
|
}
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
4046
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
4047
|
-
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.");
|
|
4048
3999
|
}
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
const maxPixelsByPathVertex = Math.floor(
|
|
4052
|
-
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
4053
|
-
);
|
|
4054
|
-
const maxPixels = Math.max(
|
|
4055
|
-
256,
|
|
4056
|
-
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
4057
|
-
);
|
|
4058
|
-
if (config.tilePixelCapacity <= maxPixels) {
|
|
4059
|
-
return config.tileSize;
|
|
4000
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
4001
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
4060
4002
|
}
|
|
4061
|
-
return
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
usage
|
|
4068
|
-
});
|
|
4069
|
-
}
|
|
4070
|
-
function alignTo(value, alignment) {
|
|
4071
|
-
const resolvedAlignment = Math.max(1, alignment);
|
|
4072
|
-
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
|
+
};
|
|
4073
4009
|
}
|
|
4074
|
-
function
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
let exponent = x >> 23 & 255;
|
|
4082
|
-
if (exponent === 255) {
|
|
4083
|
-
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;
|
|
4084
4017
|
}
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
return sign | 31744;
|
|
4018
|
+
if (canvasOrSelector?.getContext) {
|
|
4019
|
+
return canvasOrSelector;
|
|
4088
4020
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
}
|
|
4093
|
-
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4094
|
-
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.");
|
|
4095
4024
|
}
|
|
4096
|
-
return
|
|
4025
|
+
return fallback;
|
|
4097
4026
|
}
|
|
4098
|
-
function
|
|
4099
|
-
if (
|
|
4100
|
-
return
|
|
4027
|
+
async function getPipelineDiagnostics(shaderModule) {
|
|
4028
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4029
|
+
return "";
|
|
4101
4030
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
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 "";
|
|
4104
4044
|
}
|
|
4105
|
-
return 1;
|
|
4106
4045
|
}
|
|
4107
|
-
function
|
|
4108
|
-
|
|
4109
|
-
|
|
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
|
+
});
|
|
4110
4067
|
}
|
|
4111
|
-
const width = Math.max(1, environmentMap.width ?? 1);
|
|
4112
|
-
const height = Math.max(1, environmentMap.height ?? 1);
|
|
4113
|
-
return environmentMap.data.length >= width * height * 4;
|
|
4114
4068
|
}
|
|
4115
|
-
function
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
const bytesPerRow = alignTo(width * 4, 256);
|
|
4119
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4120
|
-
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
4121
|
-
for (let y = 0; y < height; y += 1) {
|
|
4122
|
-
const sourceOffset = y * width * 4;
|
|
4123
|
-
const targetOffset = y * bytesPerRow;
|
|
4124
|
-
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
4069
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
4070
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
4071
|
+
return;
|
|
4125
4072
|
}
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
}
|
|
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;
|
|
4078
|
+
}
|
|
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}`);
|
|
4132
4086
|
}
|
|
4133
|
-
function
|
|
4134
|
-
if (
|
|
4135
|
-
return
|
|
4087
|
+
async function createRenderPipeline(device, descriptor) {
|
|
4088
|
+
if (typeof device.createRenderPipelineAsync === "function") {
|
|
4089
|
+
return device.createRenderPipelineAsync(descriptor);
|
|
4136
4090
|
}
|
|
4137
|
-
|
|
4138
|
-
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
4091
|
+
return device.createRenderPipeline(descriptor);
|
|
4139
4092
|
}
|
|
4140
|
-
function
|
|
4141
|
-
const
|
|
4142
|
-
const
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
const width = Math.max(1, Math.trunc(size));
|
|
4147
|
-
const height = Math.max(1, Math.trunc(size));
|
|
4148
|
-
const rowBytes = width * 8;
|
|
4149
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4150
|
-
const bytes = new Uint8Array(bytesPerRow * height);
|
|
4151
|
-
const view = new DataView(bytes.buffer);
|
|
4152
|
-
for (let y = 0; y < height; y += 1) {
|
|
4153
|
-
const roughness = (y + 0.5) / height;
|
|
4154
|
-
for (let x = 0; x < width; x += 1) {
|
|
4155
|
-
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
4156
|
-
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
4157
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4158
|
-
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
4159
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
4160
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
4161
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
4164
|
-
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4165
|
-
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4166
|
-
return upload;
|
|
4167
|
-
}
|
|
4168
|
-
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
4169
|
-
const width = Math.max(1, environmentMap.width);
|
|
4170
|
-
const height = Math.max(1, environmentMap.height);
|
|
4171
|
-
const pixels = new Float32Array(width * height * 4);
|
|
4172
|
-
const data = environmentMap.data;
|
|
4173
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
4174
|
-
for (let index = 0; index < width * height; index += 1) {
|
|
4175
|
-
const sourceOffset = index * 4;
|
|
4176
|
-
const targetOffset = index * 4;
|
|
4177
|
-
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
4178
|
-
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
4179
|
-
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
4180
|
-
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
4181
|
-
}
|
|
4182
|
-
return pixels;
|
|
4183
|
-
}
|
|
4184
|
-
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
4185
|
-
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
4186
|
-
const theta = v * Math.PI;
|
|
4187
|
-
const sinTheta = Math.sin(theta);
|
|
4188
|
-
return [
|
|
4189
|
-
Math.cos(angle) * sinTheta,
|
|
4190
|
-
Math.cos(theta),
|
|
4191
|
-
Math.sin(angle) * sinTheta
|
|
4192
|
-
];
|
|
4193
|
-
}
|
|
4194
|
-
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
4195
|
-
const wrappedU = (u % 1 + 1) % 1;
|
|
4196
|
-
const clampedV = clamp2(v, 0, 1);
|
|
4197
|
-
const x = wrappedU * width - 0.5;
|
|
4198
|
-
const y = clampedV * height - 0.5;
|
|
4199
|
-
const x0 = (Math.floor(x) % width + width) % width;
|
|
4200
|
-
const y0 = clamp2(Math.floor(y), 0, height - 1);
|
|
4201
|
-
const x1 = (x0 + 1) % width;
|
|
4202
|
-
const y1 = clamp2(y0 + 1, 0, height - 1);
|
|
4203
|
-
const tx = x - Math.floor(x);
|
|
4204
|
-
const ty = y - Math.floor(y);
|
|
4205
|
-
const read = (px, py) => {
|
|
4206
|
-
const offset = (py * width + px) * 4;
|
|
4207
|
-
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 ?? {}
|
|
4208
4099
|
};
|
|
4209
|
-
const
|
|
4210
|
-
const
|
|
4211
|
-
const
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
4218
|
-
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
4219
|
-
];
|
|
4220
|
-
}
|
|
4221
|
-
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
4222
|
-
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
4223
|
-
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
4224
|
-
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
4225
|
-
const v = Math.acos(clamp2(unitDirection[1], -1, 1)) / Math.PI;
|
|
4226
|
-
return [u, clamp2(v, 0, 1)];
|
|
4227
|
-
}
|
|
4228
|
-
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
4229
|
-
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
4230
|
-
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
4231
|
-
}
|
|
4232
|
-
function createFloat16RgbaUploadFromLevels(levels) {
|
|
4233
|
-
return levels.map((level) => {
|
|
4234
|
-
const rowBytes = level.width * 8;
|
|
4235
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
4236
|
-
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
4237
|
-
const view = new DataView(bytes.buffer);
|
|
4238
|
-
for (let y = 0; y < level.height; y += 1) {
|
|
4239
|
-
for (let x = 0; x < level.width; x += 1) {
|
|
4240
|
-
const sourceOffset = (y * level.width + x) * 4;
|
|
4241
|
-
const targetOffset = y * bytesPerRow + x * 8;
|
|
4242
|
-
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
4243
|
-
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
4244
|
-
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
4245
|
-
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
4246
|
-
}
|
|
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
|
+
);
|
|
4247
4108
|
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
const
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
height: sourceHeight,
|
|
4260
|
-
data: sourcePixels
|
|
4261
|
-
})
|
|
4262
|
-
];
|
|
4263
|
-
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
4264
|
-
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
4265
|
-
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
4266
|
-
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
4267
|
-
const data = new Float32Array(width * height * 4);
|
|
4268
|
-
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
4269
|
-
for (let y = 0; y < height; y += 1) {
|
|
4270
|
-
for (let x = 0; x < width; x += 1) {
|
|
4271
|
-
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
4272
|
-
const normal = normalize(direction, [0, 1, 0]);
|
|
4273
|
-
const viewDirection = normal;
|
|
4274
|
-
let totalWeight = 0;
|
|
4275
|
-
const accum = [0, 0, 0];
|
|
4276
|
-
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
4277
|
-
const xi = hammersley(sampleIndex, sampleCount);
|
|
4278
|
-
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
4279
|
-
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
4280
|
-
const lightDirection = normalize(
|
|
4281
|
-
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
4282
|
-
normal
|
|
4283
|
-
);
|
|
4284
|
-
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
4285
|
-
if (nDotL <= 1e-6) {
|
|
4286
|
-
continue;
|
|
4287
|
-
}
|
|
4288
|
-
const radiance = sampleEnvironmentRadiance(
|
|
4289
|
-
sourcePixels,
|
|
4290
|
-
sourceWidth,
|
|
4291
|
-
sourceHeight,
|
|
4292
|
-
lightDirection,
|
|
4293
|
-
environmentMap.rotationRadians
|
|
4294
|
-
);
|
|
4295
|
-
accum[0] += radiance[0] * nDotL;
|
|
4296
|
-
accum[1] += radiance[1] * nDotL;
|
|
4297
|
-
accum[2] += radiance[2] * nDotL;
|
|
4298
|
-
totalWeight += nDotL;
|
|
4299
|
-
}
|
|
4300
|
-
const offset = (y * width + x) * 4;
|
|
4301
|
-
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
4302
|
-
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
4303
|
-
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
4304
|
-
data[offset + 3] = 1;
|
|
4305
|
-
}
|
|
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
|
+
);
|
|
4306
4120
|
}
|
|
4307
|
-
|
|
4121
|
+
requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
|
|
4122
|
+
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4123
|
+
traceTextureBindings
|
|
4124
|
+
);
|
|
4308
4125
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
if (
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
hasImportanceData: false
|
|
4325
|
-
});
|
|
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;
|
|
4326
4141
|
}
|
|
4327
|
-
const
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
const theta = (y + 0.5) / height * Math.PI;
|
|
4337
|
-
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
4338
|
-
let rowWeight = 0;
|
|
4339
|
-
for (let x = 0; x < width; x += 1) {
|
|
4340
|
-
const offset = (y * width + x) * 4;
|
|
4341
|
-
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
4342
|
-
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
4343
|
-
pdf[y * width + x] = weight;
|
|
4344
|
-
rowWeight += weight;
|
|
4345
|
-
conditionalCdf[y * width + x] = rowWeight;
|
|
4346
|
-
}
|
|
4347
|
-
rowSums[y] = rowWeight;
|
|
4348
|
-
totalWeight += rowWeight;
|
|
4349
|
-
if (rowWeight > 0) {
|
|
4350
|
-
for (let x = 0; x < width; x += 1) {
|
|
4351
|
-
conditionalCdf[y * width + x] /= rowWeight;
|
|
4352
|
-
}
|
|
4353
|
-
} else {
|
|
4354
|
-
for (let x = 0; x < width; x += 1) {
|
|
4355
|
-
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
4356
|
-
}
|
|
4357
|
-
}
|
|
4358
|
-
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;
|
|
4359
4151
|
}
|
|
4360
|
-
|
|
4361
|
-
|
|
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");
|
|
4362
4157
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4158
|
+
if (requiredFeatures.length > 0) {
|
|
4159
|
+
descriptor.requiredFeatures = requiredFeatures;
|
|
4365
4160
|
}
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
conditionalCdf,
|
|
4372
|
-
hasImportanceData: true
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
4376
|
-
if (environmentMap.view) {
|
|
4377
|
-
return Object.freeze({
|
|
4378
|
-
view: environmentMap.view,
|
|
4379
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4380
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4381
|
-
addressModeU: "repeat",
|
|
4382
|
-
addressModeV: "clamp-to-edge",
|
|
4383
|
-
magFilter: "linear",
|
|
4384
|
-
minFilter: "linear",
|
|
4385
|
-
mipmapFilter: "linear"
|
|
4386
|
-
}),
|
|
4387
|
-
texture: null,
|
|
4388
|
-
ownsTexture: false,
|
|
4389
|
-
width: Math.max(1, environmentMap.width),
|
|
4390
|
-
height: Math.max(1, environmentMap.height),
|
|
4391
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4392
|
-
});
|
|
4161
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
4162
|
+
descriptor.requiredLimits = {
|
|
4163
|
+
...descriptor.requiredLimits ?? {},
|
|
4164
|
+
...requiredLimits
|
|
4165
|
+
};
|
|
4393
4166
|
}
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
addressModeV: "clamp-to-edge",
|
|
4401
|
-
magFilter: "linear",
|
|
4402
|
-
minFilter: "linear",
|
|
4403
|
-
mipmapFilter: "linear"
|
|
4404
|
-
}),
|
|
4405
|
-
texture: environmentMap.texture,
|
|
4406
|
-
ownsTexture: false,
|
|
4407
|
-
width: Math.max(1, environmentMap.width),
|
|
4408
|
-
height: Math.max(1, environmentMap.height),
|
|
4409
|
-
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
4410
|
-
});
|
|
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;
|
|
4411
4173
|
}
|
|
4412
|
-
const
|
|
4413
|
-
|
|
4414
|
-
const texture = device.createTexture({
|
|
4415
|
-
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
4416
|
-
size: { width: prefiltered.width, height: prefiltered.height },
|
|
4417
|
-
format: "rgba16float",
|
|
4418
|
-
mipLevelCount: prefiltered.mipLevelCount,
|
|
4419
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4420
|
-
});
|
|
4421
|
-
uploads.forEach((upload, mipLevel) => {
|
|
4422
|
-
device.queue.writeTexture(
|
|
4423
|
-
{ texture, mipLevel },
|
|
4424
|
-
upload.bytes,
|
|
4425
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4426
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4427
|
-
);
|
|
4428
|
-
});
|
|
4429
|
-
return Object.freeze({
|
|
4430
|
-
view: texture.createView(),
|
|
4431
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
4432
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
4433
|
-
addressModeU: "repeat",
|
|
4434
|
-
addressModeV: "clamp-to-edge",
|
|
4435
|
-
magFilter: "linear",
|
|
4436
|
-
minFilter: "linear",
|
|
4437
|
-
mipmapFilter: "linear"
|
|
4438
|
-
}),
|
|
4439
|
-
texture,
|
|
4440
|
-
ownsTexture: true,
|
|
4441
|
-
width: prefiltered.width,
|
|
4442
|
-
height: prefiltered.height,
|
|
4443
|
-
mipLevelCount: prefiltered.mipLevelCount
|
|
4444
|
-
});
|
|
4174
|
+
const deviceValue = Number(device?.limits?.[name]);
|
|
4175
|
+
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
4445
4176
|
}
|
|
4446
|
-
function
|
|
4447
|
-
const
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
4451
|
-
const view = new DataView(bytes.buffer);
|
|
4452
|
-
for (let y = 0; y < tables.height; y += 1) {
|
|
4453
|
-
for (let x = 0; x < tables.width; x += 1) {
|
|
4454
|
-
const probability = tables.pdf[y * tables.width + x];
|
|
4455
|
-
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
4456
|
-
const marginal = tables.marginalCdf[y];
|
|
4457
|
-
const offset = y * bytesPerRow + x * 8;
|
|
4458
|
-
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
4459
|
-
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
4460
|
-
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
4461
|
-
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
4462
|
-
}
|
|
4177
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
4178
|
+
const info = adapter?.info;
|
|
4179
|
+
if (!info || typeof info !== "object") {
|
|
4180
|
+
return null;
|
|
4463
4181
|
}
|
|
4464
|
-
const texture = device.createTexture({
|
|
4465
|
-
label: "plasius.wavefront.environmentSampling",
|
|
4466
|
-
size: { width: tables.width, height: tables.height },
|
|
4467
|
-
format: "rgba16float",
|
|
4468
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4469
|
-
});
|
|
4470
|
-
device.queue.writeTexture(
|
|
4471
|
-
{ texture },
|
|
4472
|
-
bytes,
|
|
4473
|
-
{ bytesPerRow, rowsPerImage: tables.height },
|
|
4474
|
-
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
4475
|
-
);
|
|
4476
4182
|
return Object.freeze({
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
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 : ""
|
|
4481
4187
|
});
|
|
4482
4188
|
}
|
|
4483
|
-
function
|
|
4484
|
-
const upload = createBrdfLutUploadBytes(size);
|
|
4485
|
-
const texture = device.createTexture({
|
|
4486
|
-
label: "plasius.wavefront.brdfLut",
|
|
4487
|
-
size: { width: upload.width, height: upload.height },
|
|
4488
|
-
format: "rgba16float",
|
|
4489
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4490
|
-
});
|
|
4491
|
-
device.queue.writeTexture(
|
|
4492
|
-
{ texture },
|
|
4493
|
-
upload.bytes,
|
|
4494
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4495
|
-
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
4496
|
-
);
|
|
4189
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
4497
4190
|
return Object.freeze({
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
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")
|
|
4505
4204
|
}),
|
|
4506
|
-
|
|
4507
|
-
ownsTexture: true,
|
|
4508
|
-
width: upload.width,
|
|
4509
|
-
height: upload.height
|
|
4205
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
4510
4206
|
});
|
|
4511
4207
|
}
|
|
4512
|
-
function
|
|
4513
|
-
|
|
4514
|
-
|
|
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(
|
|
4515
4245
|
1,
|
|
4516
|
-
|
|
4246
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
4517
4247
|
);
|
|
4518
|
-
const
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
label: "plasius.wavefront.mediumTable",
|
|
4539
|
-
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
4540
|
-
format: "rgba16float",
|
|
4541
|
-
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
4542
|
-
});
|
|
4543
|
-
device.queue.writeTexture(
|
|
4544
|
-
{ texture },
|
|
4545
|
-
upload.bytes,
|
|
4546
|
-
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
4547
|
-
{ 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)
|
|
4548
4268
|
);
|
|
4549
4269
|
return Object.freeze({
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
ownsTexture: true,
|
|
4553
|
-
count: normalized.length,
|
|
4554
|
-
width
|
|
4270
|
+
timeoutMs,
|
|
4271
|
+
maxWaitMs
|
|
4555
4272
|
});
|
|
4556
4273
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
if (
|
|
4561
|
-
return
|
|
4274
|
+
|
|
4275
|
+
// src/wavefront-gpu-synchronization.js
|
|
4276
|
+
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
4277
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
4278
|
+
return true;
|
|
4562
4279
|
}
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
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;
|
|
4571
4293
|
}
|
|
4572
|
-
|
|
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
|
+
);
|
|
4573
4311
|
return false;
|
|
4574
4312
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
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;
|
|
4578
4320
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
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));
|
|
4581
4511
|
}
|
|
4582
4512
|
}
|
|
4583
|
-
|
|
4584
|
-
|
|
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
|
+
};
|
|
4585
4525
|
}
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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
|
|
4599
4542
|
);
|
|
4600
|
-
return Object.freeze({
|
|
4601
|
-
texture,
|
|
4602
|
-
view: texture.createView(),
|
|
4603
|
-
ownsTexture: true
|
|
4604
|
-
});
|
|
4605
4543
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
}
|
|
4615
|
-
const value = Number(rawValue);
|
|
4616
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4617
|
-
throw new Error(`${name} must be a non-negative safe integer.`);
|
|
4618
|
-
}
|
|
4619
|
-
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;
|
|
4620
4552
|
}
|
|
4621
|
-
function
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
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
|
|
4631
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;
|
|
4632
4597
|
}
|
|
4598
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
4633
4599
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
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;
|
|
4637
4606
|
}
|
|
4638
|
-
|
|
4639
|
-
|
|
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;
|
|
4640
4618
|
}
|
|
4641
|
-
return
|
|
4642
|
-
buffer: GPUBufferUsage,
|
|
4643
|
-
texture: GPUTextureUsage,
|
|
4644
|
-
shader: GPUShaderStage,
|
|
4645
|
-
map: typeof GPUMapMode === "undefined" ? null : GPUMapMode
|
|
4646
|
-
};
|
|
4619
|
+
return Math.max(16, Math.floor(Math.sqrt(maxPixels)));
|
|
4647
4620
|
}
|
|
4648
|
-
function
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
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);
|
|
4655
4642
|
}
|
|
4656
|
-
|
|
4657
|
-
|
|
4643
|
+
exponent = exponent - 127 + 15;
|
|
4644
|
+
if (exponent >= 31) {
|
|
4645
|
+
return sign | 31744;
|
|
4658
4646
|
}
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4647
|
+
if (exponent <= 0) {
|
|
4648
|
+
if (exponent < -10) {
|
|
4649
|
+
return sign;
|
|
4650
|
+
}
|
|
4651
|
+
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
4652
|
+
return sign | mantissa + 4096 >> 13;
|
|
4662
4653
|
}
|
|
4663
|
-
return
|
|
4654
|
+
return sign | exponent << 10 | mantissa + 4096 >> 13;
|
|
4664
4655
|
}
|
|
4665
|
-
|
|
4666
|
-
if (
|
|
4667
|
-
return
|
|
4656
|
+
function environmentMapIntegerScale(data) {
|
|
4657
|
+
if (data instanceof Uint8Array) {
|
|
4658
|
+
return 1 / 255;
|
|
4668
4659
|
}
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
const messages = info.messages ?? [];
|
|
4672
|
-
if (messages.length === 0) {
|
|
4673
|
-
return "";
|
|
4674
|
-
}
|
|
4675
|
-
return messages.map((message) => {
|
|
4676
|
-
const line = message.lineNum ?? "?";
|
|
4677
|
-
const column = message.linePos ?? "?";
|
|
4678
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4679
|
-
}).join("\n");
|
|
4680
|
-
} catch {
|
|
4681
|
-
return "";
|
|
4660
|
+
if (data instanceof Uint16Array) {
|
|
4661
|
+
return 1 / 65535;
|
|
4682
4662
|
}
|
|
4663
|
+
return 1;
|
|
4683
4664
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
const
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
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;
|
|
4705
4694
|
}
|
|
4695
|
+
const value = Number(data[index]);
|
|
4696
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
4706
4697
|
}
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
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;
|
|
4710
4703
|
}
|
|
4711
|
-
const
|
|
4712
|
-
const
|
|
4713
|
-
const
|
|
4714
|
-
|
|
4715
|
-
|
|
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
|
+
}
|
|
4716
4721
|
}
|
|
4717
|
-
const
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
return `line ${line}:${column} ${message.message}`;
|
|
4721
|
-
}).join("\n");
|
|
4722
|
-
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
4723
|
-
${diagnostics}`);
|
|
4722
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
4723
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
4724
|
+
return upload;
|
|
4724
4725
|
}
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
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);
|
|
4728
4739
|
}
|
|
4729
|
-
return
|
|
4740
|
+
return pixels;
|
|
4730
4741
|
}
|
|
4731
|
-
function
|
|
4732
|
-
const
|
|
4733
|
-
const
|
|
4734
|
-
const
|
|
4735
|
-
|
|
4736
|
-
|
|
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]];
|
|
4737
4766
|
};
|
|
4738
|
-
const
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
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
|
+
}
|
|
4744
4805
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
const
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
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
|
+
}
|
|
4756
4864
|
}
|
|
4757
|
-
|
|
4758
|
-
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
4759
|
-
TRACE_SAMPLED_TEXTURE_BINDINGS
|
|
4760
|
-
);
|
|
4761
|
-
}
|
|
4762
|
-
const sceneStorageBufferBindingBytes = Number(
|
|
4763
|
-
memory?.sceneStorageBufferBindingBytes ?? 0
|
|
4764
|
-
);
|
|
4765
|
-
const requestedStorageBufferBindingSize = Math.max(
|
|
4766
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
|
|
4767
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
|
|
4768
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
|
|
4769
|
-
);
|
|
4770
|
-
if (requestedStorageBufferBindingSize > 0) {
|
|
4771
|
-
assertAdapterSupportsRequiredLimit(
|
|
4772
|
-
adapter,
|
|
4773
|
-
"maxStorageBufferBindingSize",
|
|
4774
|
-
requestedStorageBufferBindingSize
|
|
4775
|
-
);
|
|
4776
|
-
requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
|
|
4777
|
-
}
|
|
4778
|
-
const requestedMaxBufferSize = Math.max(
|
|
4779
|
-
readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
|
|
4780
|
-
readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
|
|
4781
|
-
sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
|
|
4782
|
-
requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
|
|
4783
|
-
);
|
|
4784
|
-
if (requestedMaxBufferSize > 0) {
|
|
4785
|
-
assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
|
|
4786
|
-
requiredLimits.maxBufferSize = requestedMaxBufferSize;
|
|
4865
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
4787
4866
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
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
|
+
});
|
|
4793
4884
|
}
|
|
4794
|
-
|
|
4795
|
-
|
|
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;
|
|
4796
4917
|
}
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
...descriptor.requiredLimits ?? {},
|
|
4800
|
-
...requiredLimits
|
|
4801
|
-
};
|
|
4918
|
+
for (let y = 0; y < height; y += 1) {
|
|
4919
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
4802
4920
|
}
|
|
4803
|
-
|
|
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
|
+
});
|
|
4804
4932
|
}
|
|
4805
|
-
function
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
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
|
+
});
|
|
4809
4951
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
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
|
+
});
|
|
4812
5003
|
}
|
|
4813
|
-
function
|
|
4814
|
-
const
|
|
4815
|
-
|
|
4816
|
-
|
|
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
|
+
}
|
|
4817
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
|
+
);
|
|
4818
5034
|
return Object.freeze({
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
5035
|
+
view: texture.createView(),
|
|
5036
|
+
texture,
|
|
5037
|
+
ownsTexture: true,
|
|
5038
|
+
hasImportanceData: tables.hasImportanceData
|
|
4823
5039
|
});
|
|
4824
5040
|
}
|
|
4825
|
-
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
|
+
);
|
|
4826
5055
|
return Object.freeze({
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
4835
|
-
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
4836
|
-
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
4837
|
-
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
4838
|
-
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
|
|
4839
|
-
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"
|
|
4840
5063
|
}),
|
|
4841
|
-
|
|
5064
|
+
texture,
|
|
5065
|
+
ownsTexture: true,
|
|
5066
|
+
width: upload.width,
|
|
5067
|
+
height: upload.height
|
|
4842
5068
|
});
|
|
4843
5069
|
}
|
|
4844
|
-
function
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
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
|
|
4855
5113
|
});
|
|
4856
5114
|
}
|
|
4857
|
-
function
|
|
4858
|
-
|
|
4859
|
-
|
|
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;
|
|
4860
5120
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
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
|
+
}
|
|
4866
5141
|
}
|
|
4867
|
-
|
|
4868
|
-
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
4869
|
-
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
4870
|
-
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
5142
|
+
return true;
|
|
4871
5143
|
}
|
|
4872
|
-
function
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
4886
|
-
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
4887
|
-
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
4888
|
-
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
4889
|
-
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
4890
|
-
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
4891
|
-
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
4892
|
-
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
4893
|
-
const estimatedWindowMs = Math.round(
|
|
4894
|
-
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
4895
|
-
);
|
|
4896
|
-
const timeoutMs = Math.min(
|
|
4897
|
-
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4898
|
-
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
4899
|
-
);
|
|
4900
|
-
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
4901
|
-
const maxWaitMs = Math.min(
|
|
4902
|
-
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
4903
|
-
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 }
|
|
4904
5157
|
);
|
|
4905
5158
|
return Object.freeze({
|
|
4906
|
-
|
|
4907
|
-
|
|
5159
|
+
texture,
|
|
5160
|
+
view: texture.createView(),
|
|
5161
|
+
ownsTexture: true
|
|
4908
5162
|
});
|
|
4909
5163
|
}
|
|
4910
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
|
+
|
|
4911
5252
|
// src/wavefront-shader-bvh.js
|
|
4912
5253
|
var WAVEFRONT_SHADER_BVH_WGSL = `
|
|
4913
5254
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -5370,6 +5711,7 @@ fn no_candidate() -> Candidate {
|
|
|
5370
5711
|
1u,
|
|
5371
5712
|
0xffffffffu,
|
|
5372
5713
|
0xffffffffu,
|
|
5714
|
+
0xffffffffu,
|
|
5373
5715
|
0u,
|
|
5374
5716
|
0u
|
|
5375
5717
|
);
|
|
@@ -5396,6 +5738,7 @@ fn surface_candidate(
|
|
|
5396
5738
|
uv,
|
|
5397
5739
|
frontFace,
|
|
5398
5740
|
triangleIndex,
|
|
5741
|
+
0xffffffffu,
|
|
5399
5742
|
primitiveId,
|
|
5400
5743
|
materialRefId,
|
|
5401
5744
|
mediumRefId
|
|
@@ -5457,7 +5800,7 @@ fn intersect_triangle(ray: RayRecord, triangle: TriangleRecord, triangleIndex: u
|
|
|
5457
5800
|
);
|
|
5458
5801
|
}
|
|
5459
5802
|
|
|
5460
|
-
fn
|
|
5803
|
+
fn intersect_geometry_bvh(ray: RayRecord, initialNearest: f32, root: u32) -> Candidate {
|
|
5461
5804
|
var nearest = initialNearest;
|
|
5462
5805
|
var best = no_candidate();
|
|
5463
5806
|
if (config.bvhNodeCount == 0u || config.triangleCount == 0u) {
|
|
@@ -5466,7 +5809,7 @@ fn intersect_bvh(ray: RayRecord, initialNearest: f32) -> Candidate {
|
|
|
5466
5809
|
|
|
5467
5810
|
var stack = array<u32, 64>();
|
|
5468
5811
|
var stackSize = 1u;
|
|
5469
|
-
stack[0] =
|
|
5812
|
+
stack[0] = root;
|
|
5470
5813
|
|
|
5471
5814
|
loop {
|
|
5472
5815
|
if (stackSize == 0u) {
|
|
@@ -5757,7 +6100,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5757
6100
|
if (meshCandidate.hit == 1u && meshCandidate.distance < nearest) {
|
|
5758
6101
|
nearest = meshCandidate.distance;
|
|
5759
6102
|
candidate = meshCandidate;
|
|
5760
|
-
hitTriangle =
|
|
6103
|
+
hitTriangle = triangle_for_candidate(meshCandidate);
|
|
5761
6104
|
}
|
|
5762
6105
|
|
|
5763
6106
|
if (candidate.hit == 0u) {
|
|
@@ -6911,6 +7254,7 @@ struct Candidate {
|
|
|
6911
7254
|
uv: vec2<f32>,
|
|
6912
7255
|
frontFace: u32,
|
|
6913
7256
|
triangleIndex: u32,
|
|
7257
|
+
instanceIndex: u32,
|
|
6914
7258
|
primitiveId: u32,
|
|
6915
7259
|
materialRefId: u32,
|
|
6916
7260
|
mediumRefId: u32,
|
|
@@ -8508,6 +8852,7 @@ var WAVEFRONT_COMPUTE_WGSL = [
|
|
|
8508
8852
|
WAVEFRONT_SHADER_MATERIALS_WGSL,
|
|
8509
8853
|
WAVEFRONT_SHADER_LIGHTING_WGSL,
|
|
8510
8854
|
WAVEFRONT_SHADER_BVH_WGSL,
|
|
8855
|
+
WAVEFRONT_UNINSTANCED_SHADER_WGSL,
|
|
8511
8856
|
WAVEFRONT_SHADER_KERNELS_WGSL
|
|
8512
8857
|
].join("\n");
|
|
8513
8858
|
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
@@ -8519,7 +8864,7 @@ function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
|
8519
8864
|
if (workgroupSize !== WAVEFRONT_COMPUTE_WORKGROUP_SIZE) {
|
|
8520
8865
|
throw new Error(`wavefront mesh compute currently requires workgroupSize=${WAVEFRONT_COMPUTE_WORKGROUP_SIZE}`);
|
|
8521
8866
|
}
|
|
8522
|
-
return WAVEFRONT_COMPUTE_WGSL;
|
|
8867
|
+
return options.instanced === true ? WAVEFRONT_COMPUTE_WGSL.replace(WAVEFRONT_UNINSTANCED_SHADER_WGSL, WAVEFRONT_INSTANCE_SHADER_WGSL) : WAVEFRONT_COMPUTE_WGSL;
|
|
8523
8868
|
}
|
|
8524
8869
|
|
|
8525
8870
|
// src/wavefront-progressive-shader.js
|
|
@@ -9523,7 +9868,7 @@ function chooseSplitAxis(triangles) {
|
|
|
9523
9868
|
}
|
|
9524
9869
|
return extent[1] >= extent[2] ? 1 : 2;
|
|
9525
9870
|
}
|
|
9526
|
-
function
|
|
9871
|
+
function buildWavefrontBoundsBvh(triangles, maxLeafTriangles = 4) {
|
|
9527
9872
|
if (triangles.length === 0) {
|
|
9528
9873
|
return Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
9529
9874
|
}
|
|
@@ -9575,7 +9920,7 @@ function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null)
|
|
|
9575
9920
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
9576
9921
|
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
9577
9922
|
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
9578
|
-
return
|
|
9923
|
+
return buildWavefrontBoundsBvh(triangles);
|
|
9579
9924
|
}
|
|
9580
9925
|
function estimateMeshSourceShape(meshes) {
|
|
9581
9926
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
@@ -10393,6 +10738,145 @@ function dispatchWavefrontGpuAccelerationBuild({
|
|
|
10393
10738
|
return true;
|
|
10394
10739
|
}
|
|
10395
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
|
+
|
|
10396
10880
|
// src/wavefront-config.js
|
|
10397
10881
|
var WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 256;
|
|
10398
10882
|
function allocatedRecordBytes(count, recordBytes) {
|
|
@@ -10666,6 +11150,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10666
11150
|
const progressiveBytes = options.progressive === true ? width * height * 16 + 16 : 0;
|
|
10667
11151
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
10668
11152
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
11153
|
+
const instanceBytes = readNonNegativeInteger2("instanceBytes", options.instanceBytes, 0);
|
|
10669
11154
|
const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
|
|
10670
11155
|
const materialTableBytes = 0;
|
|
10671
11156
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -10721,6 +11206,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
10721
11206
|
pathVertexBytes,
|
|
10722
11207
|
sceneObjectBytes,
|
|
10723
11208
|
triangleBytes,
|
|
11209
|
+
instanceBytes,
|
|
10724
11210
|
materialTableBytes,
|
|
10725
11211
|
bvhNodeBytes,
|
|
10726
11212
|
bvhCombinedBytes,
|
|
@@ -10750,6 +11236,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10750
11236
|
maxDepth: config.maxDepth,
|
|
10751
11237
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
10752
11238
|
triangleCapacity: config.triangleCapacity,
|
|
11239
|
+
instanceBytes: config.instancing?.instanceBytes ?? 0,
|
|
10753
11240
|
bvhNodeCapacity: config.bvhNodeCapacity,
|
|
10754
11241
|
bvhLeafSortCapacity: config.bvhLeafSortCapacity,
|
|
10755
11242
|
emissiveTriangleCapacity: config.emissiveTriangleCapacity,
|
|
@@ -10768,6 +11255,7 @@ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
|
|
|
10768
11255
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
10769
11256
|
assertAnalyticDisplayQualityPolicy(options);
|
|
10770
11257
|
const accelerationBuildMode = resolveAccelerationBuildMode(options);
|
|
11258
|
+
if (options.instances !== void 0 && accelerationBuildMode === "gpu") throw new Error("Instance BLAS construction currently requires cpu-upload acceleration.");
|
|
10771
11259
|
const canvas = options.canvas;
|
|
10772
11260
|
const width = readPositiveInteger2("width", options.width, getCanvasDimension(canvas, "width", DEFAULT_WIDTH));
|
|
10773
11261
|
const height = readPositiveInteger2("height", options.height, getCanvasDimension(canvas, "height", DEFAULT_HEIGHT));
|
|
@@ -10798,9 +11286,16 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10798
11286
|
);
|
|
10799
11287
|
const meshes = normalizeMeshes(options);
|
|
10800
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);
|
|
10801
11295
|
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
10802
11296
|
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
10803
|
-
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([]) }));
|
|
10804
11299
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
10805
11300
|
meshes,
|
|
10806
11301
|
options.emissiveTriangleCapacity
|
|
@@ -10878,6 +11373,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10878
11373
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
10879
11374
|
gpuMeshSource,
|
|
10880
11375
|
gpuMaterialSource,
|
|
11376
|
+
instancing,
|
|
10881
11377
|
meshAcceleration,
|
|
10882
11378
|
emissiveTriangleIndices,
|
|
10883
11379
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -10916,6 +11412,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
10916
11412
|
tilePixelCapacity,
|
|
10917
11413
|
maxDepth,
|
|
10918
11414
|
sceneObjectCapacity,
|
|
11415
|
+
instanceBytes: instancing?.instanceBytes ?? 0,
|
|
10919
11416
|
triangleCapacity,
|
|
10920
11417
|
bvhNodeCapacity,
|
|
10921
11418
|
bvhLeafSortCapacity,
|
|
@@ -10949,6 +11446,7 @@ function createWavefrontTraceBindGroup({
|
|
|
10949
11446
|
counterBuffer,
|
|
10950
11447
|
outputView,
|
|
10951
11448
|
triangleBuffer,
|
|
11449
|
+
instanceView,
|
|
10952
11450
|
bvhNodeBuffer,
|
|
10953
11451
|
radianceView,
|
|
10954
11452
|
environmentPortalBuffer,
|
|
@@ -11006,7 +11504,8 @@ function createWavefrontTraceBindGroup({
|
|
|
11006
11504
|
{ binding: 41, resource: extensionAtlasResources.specularColor.view },
|
|
11007
11505
|
{ binding: 42, resource: extensionAtlasResources.iridescence.view },
|
|
11008
11506
|
{ binding: 43, resource: extensionAtlasResources.iridescenceThickness.view },
|
|
11009
|
-
{ binding: 44, resource: extensionAtlasResources.anisotropy.view }
|
|
11507
|
+
{ binding: 44, resource: extensionAtlasResources.anisotropy.view },
|
|
11508
|
+
...instanceView ? [{ binding: 47, resource: instanceView }] : []
|
|
11010
11509
|
]
|
|
11011
11510
|
});
|
|
11012
11511
|
}
|
|
@@ -11104,115 +11603,39 @@ function createWavefrontDenoiseBindGroups({
|
|
|
11104
11603
|
inputView: radianceView,
|
|
11105
11604
|
targetView: denoiseScratchView,
|
|
11106
11605
|
label: "plasius.wavefront.bind.denoise.radianceToScratch"
|
|
11107
|
-
}),
|
|
11108
|
-
resolve: createWavefrontDenoiseResolveBindGroup({
|
|
11109
|
-
device,
|
|
11110
|
-
denoiseResolveBindGroupLayout,
|
|
11111
|
-
configBuffer,
|
|
11112
|
-
inputView: denoiseScratchView,
|
|
11113
|
-
targetView: outputView,
|
|
11114
|
-
label: "plasius.wavefront.bind.denoise.scratchToOutput"
|
|
11115
|
-
}),
|
|
11116
|
-
directResolve: createWavefrontDenoiseResolveBindGroup({
|
|
11117
|
-
device,
|
|
11118
|
-
denoiseResolveBindGroupLayout,
|
|
11119
|
-
configBuffer,
|
|
11120
|
-
inputView: radianceView,
|
|
11121
|
-
targetView: outputView,
|
|
11122
|
-
label: "plasius.wavefront.bind.denoise.radianceToOutput"
|
|
11123
|
-
})
|
|
11124
|
-
});
|
|
11125
|
-
}
|
|
11126
|
-
function createWavefrontPresentBindGroup({
|
|
11127
|
-
device,
|
|
11128
|
-
presentBindGroupLayout,
|
|
11129
|
-
outputView,
|
|
11130
|
-
sampler
|
|
11131
|
-
}) {
|
|
11132
|
-
return device.createBindGroup({
|
|
11133
|
-
label: "plasius.wavefront.presentBindGroup",
|
|
11134
|
-
layout: presentBindGroupLayout,
|
|
11135
|
-
entries: [
|
|
11136
|
-
{ binding: 0, resource: outputView },
|
|
11137
|
-
{ binding: 1, resource: sampler }
|
|
11138
|
-
]
|
|
11139
|
-
});
|
|
11140
|
-
}
|
|
11141
|
-
|
|
11142
|
-
// src/wavefront-gpu-synchronization.js
|
|
11143
|
-
async function waitForSubmittedGpuWork(device, options = {}) {
|
|
11144
|
-
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
11145
|
-
return true;
|
|
11146
|
-
}
|
|
11147
|
-
const timeoutMs = Math.max(
|
|
11148
|
-
1,
|
|
11149
|
-
Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
11150
|
-
);
|
|
11151
|
-
const maxWaitMs = Math.max(
|
|
11152
|
-
timeoutMs,
|
|
11153
|
-
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
11154
|
-
);
|
|
11155
|
-
const allowTimeout = options.allowTimeout !== false;
|
|
11156
|
-
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
11157
|
-
() => ({ status: "done" }),
|
|
11158
|
-
(error) => {
|
|
11159
|
-
throw error;
|
|
11160
|
-
}
|
|
11161
|
-
);
|
|
11162
|
-
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
11163
|
-
throw new Error(
|
|
11164
|
-
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
11165
|
-
);
|
|
11166
|
-
}) : null;
|
|
11167
|
-
const startedAtMs = nowMs3();
|
|
11168
|
-
while (true) {
|
|
11169
|
-
const elapsedMs = Math.max(0, nowMs3() - startedAtMs);
|
|
11170
|
-
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
11171
|
-
if (remainingMs <= 0) {
|
|
11172
|
-
if (!allowTimeout) {
|
|
11173
|
-
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
11174
|
-
}
|
|
11175
|
-
console.warn(
|
|
11176
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
11177
|
-
);
|
|
11178
|
-
return false;
|
|
11179
|
-
}
|
|
11180
|
-
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
11181
|
-
let timeoutHandle = null;
|
|
11182
|
-
let resolveTimeoutPromise = null;
|
|
11183
|
-
let timeoutSettled = false;
|
|
11184
|
-
const settleTimeoutPromise = (value) => {
|
|
11185
|
-
if (timeoutSettled) {
|
|
11186
|
-
return;
|
|
11187
|
-
}
|
|
11188
|
-
timeoutSettled = true;
|
|
11189
|
-
resolveTimeoutPromise?.(value);
|
|
11190
|
-
};
|
|
11191
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
11192
|
-
resolveTimeoutPromise = resolve;
|
|
11193
|
-
timeoutHandle = setTimeout(
|
|
11194
|
-
() => settleTimeoutPromise({ status: "timeout" }),
|
|
11195
|
-
waitWindowMs
|
|
11196
|
-
);
|
|
11197
|
-
});
|
|
11198
|
-
let result;
|
|
11199
|
-
try {
|
|
11200
|
-
result = await Promise.race(
|
|
11201
|
-
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
11202
|
-
);
|
|
11203
|
-
} finally {
|
|
11204
|
-
if (timeoutHandle !== null) {
|
|
11205
|
-
clearTimeout(timeoutHandle);
|
|
11206
|
-
settleTimeoutPromise({ status: "cancelled" });
|
|
11207
|
-
}
|
|
11208
|
-
}
|
|
11209
|
-
if (result?.status === "done") {
|
|
11210
|
-
return true;
|
|
11211
|
-
}
|
|
11212
|
-
if (result?.status !== "timeout") {
|
|
11213
|
-
return true;
|
|
11214
|
-
}
|
|
11215
|
-
}
|
|
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
|
+
});
|
|
11216
11639
|
}
|
|
11217
11640
|
|
|
11218
11641
|
// src/wavefront-frame-telemetry.js
|
|
@@ -12039,7 +12462,7 @@ function dispatchWavefrontFrameAwaitingGpu({
|
|
|
12039
12462
|
}
|
|
12040
12463
|
|
|
12041
12464
|
// src/wavefront-pipelines.js
|
|
12042
|
-
function createWavefrontBindGroupLayouts(device, constants) {
|
|
12465
|
+
function createWavefrontBindGroupLayouts(device, constants, instanced = false) {
|
|
12043
12466
|
const trace = device.createBindGroupLayout({
|
|
12044
12467
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
12045
12468
|
entries: [
|
|
@@ -12091,7 +12514,8 @@ function createWavefrontBindGroupLayouts(device, constants) {
|
|
|
12091
12514
|
{ binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12092
12515
|
{ binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12093
12516
|
{ binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
12094
|
-
{ 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" } }] : []
|
|
12095
12519
|
]
|
|
12096
12520
|
});
|
|
12097
12521
|
const acceleration = device.createBindGroupLayout({
|
|
@@ -12286,12 +12710,12 @@ async function createWavefrontPresentPipeline(device, presentBindGroupLayout, fo
|
|
|
12286
12710
|
primitive: { topology: "triangle-list" }
|
|
12287
12711
|
});
|
|
12288
12712
|
}
|
|
12289
|
-
async function createWavefrontPipelineResources({ device, constants, format }) {
|
|
12290
|
-
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants);
|
|
12713
|
+
async function createWavefrontPipelineResources({ device, constants, format, instanced = false }) {
|
|
12714
|
+
const bindGroupLayouts = createWavefrontBindGroupLayouts(device, constants, instanced);
|
|
12291
12715
|
const pipelineLayouts = createWavefrontPipelineLayouts(device, bindGroupLayouts);
|
|
12292
12716
|
const computeShader = device.createShaderModule({
|
|
12293
12717
|
label: "plasius.wavefront.computeShader",
|
|
12294
|
-
code:
|
|
12718
|
+
code: createWavefrontPathTracingComputeShaderSource({ instanced })
|
|
12295
12719
|
});
|
|
12296
12720
|
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
12297
12721
|
return Object.freeze({
|
|
@@ -12724,890 +13148,911 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
12724
13148
|
if (!adapter) {
|
|
12725
13149
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
12726
13150
|
}
|
|
12727
|
-
const
|
|
13151
|
+
const rawDevice = await adapter.requestDevice(
|
|
12728
13152
|
createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
|
|
12729
13153
|
);
|
|
12730
|
-
const
|
|
12731
|
-
const
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
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
|
-
const accumulationBuffer = createBuffer(
|
|
12768
|
-
device,
|
|
12769
|
-
bufferUsage,
|
|
12770
|
-
memory.accumulationBytes,
|
|
12771
|
-
"plasius.wavefront.accumulation"
|
|
12772
|
-
);
|
|
12773
|
-
const pathVertexBuffer = createBuffer(
|
|
12774
|
-
device,
|
|
12775
|
-
bufferUsage,
|
|
12776
|
-
memory.pathVertexBytes,
|
|
12777
|
-
"plasius.wavefront.pathVertices"
|
|
12778
|
-
);
|
|
12779
|
-
const sceneObjectBuffer = createBuffer(
|
|
12780
|
-
device,
|
|
12781
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12782
|
-
memory.sceneObjectBytes,
|
|
12783
|
-
"plasius.wavefront.sceneObjects"
|
|
12784
|
-
);
|
|
12785
|
-
const triangleBuffer = createBuffer(
|
|
12786
|
-
device,
|
|
12787
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12788
|
-
memory.triangleBytes,
|
|
12789
|
-
"plasius.wavefront.triangles"
|
|
12790
|
-
);
|
|
12791
|
-
const bvhNodeBuffer = createBuffer(
|
|
12792
|
-
device,
|
|
12793
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12794
|
-
memory.bvhCombinedBytes,
|
|
12795
|
-
"plasius.wavefront.bvhNodes"
|
|
12796
|
-
);
|
|
12797
|
-
const meshVertexBuffer = createBuffer(
|
|
12798
|
-
device,
|
|
12799
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12800
|
-
memory.meshVertexBytes,
|
|
12801
|
-
"plasius.wavefront.meshVertices"
|
|
12802
|
-
);
|
|
12803
|
-
const meshIndexBuffer = createBuffer(
|
|
12804
|
-
device,
|
|
12805
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12806
|
-
memory.meshIndexBytes,
|
|
12807
|
-
"plasius.wavefront.meshIndices"
|
|
12808
|
-
);
|
|
12809
|
-
const meshRangeBuffer = createBuffer(
|
|
12810
|
-
device,
|
|
12811
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12812
|
-
memory.meshRangeBytes,
|
|
12813
|
-
"plasius.wavefront.meshRanges"
|
|
12814
|
-
);
|
|
12815
|
-
const environmentPortalBuffer = createBuffer(
|
|
12816
|
-
device,
|
|
12817
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12818
|
-
memory.environmentPortalBytes,
|
|
12819
|
-
"plasius.wavefront.environmentPortals"
|
|
12820
|
-
);
|
|
12821
|
-
const bvhLeafRefBuffer = createBuffer(
|
|
12822
|
-
device,
|
|
12823
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
12824
|
-
memory.bvhLeafReferenceBytes,
|
|
12825
|
-
"plasius.wavefront.bvhLeafRefs"
|
|
12826
|
-
);
|
|
12827
|
-
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
12828
|
-
const configBufferStride = memory.configBufferStride;
|
|
12829
|
-
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
12830
|
-
const frameConfigSlotCount = Math.max(
|
|
12831
|
-
1,
|
|
12832
|
-
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise || config.progressive ? 1 : 0)
|
|
12833
|
-
);
|
|
12834
|
-
const configBuffer = createBuffer(
|
|
12835
|
-
device,
|
|
12836
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12837
|
-
memory.frameConfigBytes,
|
|
12838
|
-
"plasius.wavefront.frameConfig"
|
|
12839
|
-
);
|
|
12840
|
-
const bvhBuildConfigBuffer = createBuffer(
|
|
12841
|
-
device,
|
|
12842
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
12843
|
-
memory.bvhBuildConfigBytes,
|
|
12844
|
-
"plasius.wavefront.bvhBuildConfig"
|
|
12845
|
-
);
|
|
12846
|
-
const counterBuffer = createBuffer(
|
|
12847
|
-
device,
|
|
12848
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
12849
|
-
memory.counterBytes,
|
|
12850
|
-
"plasius.wavefront.counters"
|
|
12851
|
-
);
|
|
12852
|
-
const activeDispatchBuffer = createBuffer(
|
|
12853
|
-
device,
|
|
12854
|
-
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
12855
|
-
memory.indirectDispatchBytes,
|
|
12856
|
-
"plasius.wavefront.activeDispatchArgs"
|
|
12857
|
-
);
|
|
12858
|
-
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
12859
|
-
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
12860
|
-
const packedTriangles = packWavefrontTriangles(
|
|
12861
|
-
config.meshAcceleration.triangles,
|
|
12862
|
-
Math.max(1, config.triangleCapacity)
|
|
12863
|
-
);
|
|
12864
|
-
const packedBvhNodes = packWavefrontBvhNodes(
|
|
12865
|
-
config.meshAcceleration.nodes,
|
|
12866
|
-
Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity)
|
|
12867
|
-
);
|
|
12868
|
-
const packedBvhNodeUints = new Uint32Array(packedBvhNodes.buffer);
|
|
12869
|
-
const packedBvhNodeFloats = new Float32Array(packedBvhNodes.buffer);
|
|
12870
|
-
const orderedEmissiveTriangleIndices = resolveOrderedEmissiveTriangleIndices(config);
|
|
12871
|
-
const emissiveWeights = orderedEmissiveTriangleIndices.map(
|
|
12872
|
-
(triangleIndex) => emissiveTriangleWeight(config.meshAcceleration.triangles[triangleIndex])
|
|
12873
|
-
);
|
|
12874
|
-
const totalEmissiveWeight = emissiveWeights.reduce((total, weight) => total + weight, 0);
|
|
12875
|
-
let cumulativeEmissiveWeight = 0;
|
|
12876
|
-
orderedEmissiveTriangleIndices.forEach((triangleIndex, index) => {
|
|
12877
|
-
const nodeOffset = (config.bvhNodeCapacity + index) * (BVH_NODE_RECORD_BYTES / 4);
|
|
12878
|
-
cumulativeEmissiveWeight += emissiveWeights[index] ?? 0;
|
|
12879
|
-
packedBvhNodeFloats[nodeOffset] = cumulativeEmissiveWeight;
|
|
12880
|
-
packedBvhNodeFloats[nodeOffset + 1] = emissiveWeights[index] ?? 0;
|
|
12881
|
-
packedBvhNodeFloats[nodeOffset + 2] = totalEmissiveWeight;
|
|
12882
|
-
packedBvhNodeUints[nodeOffset + 8] = triangleIndex;
|
|
12883
|
-
});
|
|
12884
|
-
device.queue.writeBuffer(triangleBuffer, 0, packedTriangles.buffer);
|
|
12885
|
-
device.queue.writeBuffer(bvhNodeBuffer, 0, packedBvhNodes.buffer);
|
|
12886
|
-
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
12887
|
-
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
12888
|
-
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
12889
|
-
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
12890
|
-
config.environmentPortals,
|
|
12891
|
-
Math.max(1, config.environmentPortalCapacity)
|
|
12892
|
-
);
|
|
12893
|
-
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
12894
|
-
const radianceTexture = device.createTexture({
|
|
12895
|
-
label: "plasius.wavefront.radiance",
|
|
12896
|
-
size: { width: config.width, height: config.height },
|
|
12897
|
-
format: "rgba16float",
|
|
12898
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
12899
|
-
});
|
|
12900
|
-
const radianceView = radianceTexture.createView();
|
|
12901
|
-
const denoiseScratchTexture = device.createTexture({
|
|
12902
|
-
label: "plasius.wavefront.denoiseScratch",
|
|
12903
|
-
size: { width: config.width, height: config.height },
|
|
12904
|
-
format: "rgba16float",
|
|
12905
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING
|
|
12906
|
-
});
|
|
12907
|
-
const denoiseScratchView = denoiseScratchTexture.createView();
|
|
12908
|
-
const outputTexture = device.createTexture({
|
|
12909
|
-
label: "plasius.wavefront.output",
|
|
12910
|
-
size: { width: config.width, height: config.height },
|
|
12911
|
-
format: "rgba8unorm",
|
|
12912
|
-
usage: constants.texture.STORAGE_BINDING | constants.texture.TEXTURE_BINDING | constants.texture.COPY_SRC
|
|
12913
|
-
});
|
|
12914
|
-
const outputView = outputTexture.createView();
|
|
12915
|
-
const sampler = device.createSampler({
|
|
12916
|
-
label: "plasius.wavefront.presentSampler",
|
|
12917
|
-
magFilter: "nearest",
|
|
12918
|
-
minFilter: "nearest"
|
|
12919
|
-
});
|
|
12920
|
-
const environmentMapResource = createEnvironmentMapResource(
|
|
12921
|
-
device,
|
|
12922
|
-
constants,
|
|
12923
|
-
config.environmentMap,
|
|
12924
|
-
config.environmentColor
|
|
12925
|
-
);
|
|
12926
|
-
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
12927
|
-
device,
|
|
12928
|
-
constants,
|
|
12929
|
-
config.environmentMap,
|
|
12930
|
-
config.environmentColor
|
|
12931
|
-
);
|
|
12932
|
-
let mediumTextureResource = createMediumTextureResource(
|
|
12933
|
-
device,
|
|
12934
|
-
constants,
|
|
12935
|
-
config.mediums
|
|
12936
|
-
);
|
|
12937
|
-
config = Object.freeze({
|
|
12938
|
-
...config,
|
|
12939
|
-
environmentMap: Object.freeze({
|
|
12940
|
-
...config.environmentMap,
|
|
12941
|
-
width: environmentMapResource.width,
|
|
12942
|
-
height: environmentMapResource.height,
|
|
12943
|
-
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
12944
|
-
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
12945
|
-
})
|
|
12946
|
-
});
|
|
12947
|
-
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
12948
|
-
const baseColorAtlasResource = createAtlasTextureResource(
|
|
12949
|
-
device,
|
|
12950
|
-
constants,
|
|
12951
|
-
config.gpuMaterialSource.baseColorAtlas,
|
|
12952
|
-
"plasius.wavefront.materialAtlas.baseColor"
|
|
12953
|
-
);
|
|
12954
|
-
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
12955
|
-
device,
|
|
12956
|
-
constants,
|
|
12957
|
-
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
12958
|
-
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
12959
|
-
);
|
|
12960
|
-
const normalAtlasResource = createAtlasTextureResource(
|
|
12961
|
-
device,
|
|
12962
|
-
constants,
|
|
12963
|
-
config.gpuMaterialSource.normalAtlas,
|
|
12964
|
-
"plasius.wavefront.materialAtlas.normal"
|
|
12965
|
-
);
|
|
12966
|
-
const occlusionAtlasResource = createAtlasTextureResource(
|
|
12967
|
-
device,
|
|
12968
|
-
constants,
|
|
12969
|
-
config.gpuMaterialSource.occlusionAtlas,
|
|
12970
|
-
"plasius.wavefront.materialAtlas.occlusion"
|
|
12971
|
-
);
|
|
12972
|
-
const emissiveAtlasResource = createAtlasTextureResource(
|
|
12973
|
-
device,
|
|
12974
|
-
constants,
|
|
12975
|
-
config.gpuMaterialSource.emissiveAtlas,
|
|
12976
|
-
"plasius.wavefront.materialAtlas.emissive"
|
|
12977
|
-
);
|
|
12978
|
-
const extensionAtlasNames = [
|
|
12979
|
-
"clearcoat",
|
|
12980
|
-
"clearcoatRoughness",
|
|
12981
|
-
"clearcoatNormal",
|
|
12982
|
-
"transmission",
|
|
12983
|
-
"thickness",
|
|
12984
|
-
"sheenColor",
|
|
12985
|
-
"sheenRoughness",
|
|
12986
|
-
"specular",
|
|
12987
|
-
"specularColor",
|
|
12988
|
-
"iridescence",
|
|
12989
|
-
"iridescenceThickness",
|
|
12990
|
-
"anisotropy"
|
|
12991
|
-
];
|
|
12992
|
-
const extensionAtlasResources = Object.freeze(
|
|
12993
|
-
Object.fromEntries(
|
|
12994
|
-
extensionAtlasNames.map((name) => [
|
|
12995
|
-
name,
|
|
12996
|
-
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({
|
|
12997
13191
|
device,
|
|
12998
13192
|
constants,
|
|
12999
|
-
config.
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
)
|
|
13004
|
-
|
|
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
|
-
bvhLeafRefBuffer
|
|
13064
|
-
});
|
|
13065
|
-
const {
|
|
13066
|
-
radiance: denoiseRadianceBindGroup,
|
|
13067
|
-
resolve: denoiseResolveBindGroup,
|
|
13068
|
-
directResolve: denoiseDirectResolveBindGroup
|
|
13069
|
-
} = createWavefrontDenoiseBindGroups({
|
|
13070
|
-
device,
|
|
13071
|
-
denoiseRadianceBindGroupLayout,
|
|
13072
|
-
denoiseResolveBindGroupLayout,
|
|
13073
|
-
configBuffer,
|
|
13074
|
-
radianceView,
|
|
13075
|
-
denoiseScratchView,
|
|
13076
|
-
outputView
|
|
13077
|
-
});
|
|
13078
|
-
const presentBindGroup = createWavefrontPresentBindGroup({
|
|
13079
|
-
device,
|
|
13080
|
-
presentBindGroupLayout,
|
|
13081
|
-
outputView,
|
|
13082
|
-
sampler
|
|
13083
|
-
});
|
|
13084
|
-
const progressive = await createWavefrontProgressiveResources({ device, constants, config, configBuffer, radianceTexture, radianceView, outputView });
|
|
13085
|
-
let activeFrameTelemetry = null;
|
|
13086
|
-
let frameTelemetryResources = null;
|
|
13087
|
-
const frameEncoder = createWavefrontFrameEncoder({
|
|
13088
|
-
getConfig: () => config,
|
|
13089
|
-
progressive,
|
|
13090
|
-
getBindGroups: () => bindGroups,
|
|
13091
|
-
pipelines,
|
|
13092
|
-
counterBuffer,
|
|
13093
|
-
activeDispatchBuffer,
|
|
13094
|
-
denoiseRadianceBindGroup,
|
|
13095
|
-
denoiseResolveBindGroup,
|
|
13096
|
-
denoiseDirectResolveBindGroup,
|
|
13097
|
-
presentPipeline,
|
|
13098
|
-
presentBindGroup,
|
|
13099
|
-
context,
|
|
13100
|
-
getFrameTelemetry: () => activeFrameTelemetry
|
|
13101
|
-
});
|
|
13102
|
-
let frame = 0;
|
|
13103
|
-
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
13104
|
-
let accelerationBuildCount = 0;
|
|
13105
|
-
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA2;
|
|
13106
|
-
let lastCompletedFrameTimeMs = null;
|
|
13107
|
-
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
13108
|
-
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
13109
|
-
gpuAdapterParallelism,
|
|
13110
|
-
createGpuParallelismCounters()
|
|
13111
|
-
);
|
|
13112
|
-
const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
|
|
13113
|
-
function ensureFrameTelemetryResources() {
|
|
13114
|
-
if (!frameTelemetryResources) {
|
|
13115
|
-
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,
|
|
13116
13257
|
device,
|
|
13117
|
-
|
|
13118
|
-
|
|
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
|
+
)
|
|
13119
13485
|
});
|
|
13120
13486
|
}
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
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;
|
|
13130
13633
|
});
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
config,
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
frameIndex,
|
|
13147
|
-
accelerationBuilt,
|
|
13148
|
-
accelerationBuildCount,
|
|
13149
|
-
accelerationBuildSubmitted,
|
|
13150
|
-
frameSubmissionCount,
|
|
13151
|
-
frameConfigSlotCount,
|
|
13152
|
-
gpuAdapterParallelism,
|
|
13153
|
-
parallelismCounters,
|
|
13154
|
-
renderedSamplesPerPixel,
|
|
13155
|
-
targetSamplesPerPixel,
|
|
13156
|
-
frameTimeBudgetMs,
|
|
13157
|
-
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
|
|
13158
13649
|
});
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
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(
|
|
13164
13671
|
device,
|
|
13165
13672
|
constants,
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
});
|
|
13169
|
-
}
|
|
13170
|
-
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
13171
|
-
if (slot >= frameConfigSlotCount) {
|
|
13172
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
13173
|
-
}
|
|
13174
|
-
const offset = slot * configBufferStride;
|
|
13175
|
-
device.queue.writeBuffer(
|
|
13176
|
-
configBuffer,
|
|
13177
|
-
offset,
|
|
13178
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
13673
|
+
config.environmentMap,
|
|
13674
|
+
config.environmentColor
|
|
13179
13675
|
);
|
|
13180
|
-
|
|
13181
|
-
}
|
|
13182
|
-
function createFrameConfigWriter(frameIndex) {
|
|
13183
|
-
let slot = 0;
|
|
13184
|
-
return (tile, buildRange = {}) => {
|
|
13185
|
-
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
13186
|
-
slot += 1;
|
|
13187
|
-
return offset;
|
|
13188
|
-
};
|
|
13189
|
-
}
|
|
13190
|
-
function dispatchGpuAccelerationBuild(frameIndex, parallelism) {
|
|
13191
|
-
const submitted = dispatchWavefrontGpuAccelerationBuild({
|
|
13192
|
-
config,
|
|
13193
|
-
accelerationBuilt,
|
|
13194
|
-
tiles,
|
|
13676
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
13195
13677
|
device,
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
frameIndex
|
|
13202
|
-
});
|
|
13203
|
-
if (submitted) {
|
|
13204
|
-
accelerationBuilt = true;
|
|
13205
|
-
accelerationBuildCount += 1;
|
|
13206
|
-
}
|
|
13207
|
-
return submitted;
|
|
13208
|
-
}
|
|
13209
|
-
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
13210
|
-
return dispatchWavefrontFrame({
|
|
13211
|
-
config,
|
|
13212
|
-
tiles,
|
|
13678
|
+
constants,
|
|
13679
|
+
config.environmentMap,
|
|
13680
|
+
config.environmentColor
|
|
13681
|
+
);
|
|
13682
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
13213
13683
|
device,
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
renderedSamplesPerPixel,
|
|
13217
|
-
frameEncoder,
|
|
13218
|
-
createFrameConfigWriter
|
|
13219
|
-
});
|
|
13220
|
-
}
|
|
13221
|
-
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
13222
|
-
const frameStartTimeMs = nowMs3();
|
|
13223
|
-
frame += 1;
|
|
13224
|
-
const frameIndex = frame + config.frameIndex;
|
|
13225
|
-
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
13226
|
-
const parallelismCounters = createGpuParallelismCounters();
|
|
13227
|
-
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
13228
|
-
const frameSubmissionCount = dispatchFrame(
|
|
13229
|
-
frameIndex,
|
|
13230
|
-
parallelismCounters,
|
|
13231
|
-
samplingPlan.renderedSamplesPerPixel
|
|
13684
|
+
constants,
|
|
13685
|
+
config.mediums
|
|
13232
13686
|
);
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13242
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13243
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13244
|
-
}),
|
|
13245
|
-
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
13246
|
-
lastGpuParallelism,
|
|
13247
|
-
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
13248
|
-
frameTimeMs,
|
|
13249
|
-
false
|
|
13250
|
-
),
|
|
13251
|
-
timings: createWavefrontFrameTimingTelemetry({
|
|
13252
|
-
status: "not-requested",
|
|
13253
|
-
source: "cpu-submit",
|
|
13254
|
-
timestampQueryStatus: "not-recorded",
|
|
13255
|
-
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
|
|
13256
13695
|
})
|
|
13257
13696
|
});
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
return dispatchWavefrontFrameAwaitingGpu({
|
|
13261
|
-
config,
|
|
13262
|
-
tiles,
|
|
13697
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
13698
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
13263
13699
|
device,
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
optionsForFrame
|
|
13270
|
-
});
|
|
13271
|
-
}
|
|
13272
|
-
async function readOutputProbe(optionsForProbe = {}) {
|
|
13273
|
-
return readWavefrontOutputProbe({
|
|
13700
|
+
constants,
|
|
13701
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
13702
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
13703
|
+
);
|
|
13704
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
13274
13705
|
device,
|
|
13275
13706
|
constants,
|
|
13276
|
-
config,
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
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"
|
|
13280
13761
|
});
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
const
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
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
|
-
|
|
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,
|
|
13338
13889
|
{
|
|
13339
|
-
|
|
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,
|
|
13340
13897
|
includePresent: tileIndex + 1 >= tiles.length
|
|
13341
13898
|
}
|
|
13342
|
-
)
|
|
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,
|
|
13343
13936
|
allowTimeout: false
|
|
13937
|
+
} : {
|
|
13938
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13939
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13344
13940
|
};
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
accelerationBuildSubmitted,
|
|
13350
|
-
frameSubmissionCount,
|
|
13351
|
-
parallelismCounters,
|
|
13352
|
-
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
13353
|
-
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
13354
|
-
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
13355
|
-
budgetConstrained: samplingPlan.budgetConstrained
|
|
13356
|
-
});
|
|
13357
|
-
} else {
|
|
13358
|
-
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
13359
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
13360
|
-
tiles.length,
|
|
13361
|
-
renderOptions.submittedWorkTimeoutMs,
|
|
13362
|
-
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
13363
|
-
);
|
|
13364
|
-
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
13365
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13366
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
13367
|
-
allowTimeout: false
|
|
13368
|
-
} : {
|
|
13369
|
-
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
13370
|
-
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
13371
|
-
};
|
|
13372
|
-
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
13373
|
-
if (awaitGPUCompletion) {
|
|
13374
|
-
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
13941
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
13942
|
+
if (awaitGPUCompletion) {
|
|
13943
|
+
await waitForSubmittedGpuWork2(submissionWaitOptions);
|
|
13944
|
+
}
|
|
13375
13945
|
}
|
|
13946
|
+
} finally {
|
|
13947
|
+
activeFrameTelemetry = null;
|
|
13376
13948
|
}
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
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
|
-
|
|
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();
|
|
13456
14031
|
return Object.freeze({
|
|
13457
|
-
...completedFrame,
|
|
13458
|
-
transportGuardrails: createWavefrontTransportGuardrailSummary(completedFrame)
|
|
13459
|
-
});
|
|
13460
|
-
}
|
|
13461
|
-
function rebuildLiveConfig(overrides = {}) {
|
|
13462
|
-
return createWavefrontPathTracingComputeConfig({
|
|
13463
|
-
...options,
|
|
13464
14032
|
canvas,
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
featureFlags: options.featureFlags,
|
|
13478
|
-
frameIndex: config.frameIndex,
|
|
13479
|
-
...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
|
|
13480
14045
|
});
|
|
13481
|
-
}
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
bindGroups = createTraceBindGroups();
|
|
13486
|
-
if (previousMediumTextureResource?.ownsTexture) {
|
|
13487
|
-
previousMediumTextureResource.texture?.destroy?.();
|
|
13488
|
-
}
|
|
13489
|
-
}
|
|
13490
|
-
function updateSceneObjects(sceneObjects) {
|
|
13491
|
-
progressive?.reset();
|
|
13492
|
-
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
13493
|
-
packedScene = nextPackedScene;
|
|
13494
|
-
const nextConfig = rebuildLiveConfig();
|
|
13495
|
-
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
13496
|
-
rebuildMediumResources(nextConfig);
|
|
14046
|
+
} catch (error) {
|
|
14047
|
+
try {
|
|
14048
|
+
await finishAllocationScopes?.();
|
|
14049
|
+
} catch {
|
|
13497
14050
|
}
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
function updateCamera(cameraOptions = {}) {
|
|
13503
|
-
progressive?.reset();
|
|
13504
|
-
activeCameraOptions = cameraOptions;
|
|
13505
|
-
config = updateWavefrontCameraConfig(config, cameraOptions);
|
|
13506
|
-
return config;
|
|
13507
|
-
}
|
|
13508
|
-
function getSnapshot() {
|
|
13509
|
-
return Object.freeze({
|
|
13510
|
-
frame,
|
|
13511
|
-
accumulatedSamplesPerPixel: progressive?.samples ?? 0,
|
|
13512
|
-
width: config.width,
|
|
13513
|
-
height: config.height,
|
|
13514
|
-
maxDepth: config.maxDepth,
|
|
13515
|
-
tiles: tiles.length,
|
|
13516
|
-
tileSize: config.tileSize,
|
|
13517
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
13518
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
13519
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
13520
|
-
triangleCount: config.triangleCount,
|
|
13521
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
13522
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
13523
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
13524
|
-
mediumCount: config.mediumCount,
|
|
13525
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
13526
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
13527
|
-
strictPhysicalLowSppLighting: config.strictPhysicalLowSppLighting,
|
|
13528
|
-
transportExperiments: config.transportExperiments,
|
|
13529
|
-
transportExperimentFlags: config.transportExperimentFlags,
|
|
13530
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
13531
|
-
displayQuality: config.displayQuality,
|
|
13532
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
13533
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
13534
|
-
accelerationBuilt,
|
|
13535
|
-
accelerationBuildCount,
|
|
13536
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
13537
|
-
gpuParallelism: lastGpuParallelism,
|
|
13538
|
-
memory: config.memory
|
|
13539
|
-
});
|
|
13540
|
-
}
|
|
13541
|
-
function destroy() {
|
|
13542
|
-
progressive?.destroy();
|
|
13543
|
-
frameTelemetryResources?.destroy?.();
|
|
13544
|
-
activeQueue.destroy?.();
|
|
13545
|
-
nextQueue.destroy?.();
|
|
13546
|
-
hitBuffer.destroy?.();
|
|
13547
|
-
accumulationBuffer.destroy?.();
|
|
13548
|
-
pathVertexBuffer.destroy?.();
|
|
13549
|
-
sceneObjectBuffer.destroy?.();
|
|
13550
|
-
triangleBuffer.destroy?.();
|
|
13551
|
-
bvhNodeBuffer.destroy?.();
|
|
13552
|
-
meshVertexBuffer.destroy?.();
|
|
13553
|
-
meshIndexBuffer.destroy?.();
|
|
13554
|
-
meshRangeBuffer.destroy?.();
|
|
13555
|
-
environmentPortalBuffer.destroy?.();
|
|
13556
|
-
bvhLeafRefBuffer.destroy?.();
|
|
13557
|
-
configBuffer.destroy?.();
|
|
13558
|
-
bvhBuildConfigBuffer.destroy?.();
|
|
13559
|
-
counterBuffer.destroy?.();
|
|
13560
|
-
activeDispatchBuffer.destroy?.();
|
|
13561
|
-
radianceTexture.destroy?.();
|
|
13562
|
-
denoiseScratchTexture.destroy?.();
|
|
13563
|
-
outputTexture.destroy?.();
|
|
13564
|
-
if (environmentMapResource.ownsTexture) {
|
|
13565
|
-
environmentMapResource.texture?.destroy?.();
|
|
13566
|
-
}
|
|
13567
|
-
if (environmentSamplingResource.ownsTexture) {
|
|
13568
|
-
environmentSamplingResource.texture?.destroy?.();
|
|
13569
|
-
}
|
|
13570
|
-
if (mediumTextureResource.ownsTexture) {
|
|
13571
|
-
mediumTextureResource.texture?.destroy?.();
|
|
13572
|
-
}
|
|
13573
|
-
brdfLutResource.texture?.destroy?.();
|
|
13574
|
-
if (baseColorAtlasResource.ownsTexture) {
|
|
13575
|
-
baseColorAtlasResource.texture?.destroy?.();
|
|
13576
|
-
}
|
|
13577
|
-
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
13578
|
-
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
13579
|
-
}
|
|
13580
|
-
if (normalAtlasResource.ownsTexture) {
|
|
13581
|
-
normalAtlasResource.texture?.destroy?.();
|
|
13582
|
-
}
|
|
13583
|
-
if (occlusionAtlasResource.ownsTexture) {
|
|
13584
|
-
occlusionAtlasResource.texture?.destroy?.();
|
|
13585
|
-
}
|
|
13586
|
-
if (emissiveAtlasResource.ownsTexture) {
|
|
13587
|
-
emissiveAtlasResource.texture?.destroy?.();
|
|
13588
|
-
}
|
|
13589
|
-
Object.values(extensionAtlasResources).forEach((resource) => {
|
|
13590
|
-
if (resource.ownsTexture) {
|
|
13591
|
-
resource.texture?.destroy?.();
|
|
13592
|
-
}
|
|
13593
|
-
});
|
|
13594
|
-
context.unconfigure?.();
|
|
14051
|
+
context?.unconfigure?.();
|
|
14052
|
+
allocationTracker.destroy();
|
|
14053
|
+
rawDevice.destroy?.();
|
|
14054
|
+
throw error;
|
|
13595
14055
|
}
|
|
13596
|
-
return Object.freeze({
|
|
13597
|
-
canvas,
|
|
13598
|
-
context,
|
|
13599
|
-
device,
|
|
13600
|
-
format,
|
|
13601
|
-
config,
|
|
13602
|
-
renderOnce,
|
|
13603
|
-
renderFrame,
|
|
13604
|
-
readOutputProbe,
|
|
13605
|
-
updateSceneObjects,
|
|
13606
|
-
updateCamera,
|
|
13607
|
-
resetAccumulation: () => progressive?.reset(),
|
|
13608
|
-
getSnapshot,
|
|
13609
|
-
destroy
|
|
13610
|
-
});
|
|
13611
14056
|
}
|
|
13612
14057
|
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
13613
14058
|
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|