@plasius/gpu-renderer 0.2.0 → 0.2.2
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 +41 -4
- package/README.md +17 -1
- package/dist/index.cjs +131 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +126 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/index.d.ts +68 -26
- package/src/index.js +5 -0
- package/src/wavefront-compute.js +127 -16
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
|
7
7
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
8
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
9
9
|
var WORKGROUP_SIZE = 64;
|
|
10
|
+
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
11
|
+
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
12
|
+
var rendererWavefrontComputeStatsStride = 8;
|
|
10
13
|
var RAY_RECORD_BYTES = 80;
|
|
11
14
|
var HIT_RECORD_BYTES = 208;
|
|
12
15
|
var SCENE_OBJECT_RECORD_BYTES = 96;
|
|
@@ -19,7 +22,9 @@ var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
|
19
22
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
20
23
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
21
24
|
var CONFIG_BUFFER_BYTES = 272;
|
|
22
|
-
var
|
|
25
|
+
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
26
|
+
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
27
|
+
var COUNTER_BUFFER_BYTES = 32;
|
|
23
28
|
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
24
29
|
var MATERIAL_DIFFUSE = 0;
|
|
25
30
|
var MATERIAL_METAL = 1;
|
|
@@ -59,7 +64,9 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
59
64
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
60
65
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
61
66
|
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
62
|
-
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
67
|
+
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
68
|
+
counterRecordBytes: COUNTER_BUFFER_BYTES,
|
|
69
|
+
indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
|
|
63
70
|
});
|
|
64
71
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
65
72
|
sphere: OBJECT_KIND_SPHERE,
|
|
@@ -1010,7 +1017,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1010
1017
|
environmentPortalBytes,
|
|
1011
1018
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1012
1019
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1013
|
-
|
|
1020
|
+
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1021
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1014
1022
|
});
|
|
1015
1023
|
}
|
|
1016
1024
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1087,6 +1095,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1087
1095
|
environmentPortals.length > 0
|
|
1088
1096
|
);
|
|
1089
1097
|
return Object.freeze({
|
|
1098
|
+
mode: rendererWavefrontComputeMode,
|
|
1090
1099
|
width,
|
|
1091
1100
|
height,
|
|
1092
1101
|
maxDepth,
|
|
@@ -1141,6 +1150,9 @@ function getGpuUsageConstants() {
|
|
|
1141
1150
|
if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
|
|
1142
1151
|
throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
|
|
1143
1152
|
}
|
|
1153
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
1154
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
1155
|
+
}
|
|
1144
1156
|
return {
|
|
1145
1157
|
buffer: GPUBufferUsage,
|
|
1146
1158
|
texture: GPUTextureUsage,
|
|
@@ -1777,6 +1789,10 @@ struct Counters {
|
|
|
1777
1789
|
nextCount: atomic<u32>,
|
|
1778
1790
|
terminatedCount: atomic<u32>,
|
|
1779
1791
|
hitCount: atomic<u32>,
|
|
1792
|
+
dispatchX: u32,
|
|
1793
|
+
dispatchY: u32,
|
|
1794
|
+
dispatchZ: u32,
|
|
1795
|
+
dispatchPad: u32,
|
|
1780
1796
|
};
|
|
1781
1797
|
|
|
1782
1798
|
struct Candidate {
|
|
@@ -1939,6 +1955,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
1939
1955
|
return environment_radiance(origin, direction);
|
|
1940
1956
|
}
|
|
1941
1957
|
|
|
1958
|
+
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
1959
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1960
|
+
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
1961
|
+
let normalEnvironment = gated_environment_radiance(
|
|
1962
|
+
hit.position.xyz + normal * 0.003,
|
|
1963
|
+
normal
|
|
1964
|
+
);
|
|
1965
|
+
let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
|
|
1966
|
+
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
1967
|
+
return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1942
1970
|
fn default_mesh_range() -> MeshRange {
|
|
1943
1971
|
return MeshRange(
|
|
1944
1972
|
0u,
|
|
@@ -2510,6 +2538,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
|
|
|
2510
2538
|
return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
|
|
2511
2539
|
}
|
|
2512
2540
|
|
|
2541
|
+
fn ray_workgroups_for_count(rayCount: u32) -> u32 {
|
|
2542
|
+
return max(1u, (rayCount + 63u) / 64u);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
fn write_active_dispatch_args(activeCount: u32) {
|
|
2546
|
+
counters.dispatchX = ray_workgroups_for_count(activeCount);
|
|
2547
|
+
counters.dispatchY = 1u;
|
|
2548
|
+
counters.dispatchZ = 1u;
|
|
2549
|
+
counters.dispatchPad = 0u;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2513
2552
|
fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
2514
2553
|
return value / (vec3<f32>(1.0) + value);
|
|
2515
2554
|
}
|
|
@@ -2522,6 +2561,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2522
2561
|
atomicStore(&counters.nextCount, 0u);
|
|
2523
2562
|
atomicStore(&counters.terminatedCount, 0u);
|
|
2524
2563
|
atomicStore(&counters.hitCount, 0u);
|
|
2564
|
+
write_active_dispatch_args(config.tilePixelCount);
|
|
2525
2565
|
}
|
|
2526
2566
|
if (index >= config.tilePixelCount) {
|
|
2527
2567
|
return;
|
|
@@ -2797,9 +2837,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2797
2837
|
}
|
|
2798
2838
|
|
|
2799
2839
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
2840
|
+
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2800
2841
|
accumulation[ray.rayId] =
|
|
2801
|
-
accumulation[ray.rayId] +
|
|
2802
|
-
vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
|
|
2842
|
+
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
2803
2843
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2804
2844
|
return;
|
|
2805
2845
|
}
|
|
@@ -2808,6 +2848,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2808
2848
|
let scatter = scatter_direction(ray, hit, seed);
|
|
2809
2849
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
2810
2850
|
if (nextIndex >= config.tilePixelCount) {
|
|
2851
|
+
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2852
|
+
accumulation[ray.rayId] =
|
|
2853
|
+
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
2854
|
+
atomicAdd(&counters.terminatedCount, 1u);
|
|
2811
2855
|
return;
|
|
2812
2856
|
}
|
|
2813
2857
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
@@ -2836,8 +2880,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2836
2880
|
return;
|
|
2837
2881
|
}
|
|
2838
2882
|
let nextCount = atomicLoad(&counters.nextCount);
|
|
2839
|
-
|
|
2883
|
+
let activeCount = min(nextCount, config.tilePixelCount);
|
|
2884
|
+
atomicStore(&counters.activeCount, activeCount);
|
|
2840
2885
|
atomicStore(&counters.nextCount, 0u);
|
|
2886
|
+
write_active_dispatch_args(activeCount);
|
|
2841
2887
|
}
|
|
2842
2888
|
|
|
2843
2889
|
@compute @workgroup_size(64)
|
|
@@ -3115,10 +3161,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3115
3161
|
);
|
|
3116
3162
|
const counterBuffer = createBuffer(
|
|
3117
3163
|
device,
|
|
3118
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
3164
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
3119
3165
|
COUNTER_BUFFER_BYTES,
|
|
3120
3166
|
"plasius.wavefront.counters"
|
|
3121
3167
|
);
|
|
3168
|
+
const activeDispatchBuffer = createBuffer(
|
|
3169
|
+
device,
|
|
3170
|
+
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
3171
|
+
INDIRECT_DISPATCH_ARGS_BYTES,
|
|
3172
|
+
"plasius.wavefront.activeDispatchArgs"
|
|
3173
|
+
);
|
|
3122
3174
|
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
3123
3175
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
3124
3176
|
const packedTriangles = packWavefrontTriangles(
|
|
@@ -3540,24 +3592,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3540
3592
|
return true;
|
|
3541
3593
|
}
|
|
3542
3594
|
function encodeTileSample(encoder, tile, configOffset) {
|
|
3543
|
-
const
|
|
3544
|
-
label: "plasius.wavefront.
|
|
3595
|
+
const generatePass = encoder.beginComputePass({
|
|
3596
|
+
label: "plasius.wavefront.generatePrimaryRaysPass"
|
|
3545
3597
|
});
|
|
3546
3598
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3599
|
+
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3600
|
+
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
3601
|
+
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
3602
|
+
generatePass.end();
|
|
3551
3603
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3604
|
+
encoder.copyBufferToBuffer(
|
|
3605
|
+
counterBuffer,
|
|
3606
|
+
COUNTER_DISPATCH_ARGS_OFFSET,
|
|
3607
|
+
activeDispatchBuffer,
|
|
3608
|
+
0,
|
|
3609
|
+
INDIRECT_DISPATCH_ARGS_BYTES
|
|
3610
|
+
);
|
|
3611
|
+
const passEncoder = encoder.beginComputePass({
|
|
3612
|
+
label: `plasius.wavefront.bounce.${bounceIndex}`
|
|
3613
|
+
});
|
|
3552
3614
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3553
3615
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3554
|
-
passEncoder.
|
|
3616
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3555
3617
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
3556
|
-
passEncoder.
|
|
3618
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3557
3619
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
3558
3620
|
passEncoder.dispatchWorkgroups(1);
|
|
3621
|
+
passEncoder.end();
|
|
3559
3622
|
}
|
|
3560
|
-
passEncoder.end();
|
|
3561
3623
|
}
|
|
3562
3624
|
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3563
3625
|
const passEncoder = encoder.beginComputePass({
|
|
@@ -3700,6 +3762,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3700
3762
|
luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
|
|
3701
3763
|
});
|
|
3702
3764
|
}
|
|
3765
|
+
async function renderFrame(renderOptions = {}) {
|
|
3766
|
+
const frameStats = renderOnce();
|
|
3767
|
+
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
3768
|
+
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
3769
|
+
return Object.freeze({
|
|
3770
|
+
...frameStats,
|
|
3771
|
+
outputProbe: probe ? Object.freeze({
|
|
3772
|
+
...probe,
|
|
3773
|
+
sampledPixels: 1,
|
|
3774
|
+
nonZeroSamples: maxChannel > 0 ? 1 : 0,
|
|
3775
|
+
maxChannel
|
|
3776
|
+
}) : null,
|
|
3777
|
+
bounces: [],
|
|
3778
|
+
termination: Object.freeze({
|
|
3779
|
+
emissive: 0,
|
|
3780
|
+
environment: 0,
|
|
3781
|
+
ambientFallback: 0,
|
|
3782
|
+
maxDepth: 0
|
|
3783
|
+
}),
|
|
3784
|
+
queueOverflow: 0
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3703
3787
|
function updateSceneObjects(sceneObjects) {
|
|
3704
3788
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
3705
3789
|
packedScene = nextPackedScene;
|
|
@@ -3758,6 +3842,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3758
3842
|
configBuffer.destroy?.();
|
|
3759
3843
|
bvhBuildConfigBuffer.destroy?.();
|
|
3760
3844
|
counterBuffer.destroy?.();
|
|
3845
|
+
activeDispatchBuffer.destroy?.();
|
|
3761
3846
|
radianceTexture.destroy?.();
|
|
3762
3847
|
denoiseScratchTexture.destroy?.();
|
|
3763
3848
|
outputTexture.destroy?.();
|
|
@@ -3770,12 +3855,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3770
3855
|
format,
|
|
3771
3856
|
config,
|
|
3772
3857
|
renderOnce,
|
|
3858
|
+
renderFrame,
|
|
3773
3859
|
readOutputProbe,
|
|
3774
3860
|
updateSceneObjects,
|
|
3775
3861
|
getSnapshot,
|
|
3776
3862
|
destroy
|
|
3777
3863
|
});
|
|
3778
3864
|
}
|
|
3865
|
+
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
3866
|
+
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|
|
3867
|
+
try {
|
|
3868
|
+
return await renderer.renderFrame(options);
|
|
3869
|
+
} finally {
|
|
3870
|
+
renderer.destroy();
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
3874
|
+
const workgroupSize = readPositiveInteger(
|
|
3875
|
+
"workgroupSize",
|
|
3876
|
+
options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
|
|
3877
|
+
rendererWavefrontComputeWorkgroupSize
|
|
3878
|
+
);
|
|
3879
|
+
if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
|
|
3880
|
+
throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
|
|
3881
|
+
}
|
|
3882
|
+
return WAVEFRONT_COMPUTE_WGSL;
|
|
3883
|
+
}
|
|
3779
3884
|
|
|
3780
3885
|
// src/index.js
|
|
3781
3886
|
var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
|
|
@@ -5075,6 +5180,7 @@ export {
|
|
|
5075
5180
|
createWavefrontMeshAcceleration,
|
|
5076
5181
|
createWavefrontPathTracingComputeConfig,
|
|
5077
5182
|
createWavefrontPathTracingComputeRenderer,
|
|
5183
|
+
createWavefrontPathTracingComputeShaderSource,
|
|
5078
5184
|
createWavefrontPathTracingPlan,
|
|
5079
5185
|
createWavefrontReferenceRay,
|
|
5080
5186
|
defaultRendererClearColor,
|
|
@@ -5088,11 +5194,15 @@ export {
|
|
|
5088
5194
|
packWavefrontBvhNodes,
|
|
5089
5195
|
packWavefrontSceneObjects,
|
|
5090
5196
|
packWavefrontTriangles,
|
|
5197
|
+
renderWavefrontPathTracingComputeFrame,
|
|
5091
5198
|
rendererAccelerationStructureUpdateClasses,
|
|
5092
5199
|
rendererDebugOwner,
|
|
5093
5200
|
rendererRayTracingStageOrder,
|
|
5094
5201
|
rendererRepresentationBands,
|
|
5095
5202
|
rendererWavefrontBufferSchemaVersion,
|
|
5203
|
+
rendererWavefrontComputeMode,
|
|
5204
|
+
rendererWavefrontComputeStatsStride,
|
|
5205
|
+
rendererWavefrontComputeWorkgroupSize,
|
|
5096
5206
|
rendererWavefrontHitTypes,
|
|
5097
5207
|
rendererWavefrontPassOrder,
|
|
5098
5208
|
rendererWavefrontQueuePairStrategy,
|