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