@tinypoly/gpu-renderer 0.0.0-stage → 0.0.0
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/ARCHITECTURE.md +334 -0
- package/LICENSE +21 -0
- package/README.md +258 -2
- package/dist/accumulator.d.ts +27 -0
- package/dist/accumulator.d.ts.map +1 -0
- package/dist/accumulator.js +57 -0
- package/dist/accumulator.js.map +1 -0
- package/dist/bvh.d.ts +26 -0
- package/dist/bvh.d.ts.map +1 -0
- package/dist/bvh.js +207 -0
- package/dist/bvh.js.map +1 -0
- package/dist/camera.d.ts +29 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +48 -0
- package/dist/camera.js.map +1 -0
- package/dist/canvas.d.ts +9 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +11 -0
- package/dist/canvas.js.map +1 -0
- package/dist/denoise.d.ts +69 -0
- package/dist/denoise.d.ts.map +1 -0
- package/dist/denoise.js +455 -0
- package/dist/denoise.js.map +1 -0
- package/dist/device.d.ts +37 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +52 -0
- package/dist/device.js.map +1 -0
- package/dist/dispatchScheduler.d.ts +43 -0
- package/dist/dispatchScheduler.d.ts.map +1 -0
- package/dist/dispatchScheduler.js +49 -0
- package/dist/dispatchScheduler.js.map +1 -0
- package/dist/environment.d.ts +39 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +168 -0
- package/dist/environment.js.map +1 -0
- package/dist/frameLayout.d.ts +4 -0
- package/dist/frameLayout.d.ts.map +1 -0
- package/dist/frameLayout.js +58 -0
- package/dist/frameLayout.js.map +1 -0
- package/dist/geometry.d.ts +75 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +671 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lights.d.ts +64 -0
- package/dist/lights.d.ts.map +1 -0
- package/dist/lights.js +264 -0
- package/dist/lights.js.map +1 -0
- package/dist/materialLayout.d.ts +143 -0
- package/dist/materialLayout.d.ts.map +1 -0
- package/dist/materialLayout.js +297 -0
- package/dist/materialLayout.js.map +1 -0
- package/dist/math.d.ts +10 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +40 -0
- package/dist/math.js.map +1 -0
- package/dist/pathTracePass.d.ts +27 -0
- package/dist/pathTracePass.d.ts.map +1 -0
- package/dist/pathTracePass.js +83 -0
- package/dist/pathTracePass.js.map +1 -0
- package/dist/planarCapture.d.ts +38 -0
- package/dist/planarCapture.d.ts.map +1 -0
- package/dist/planarCapture.js +163 -0
- package/dist/planarCapture.js.map +1 -0
- package/dist/presentPass.d.ts +31 -0
- package/dist/presentPass.d.ts.map +1 -0
- package/dist/presentPass.js +78 -0
- package/dist/presentPass.js.map +1 -0
- package/dist/renderer.d.ts +196 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +839 -0
- package/dist/renderer.js.map +1 -0
- package/dist/sceneAssets.d.ts +24 -0
- package/dist/sceneAssets.d.ts.map +1 -0
- package/dist/sceneAssets.js +77 -0
- package/dist/sceneAssets.js.map +1 -0
- package/dist/sceneUpload.d.ts +61 -0
- package/dist/sceneUpload.d.ts.map +1 -0
- package/dist/sceneUpload.js +98 -0
- package/dist/sceneUpload.js.map +1 -0
- package/dist/shaderModule.d.ts +7 -0
- package/dist/shaderModule.d.ts.map +1 -0
- package/dist/shaderModule.js +19 -0
- package/dist/shaderModule.js.map +1 -0
- package/dist/shaderProgram.d.ts +124 -0
- package/dist/shaderProgram.d.ts.map +1 -0
- package/dist/shaderProgram.js +1030 -0
- package/dist/shaderProgram.js.map +1 -0
- package/dist/structLayout.d.ts +28 -0
- package/dist/structLayout.d.ts.map +1 -0
- package/dist/structLayout.js +58 -0
- package/dist/structLayout.js.map +1 -0
- package/dist/texturePack.d.ts +54 -0
- package/dist/texturePack.d.ts.map +1 -0
- package/dist/texturePack.js +86 -0
- package/dist/texturePack.js.map +1 -0
- package/dist/upstream.d.ts +7 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +11 -0
- package/dist/upstream.js.map +1 -0
- package/dist/volumetric.d.ts +10 -0
- package/dist/volumetric.d.ts.map +1 -0
- package/dist/volumetric.js +34 -0
- package/dist/volumetric.js.map +1 -0
- package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
- package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bsdf.wgsl.js +619 -0
- package/dist/wgsl/bsdf.wgsl.js.map +1 -0
- package/dist/wgsl/bvh.wgsl.d.ts +3 -0
- package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bvh.wgsl.js +218 -0
- package/dist/wgsl/bvh.wgsl.js.map +1 -0
- package/dist/wgsl/common.wgsl.d.ts +3 -0
- package/dist/wgsl/common.wgsl.d.ts.map +1 -0
- package/dist/wgsl/common.wgsl.js +193 -0
- package/dist/wgsl/common.wgsl.js.map +1 -0
- package/dist/wgsl/environment.wgsl.d.ts +10 -0
- package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
- package/dist/wgsl/environment.wgsl.js +145 -0
- package/dist/wgsl/environment.wgsl.js.map +1 -0
- package/dist/wgsl/lights.wgsl.d.ts +10 -0
- package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
- package/dist/wgsl/lights.wgsl.js +434 -0
- package/dist/wgsl/lights.wgsl.js.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.js +1020 -0
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
- package/dist/wgsl/present.wgsl.d.ts +7 -0
- package/dist/wgsl/present.wgsl.d.ts.map +1 -0
- package/dist/wgsl/present.wgsl.js +154 -0
- package/dist/wgsl/present.wgsl.js.map +1 -0
- package/dist/wgsl/random.wgsl.d.ts +9 -0
- package/dist/wgsl/random.wgsl.d.ts.map +1 -0
- package/dist/wgsl/random.wgsl.js +91 -0
- package/dist/wgsl/random.wgsl.js.map +1 -0
- package/dist/wgsl/sampling.wgsl.d.ts +3 -0
- package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
- package/dist/wgsl/sampling.wgsl.js +31 -0
- package/dist/wgsl/sampling.wgsl.js.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.js +120 -0
- package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
- package/dist/wgsl/surface.wgsl.d.ts +10 -0
- package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
- package/dist/wgsl/surface.wgsl.js +794 -0
- package/dist/wgsl/surface.wgsl.js.map +1 -0
- package/dist/wgsl/texture.wgsl.d.ts +11 -0
- package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
- package/dist/wgsl/texture.wgsl.js +132 -0
- package/dist/wgsl/texture.wgsl.js.map +1 -0
- package/package.json +76 -3
- package/src/accumulator.ts +64 -0
- package/src/bvh.ts +272 -0
- package/src/camera.ts +77 -0
- package/src/canvas.ts +13 -0
- package/src/denoise.ts +514 -0
- package/src/device.ts +81 -0
- package/src/dispatchScheduler.ts +71 -0
- package/src/environment.ts +233 -0
- package/src/frameLayout.ts +59 -0
- package/src/geometry.ts +899 -0
- package/src/index.ts +61 -0
- package/src/lights.ts +365 -0
- package/src/materialLayout.ts +408 -0
- package/src/math.ts +48 -0
- package/src/pathTracePass.ts +97 -0
- package/src/planarCapture.ts +225 -0
- package/src/presentPass.ts +99 -0
- package/src/renderer.ts +1117 -0
- package/src/sceneAssets.ts +118 -0
- package/src/sceneUpload.ts +185 -0
- package/src/shaderModule.ts +19 -0
- package/src/shaderProgram.ts +1370 -0
- package/src/structLayout.ts +93 -0
- package/src/texturePack.ts +152 -0
- package/src/upstream.ts +56 -0
- package/src/volumetric.ts +42 -0
- package/src/wgsl/bsdf.wgsl.ts +618 -0
- package/src/wgsl/bvh.wgsl.ts +217 -0
- package/src/wgsl/common.wgsl.ts +193 -0
- package/src/wgsl/environment.wgsl.ts +144 -0
- package/src/wgsl/lights.wgsl.ts +433 -0
- package/src/wgsl/pathTrace.wgsl.ts +1019 -0
- package/src/wgsl/present.wgsl.ts +153 -0
- package/src/wgsl/random.wgsl.ts +90 -0
- package/src/wgsl/sampling.wgsl.ts +30 -0
- package/src/wgsl/shaderSupport.wgsl.ts +119 -0
- package/src/wgsl/surface.wgsl.ts +793 -0
- package/src/wgsl/texture.wgsl.ts +131 -0
package/dist/denoise.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { createShaderModule } from "./shaderModule.js";
|
|
2
|
+
export const DEFAULT_DENOISER = Object.freeze({
|
|
3
|
+
strength: 1,
|
|
4
|
+
passes: 3,
|
|
5
|
+
edge: 2,
|
|
6
|
+
onlyNoisy: true,
|
|
7
|
+
antiFirefly: true,
|
|
8
|
+
});
|
|
9
|
+
/** The settings with `input` applied, each field kept in range. */
|
|
10
|
+
export function mergeDenoiser(base, input = {}) {
|
|
11
|
+
const merged = { ...base, ...Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined)) };
|
|
12
|
+
return {
|
|
13
|
+
strength: Math.min(1, Math.max(0, Number(merged.strength) || 0)),
|
|
14
|
+
passes: Math.min(5, Math.max(1, Math.round(Number(merged.passes) || 1))),
|
|
15
|
+
edge: Math.max(0.01, Number(merged.edge) || DEFAULT_DENOISER.edge),
|
|
16
|
+
onlyNoisy: Boolean(merged.onlyNoisy),
|
|
17
|
+
antiFirefly: Boolean(merged.antiFirefly),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The error a pixel counts as converged under, the one adaptive sampling stops at. */
|
|
21
|
+
export const CONVERGED_ERROR = 0.004;
|
|
22
|
+
/**
|
|
23
|
+
* The denoiser: the spatial half of SVGF (Schied and others, 2017), with the diffuse and the specular light filtered
|
|
24
|
+
* apart, as NVIDIA's RELAX and REBLUR do.
|
|
25
|
+
*
|
|
26
|
+
* The path tracer splits every path at its first scatter, by the lobe it took, so each pixel holds a diffuse and a
|
|
27
|
+
* specular signal, each with its own variance. Each is divided by its own albedo (the diffuse colour, and the GGX
|
|
28
|
+
* lobe's directional albedo), so the filter sees light and never blurs a texture or a colour; both are multiplied back
|
|
29
|
+
* at the end. A pixel brighter than all of its neighbours is first clamped to the brightest of them.
|
|
30
|
+
*
|
|
31
|
+
* Then a few passes of an edge-avoiding a-trous wavelet, its taps twice as far apart each pass. The diffuse light is
|
|
32
|
+
* weighed by the agreement of normals and depths and by its luminance against the pixel's own variance. The specular
|
|
33
|
+
* light also by roughness, by normals more sharply the smoother the surface, and within the footprint its lobe leaves on
|
|
34
|
+
* the screen: the lobe's angle times the distance to what it reflects, seen from the camera. A sharp reflection of
|
|
35
|
+
* something near is left almost alone; a rough reflection of something far is smoothed across a wide area.
|
|
36
|
+
*
|
|
37
|
+
* The weights are normalized, which keeps every flat area at exactly its mean, and each signal is mixed with what was
|
|
38
|
+
* traced by how noisy it still is, so what has converged keeps exactly its traced value.
|
|
39
|
+
*/
|
|
40
|
+
export const DENOISE_WGSL = /* wgsl */ `
|
|
41
|
+
struct Params {
|
|
42
|
+
size: vec2<u32>,
|
|
43
|
+
step: u32,
|
|
44
|
+
count: u32,
|
|
45
|
+
edge: f32,
|
|
46
|
+
strength: f32,
|
|
47
|
+
onlyNoisy: u32,
|
|
48
|
+
converged: f32,
|
|
49
|
+
pixelScale: f32,
|
|
50
|
+
orthographic: u32,
|
|
51
|
+
padding: vec2<u32>,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
55
|
+
@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;
|
|
56
|
+
@group(0) @binding(2) var<storage, read> diffuseIn: array<vec4<f32>>;
|
|
57
|
+
@group(0) @binding(3) var<storage, read> specularIn: array<vec4<f32>>;
|
|
58
|
+
@group(0) @binding(4) var<storage, read_write> diffuseOut: array<vec4<f32>>;
|
|
59
|
+
@group(0) @binding(5) var<storage, read_write> specularOut: array<vec4<f32>>;
|
|
60
|
+
|
|
61
|
+
fn luminance(color: vec3<f32>) -> f32 {
|
|
62
|
+
return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn samples(index: u32) -> f32 {
|
|
66
|
+
return max(accumulation[index].w, 1.0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Each signal's mean radiance: the diffuse one stored apart, the specular one the rest. */
|
|
70
|
+
fn diffuseMean(index: u32) -> vec3<f32> {
|
|
71
|
+
return accumulation[4u * params.count + index].rgb / samples(index);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn specularMean(index: u32) -> vec3<f32> {
|
|
75
|
+
return accumulation[index].rgb / samples(index) - diffuseMean(index);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The albedos the light is divided by, kept off zero so the division never explodes. */
|
|
79
|
+
fn diffuseAlbedo(index: u32) -> vec3<f32> {
|
|
80
|
+
return max(accumulation[2u * params.count + index].rgb / samples(index), vec3<f32>(0.01));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn specularAlbedo(index: u32) -> vec3<f32> {
|
|
84
|
+
return max(accumulation[5u * params.count + index].rgb / samples(index), vec3<f32>(0.01));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fn guideNormal(index: u32) -> vec3<f32> {
|
|
88
|
+
let sum = accumulation[3u * params.count + index].xyz;
|
|
89
|
+
let size = length(sum);
|
|
90
|
+
|
|
91
|
+
return select(vec3<f32>(0.0), sum / size, size > 1e-6);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn guideDepth(index: u32) -> f32 {
|
|
95
|
+
return accumulation[3u * params.count + index].w / samples(index);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn guideRoughness(index: u32) -> f32 {
|
|
99
|
+
return accumulation[2u * params.count + index].w / samples(index);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn specularDistance(index: u32) -> f32 {
|
|
103
|
+
return accumulation[4u * params.count + index].w / samples(index);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The variance of a signal's mean, in the space of its demodulated light. */
|
|
107
|
+
fn variance(mean: vec3<f32>, square: f32, count: f32, albedo: vec3<f32>) -> f32 {
|
|
108
|
+
let m = luminance(mean);
|
|
109
|
+
let a = max(luminance(albedo), 0.01);
|
|
110
|
+
|
|
111
|
+
return max(square / count - m * m, 0.0) / count / (a * a);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Both signals, divided by their albedos, with the variance of their means. */
|
|
115
|
+
@compute @workgroup_size(8, 8, 1)
|
|
116
|
+
fn prepare(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
117
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let index = id.y * params.size.x + id.x;
|
|
122
|
+
let count = accumulation[index].w;
|
|
123
|
+
|
|
124
|
+
if (count <= 0.0) {
|
|
125
|
+
diffuseOut[index] = vec4<f32>(0.0);
|
|
126
|
+
specularOut[index] = vec4<f32>(0.0);
|
|
127
|
+
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let squares = accumulation[params.count + index];
|
|
132
|
+
let diffuse = diffuseMean(index);
|
|
133
|
+
let specular = specularMean(index);
|
|
134
|
+
|
|
135
|
+
diffuseOut[index] = vec4<f32>(diffuse / diffuseAlbedo(index), variance(diffuse, squares.z, count, diffuseAlbedo(index)));
|
|
136
|
+
specularOut[index] = vec4<f32>(
|
|
137
|
+
specular / specularAlbedo(index), variance(specular, squares.w, count, specularAlbedo(index)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn inside(q: vec2<i32>) -> bool {
|
|
141
|
+
return q.x >= 0 && q.y >= 0 && q.x < i32(params.size.x) && q.y < i32(params.size.y);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A value brighter than every neighbour, scaled down to the brightest of them: a firefly, not a feature. */
|
|
145
|
+
fn clampFirefly(value: vec4<f32>, brightest: f32) -> vec4<f32> {
|
|
146
|
+
let own = luminance(value.rgb);
|
|
147
|
+
|
|
148
|
+
if (own <= brightest || own <= 0.0) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return vec4<f32>(value.rgb * (brightest / own), value.w);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@compute @workgroup_size(8, 8, 1)
|
|
156
|
+
fn antiFirefly(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
157
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let pixel = vec2<i32>(id.xy);
|
|
162
|
+
let index = id.y * params.size.x + id.x;
|
|
163
|
+
var diffuse = 0.0;
|
|
164
|
+
var specular = 0.0;
|
|
165
|
+
|
|
166
|
+
for (var dy = -1; dy <= 1; dy = dy + 1) {
|
|
167
|
+
for (var dx = -1; dx <= 1; dx = dx + 1) {
|
|
168
|
+
let q = pixel + vec2<i32>(dx, dy);
|
|
169
|
+
|
|
170
|
+
if ((dx == 0 && dy == 0) || !inside(q)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
175
|
+
diffuse = max(diffuse, luminance(diffuseIn[other].rgb));
|
|
176
|
+
specular = max(specular, luminance(specularIn[other].rgb));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
diffuseOut[index] = clampFirefly(diffuseIn[index], diffuse);
|
|
181
|
+
specularOut[index] = clampFirefly(specularIn[index], specular);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const KERNEL = array<f32, 5>(0.0625, 0.25, 0.375, 0.25, 0.0625);
|
|
185
|
+
|
|
186
|
+
/** The variance around a pixel, blurred over 3 x 3, which steadies the luminance test on a few samples. */
|
|
187
|
+
fn localVariance(pixel: vec2<i32>, specular: bool) -> f32 {
|
|
188
|
+
var sum = 0.0;
|
|
189
|
+
var weight = 0.0;
|
|
190
|
+
|
|
191
|
+
for (var dy = -1; dy <= 1; dy = dy + 1) {
|
|
192
|
+
for (var dx = -1; dx <= 1; dx = dx + 1) {
|
|
193
|
+
let q = pixel + vec2<i32>(dx, dy);
|
|
194
|
+
|
|
195
|
+
if (!inside(q)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
200
|
+
let w = select(0.5, 1.0, dx == 0) * select(0.5, 1.0, dy == 0);
|
|
201
|
+
sum = sum + select(diffuseIn[other].w, specularIn[other].w, specular) * w;
|
|
202
|
+
weight = weight + w;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return sum / max(weight, 1e-6);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* How many pixels the specular lobe's footprint covers: the lobe's angle times the distance to what it reflects, seen
|
|
211
|
+
* from the camera across the distance to the surface plus that.
|
|
212
|
+
*/
|
|
213
|
+
fn specularRadius(index: u32, depth: f32) -> f32 {
|
|
214
|
+
let alpha = guideRoughness(index) * guideRoughness(index);
|
|
215
|
+
let reflected = specularDistance(index);
|
|
216
|
+
|
|
217
|
+
if (params.orthographic != 0u) {
|
|
218
|
+
return min(alpha * reflected / params.pixelScale, 64.0);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return min(alpha * reflected / max(depth + reflected, 1e-6) / params.pixelScale, 64.0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** One a-trous pass over both signals, its taps params.step pixels apart. */
|
|
225
|
+
@compute @workgroup_size(8, 8, 1)
|
|
226
|
+
fn atrous(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
227
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let pixel = vec2<i32>(id.xy);
|
|
232
|
+
let index = id.y * params.size.x + id.x;
|
|
233
|
+
let diffuseCenter = diffuseIn[index];
|
|
234
|
+
let specularCenter = specularIn[index];
|
|
235
|
+
|
|
236
|
+
if (accumulation[index].w <= 0.0) {
|
|
237
|
+
diffuseOut[index] = diffuseCenter;
|
|
238
|
+
specularOut[index] = specularCenter;
|
|
239
|
+
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let normal = guideNormal(index);
|
|
244
|
+
let depth = guideDepth(index);
|
|
245
|
+
let roughness = guideRoughness(index);
|
|
246
|
+
let diffuseSigma = params.edge * sqrt(max(localVariance(pixel, false), 0.0)) + 1e-6;
|
|
247
|
+
let specularSigma = params.edge * sqrt(max(localVariance(pixel, true), 0.0)) + 1e-6;
|
|
248
|
+
let radius = specularRadius(index, depth);
|
|
249
|
+
// A smoother surface's reflection turns faster with its normal: the normal test sharpens with it.
|
|
250
|
+
let specularPower = min(128.0 / max(roughness * roughness, 0.03), 4096.0);
|
|
251
|
+
let step = i32(params.step);
|
|
252
|
+
var diffuse = vec4<f32>(0.0);
|
|
253
|
+
var specular = vec4<f32>(0.0);
|
|
254
|
+
var diffuseTotal = 0.0;
|
|
255
|
+
var specularTotal = 0.0;
|
|
256
|
+
|
|
257
|
+
for (var dy = -2; dy <= 2; dy = dy + 1) {
|
|
258
|
+
for (var dx = -2; dx <= 2; dx = dx + 1) {
|
|
259
|
+
let q = pixel + vec2<i32>(dx, dy) * step;
|
|
260
|
+
|
|
261
|
+
if (!inside(q)) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
266
|
+
let d = diffuseIn[other];
|
|
267
|
+
let s = specularIn[other];
|
|
268
|
+
let base = KERNEL[dx + 2] * KERNEL[dy + 2];
|
|
269
|
+
var wd = base;
|
|
270
|
+
var ws = base;
|
|
271
|
+
|
|
272
|
+
if (dx != 0 || dy != 0) {
|
|
273
|
+
if (accumulation[other].w <= 0.0) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let offset = length(vec2<f32>(f32(dx * step), f32(dy * step)));
|
|
278
|
+
let alignment = max(dot(normal, guideNormal(other)), 0.0);
|
|
279
|
+
let depthWeight = exp(-abs(depth - guideDepth(other)) / (0.02 * depth * offset + 1e-3));
|
|
280
|
+
|
|
281
|
+
wd = wd * pow(alignment, 128.0) * depthWeight
|
|
282
|
+
* exp(-abs(luminance(d.rgb) - luminance(diffuseCenter.rgb)) / diffuseSigma);
|
|
283
|
+
|
|
284
|
+
ws = ws * pow(alignment, specularPower) * depthWeight
|
|
285
|
+
* exp(-abs(guideRoughness(other) - roughness) * 10.0)
|
|
286
|
+
* exp(-abs(luminance(s.rgb) - luminance(specularCenter.rgb)) / specularSigma)
|
|
287
|
+
* select(0.0, exp(-offset * offset / (radius * radius)), radius >= 0.5);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
diffuse = diffuse + vec4<f32>(d.rgb * wd, d.w * wd * wd);
|
|
291
|
+
specular = specular + vec4<f32>(s.rgb * ws, s.w * ws * ws);
|
|
292
|
+
diffuseTotal = diffuseTotal + wd;
|
|
293
|
+
specularTotal = specularTotal + ws;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
diffuseOut[index] = vec4<f32>(diffuse.rgb / diffuseTotal, diffuse.w / (diffuseTotal * diffuseTotal));
|
|
298
|
+
specularOut[index] = vec4<f32>(specular.rgb / specularTotal, specular.w / (specularTotal * specularTotal));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** How much of the filtered signal replaces the traced one: by how noisy the signal still is, when asked. */
|
|
302
|
+
fn amount(mean: vec3<f32>, square: f32, count: f32) -> f32 {
|
|
303
|
+
if (params.onlyNoisy == 0u || count <= 1.0) {
|
|
304
|
+
return params.strength;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let m = luminance(mean);
|
|
308
|
+
let error = sqrt(max(square / count - m * m, 0.0) / count) / sqrt(max(m, 0.0) + 1e-3);
|
|
309
|
+
|
|
310
|
+
return params.strength * smoothstep(params.converged, 4.0 * params.converged, error);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Both filtered signals, times the albedos they were divided by, each mixed with what was traced. */
|
|
314
|
+
@compute @workgroup_size(8, 8, 1)
|
|
315
|
+
fn finish(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
316
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let index = id.y * params.size.x + id.x;
|
|
321
|
+
let count = accumulation[index].w;
|
|
322
|
+
|
|
323
|
+
if (count <= 0.0) {
|
|
324
|
+
diffuseOut[index] = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
325
|
+
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let squares = accumulation[params.count + index];
|
|
330
|
+
let diffuse = diffuseMean(index);
|
|
331
|
+
let specular = specularMean(index);
|
|
332
|
+
let filteredDiffuse = diffuseIn[index].rgb * diffuseAlbedo(index);
|
|
333
|
+
let filteredSpecular = specularIn[index].rgb * specularAlbedo(index);
|
|
334
|
+
|
|
335
|
+
diffuseOut[index] = vec4<f32>(
|
|
336
|
+
mix(diffuse, filteredDiffuse, amount(diffuse, squares.z, count))
|
|
337
|
+
+ mix(specular, filteredSpecular, amount(specular, squares.w, count)), 1.0);
|
|
338
|
+
}
|
|
339
|
+
`;
|
|
340
|
+
/** The a-trous passes, their taps 1, 2, 4, 8 and 16 pixels apart. */
|
|
341
|
+
const STEPS = [1, 2, 4, 8, 16];
|
|
342
|
+
/** Uniforms: prepare, anti-firefly, one per a-trous pass, finish. */
|
|
343
|
+
const STAGES = STEPS.length + 3;
|
|
344
|
+
export class DenoisePass {
|
|
345
|
+
device;
|
|
346
|
+
layout;
|
|
347
|
+
prepare;
|
|
348
|
+
antiFirefly;
|
|
349
|
+
atrous;
|
|
350
|
+
finish;
|
|
351
|
+
/** One uniform per stage, so a whole denoise is a single submit. */
|
|
352
|
+
params;
|
|
353
|
+
/** Two ping-pong pairs, diffuse and specular, and the output. */
|
|
354
|
+
buffers = [];
|
|
355
|
+
width = 0;
|
|
356
|
+
height = 0;
|
|
357
|
+
/** The denoised radiance, one vec4 per pixel, once `encode` has run. */
|
|
358
|
+
output = null;
|
|
359
|
+
constructor(device) {
|
|
360
|
+
this.device = device;
|
|
361
|
+
const module = createShaderModule(device, "denoise", DENOISE_WGSL);
|
|
362
|
+
const storage = (type) => ({ type });
|
|
363
|
+
const visibility = GPUShaderStage.COMPUTE;
|
|
364
|
+
this.layout = device.createBindGroupLayout({
|
|
365
|
+
entries: [
|
|
366
|
+
{ binding: 0, visibility, buffer: { type: "uniform" } },
|
|
367
|
+
{ binding: 1, visibility, buffer: storage("read-only-storage") },
|
|
368
|
+
{ binding: 2, visibility, buffer: storage("read-only-storage") },
|
|
369
|
+
{ binding: 3, visibility, buffer: storage("read-only-storage") },
|
|
370
|
+
{ binding: 4, visibility, buffer: storage("storage") },
|
|
371
|
+
{ binding: 5, visibility, buffer: storage("storage") },
|
|
372
|
+
],
|
|
373
|
+
});
|
|
374
|
+
const layout = device.createPipelineLayout({ bindGroupLayouts: [this.layout] });
|
|
375
|
+
const pipeline = (entryPoint) => device.createComputePipeline({ layout, compute: { module, entryPoint } });
|
|
376
|
+
this.prepare = pipeline("prepare");
|
|
377
|
+
this.antiFirefly = pipeline("antiFirefly");
|
|
378
|
+
this.atrous = pipeline("atrous");
|
|
379
|
+
this.finish = pipeline("finish");
|
|
380
|
+
this.params = Array.from({ length: STAGES }, () => device.createBuffer({ size: 48, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }));
|
|
381
|
+
}
|
|
382
|
+
resize(width, height) {
|
|
383
|
+
if (width === this.width && height === this.height && this.buffers.length)
|
|
384
|
+
return;
|
|
385
|
+
for (const buffer of this.buffers)
|
|
386
|
+
buffer.destroy();
|
|
387
|
+
const size = Math.max(16, width * height * 16);
|
|
388
|
+
this.buffers = Array.from({ length: 5 }, () => this.device.createBuffer({ size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }));
|
|
389
|
+
this.output = this.buffers[4];
|
|
390
|
+
this.width = width;
|
|
391
|
+
this.height = height;
|
|
392
|
+
}
|
|
393
|
+
writeParams(width, height, settings, view) {
|
|
394
|
+
const steps = [1, 1, ...STEPS, 1];
|
|
395
|
+
steps.forEach((step, k) => {
|
|
396
|
+
const values = new ArrayBuffer(48);
|
|
397
|
+
const words = new Uint32Array(values);
|
|
398
|
+
const floats = new Float32Array(values);
|
|
399
|
+
words.set([width, height, step, width * height]);
|
|
400
|
+
floats[4] = settings.edge;
|
|
401
|
+
floats[5] = settings.strength;
|
|
402
|
+
words[6] = settings.onlyNoisy ? 1 : 0;
|
|
403
|
+
floats[7] = CONVERGED_ERROR;
|
|
404
|
+
floats[8] = view.pixelScale;
|
|
405
|
+
words[9] = view.orthographic ? 1 : 0;
|
|
406
|
+
this.device.queue.writeBuffer(this.params[k], 0, values);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/** Denoises what `accumulation` holds into `output`. */
|
|
410
|
+
encode(encoder, accumulation, width, height, settings = DEFAULT_DENOISER, view = { pixelScale: 1 / height, orthographic: false }) {
|
|
411
|
+
this.resize(width, height);
|
|
412
|
+
this.writeParams(width, height, settings, view);
|
|
413
|
+
const [diffuseA, diffuseB, specularA, specularB, output] = this.buffers;
|
|
414
|
+
const groups = [Math.ceil(width / 8), Math.ceil(height / 8)];
|
|
415
|
+
const run = (pipeline, params, sources, targets) => {
|
|
416
|
+
const group = this.device.createBindGroup({
|
|
417
|
+
layout: this.layout,
|
|
418
|
+
entries: [
|
|
419
|
+
{ binding: 0, resource: { buffer: params } },
|
|
420
|
+
{ binding: 1, resource: { buffer: accumulation } },
|
|
421
|
+
{ binding: 2, resource: { buffer: sources[0] } },
|
|
422
|
+
{ binding: 3, resource: { buffer: sources[1] } },
|
|
423
|
+
{ binding: 4, resource: { buffer: targets[0] } },
|
|
424
|
+
{ binding: 5, resource: { buffer: targets[1] } },
|
|
425
|
+
],
|
|
426
|
+
});
|
|
427
|
+
const pass = encoder.beginComputePass({ label: "denoise" });
|
|
428
|
+
pass.setPipeline(pipeline);
|
|
429
|
+
pass.setBindGroup(0, group);
|
|
430
|
+
pass.dispatchWorkgroups(groups[0], groups[1]);
|
|
431
|
+
pass.end();
|
|
432
|
+
};
|
|
433
|
+
// prepare reads nothing from its sources; the B buffers stand in so the bindings are valid.
|
|
434
|
+
run(this.prepare, this.params[0], [diffuseB, specularB], [diffuseA, specularA]);
|
|
435
|
+
let from = [diffuseA, specularA];
|
|
436
|
+
let to = [diffuseB, specularB];
|
|
437
|
+
if (settings.antiFirefly) {
|
|
438
|
+
run(this.antiFirefly, this.params[1], from, to);
|
|
439
|
+
[from, to] = [to, from];
|
|
440
|
+
}
|
|
441
|
+
STEPS.slice(0, settings.passes).forEach((_, k) => {
|
|
442
|
+
run(this.atrous, this.params[k + 2], from, to);
|
|
443
|
+
[from, to] = [to, from];
|
|
444
|
+
});
|
|
445
|
+
// finish writes the result to the output; the free specular buffer fills the second, unused, target.
|
|
446
|
+
run(this.finish, this.params[STAGES - 1], from, [output, to[1]]);
|
|
447
|
+
}
|
|
448
|
+
destroy() {
|
|
449
|
+
for (const buffer of [...this.buffers, ...this.params])
|
|
450
|
+
buffer.destroy();
|
|
451
|
+
this.buffers = [];
|
|
452
|
+
this.output = null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=denoise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denoise.js","sourceRoot":"","sources":["../src/denoise.ts"],"names":[],"mappings":"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"}
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Acquiring a device, and the questions a host asks before choosing this renderer at all. */
|
|
2
|
+
/** What the renderer needs from the platform, and what is missing when it cannot run. */
|
|
3
|
+
export interface WebGpuSupport {
|
|
4
|
+
/** True when an adapter answered. */
|
|
5
|
+
supported: boolean;
|
|
6
|
+
/** Why the renderer cannot run here, in one sentence. Empty when `supported`. */
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RenderDevice {
|
|
10
|
+
adapter: GPUAdapter;
|
|
11
|
+
device: GPUDevice;
|
|
12
|
+
/** Largest storage buffer this device accepts, which is the ceiling on how big a scene can be. */
|
|
13
|
+
maxStorageBufferBindingSize: number;
|
|
14
|
+
maxBufferSize: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RenderDeviceOptions {
|
|
17
|
+
/** Called when the device is lost. The renderer stops; choosing what to do next is the host's decision. */
|
|
18
|
+
onLost?: (reason: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether this context exposes WebGPU at all. It is a cheap, synchronous check for a fallback decision;
|
|
22
|
+
* it does not prove that an adapter exists, which only `requestWebGpuSupport` can tell.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isWebGpuAvailable(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Asks the platform for an adapter, which is the only honest answer to "can this machine render?".
|
|
27
|
+
* A browser can expose `navigator.gpu` and still hand back no adapter (a blocklisted driver, a headless session
|
|
28
|
+
* without a software fallback), so hosts deciding between renderers should await this one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function requestWebGpuSupport(): Promise<WebGpuSupport>;
|
|
31
|
+
/**
|
|
32
|
+
* The device the renderer runs on, asking for everything the adapter offers. Scene geometry, the hierarchy and the
|
|
33
|
+
* accumulation buffer all live in storage buffers, so the default 128 MiB binding size is the first ceiling a real
|
|
34
|
+
* scene meets, and adapters routinely allow far more.
|
|
35
|
+
*/
|
|
36
|
+
export declare function requestRenderDevice(options?: RenderDeviceOptions): Promise<RenderDevice>;
|
|
37
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAE9F,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,kGAAkG;IAClG,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,2GAA2G;IAC3G,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAanE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBlG"}
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Acquiring a device, and the questions a host asks before choosing this renderer at all. */
|
|
2
|
+
/**
|
|
3
|
+
* Whether this context exposes WebGPU at all. It is a cheap, synchronous check for a fallback decision;
|
|
4
|
+
* it does not prove that an adapter exists, which only `requestWebGpuSupport` can tell.
|
|
5
|
+
*/
|
|
6
|
+
export function isWebGpuAvailable() {
|
|
7
|
+
return typeof navigator !== "undefined" && "gpu" in navigator && navigator.gpu != null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Asks the platform for an adapter, which is the only honest answer to "can this machine render?".
|
|
11
|
+
* A browser can expose `navigator.gpu` and still hand back no adapter (a blocklisted driver, a headless session
|
|
12
|
+
* without a software fallback), so hosts deciding between renderers should await this one.
|
|
13
|
+
*/
|
|
14
|
+
export async function requestWebGpuSupport() {
|
|
15
|
+
if (!isWebGpuAvailable())
|
|
16
|
+
return { supported: false, reason: "This browser does not expose WebGPU." };
|
|
17
|
+
try {
|
|
18
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
19
|
+
if (!adapter)
|
|
20
|
+
return { supported: false, reason: "WebGPU is exposed, but no adapter was available." };
|
|
21
|
+
return { supported: true, reason: "" };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return { supported: false, reason: `Requesting a WebGPU adapter failed: ${error.message}` };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The device the renderer runs on, asking for everything the adapter offers. Scene geometry, the hierarchy and the
|
|
29
|
+
* accumulation buffer all live in storage buffers, so the default 128 MiB binding size is the first ceiling a real
|
|
30
|
+
* scene meets, and adapters routinely allow far more.
|
|
31
|
+
*/
|
|
32
|
+
export async function requestRenderDevice(options = {}) {
|
|
33
|
+
if (!isWebGpuAvailable())
|
|
34
|
+
throw new Error("GPU renderer: this browser does not expose WebGPU.");
|
|
35
|
+
const adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
|
|
36
|
+
if (!adapter)
|
|
37
|
+
throw new Error("GPU renderer: no WebGPU adapter was available.");
|
|
38
|
+
const device = await adapter.requestDevice({
|
|
39
|
+
requiredLimits: {
|
|
40
|
+
maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,
|
|
41
|
+
maxBufferSize: adapter.limits.maxBufferSize,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
void device.lost.then(info => options.onLost?.(info.message || String(info.reason)));
|
|
45
|
+
return {
|
|
46
|
+
adapter,
|
|
47
|
+
device,
|
|
48
|
+
maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
|
|
49
|
+
maxBufferSize: device.limits.maxBufferSize,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAuB9F;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,SAAS,KAAK,WAAW,IAAI,KAAK,IAAI,SAAS,IAAI,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC;AACzF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,iBAAiB,EAAE;QACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;IAE9E,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAErD,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,kDAAkD,EAAE,CAAC;QAEtG,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACzC,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,uCAAwC,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;IACzG,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,UAA+B,EAAE;IACzE,IAAI,CAAC,iBAAiB,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAExE,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAE5F,IAAI,CAAC,OAAO;QACV,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAEpE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC;QACzC,cAAc,EAAE;YACd,2BAA2B,EAAE,OAAO,CAAC,MAAM,CAAC,2BAA2B;YACvE,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa;SAC5C;KACF,CAAC,CAAC;IAEH,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAErF,OAAO;QACL,OAAO;QACP,MAAM;QACN,2BAA2B,EAAE,MAAM,CAAC,MAAM,CAAC,2BAA2B;QACtE,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many rows of pixels one submit covers. The system watchdog kills a command that runs too long (around two
|
|
3
|
+
* seconds on Windows), and a tab that submits the whole image at once stops answering, so the work is cut into bands
|
|
4
|
+
* and the band grows or shrinks towards a target time per submit.
|
|
5
|
+
*
|
|
6
|
+
* The first band of a new scene is deliberately small: nothing is known about the machine yet, and a slow integrated
|
|
7
|
+
* GPU should discover that on a band it survives.
|
|
8
|
+
*/
|
|
9
|
+
export interface DispatchSchedulerOptions {
|
|
10
|
+
/** Milliseconds one submit should take. */
|
|
11
|
+
targetMs?: number;
|
|
12
|
+
/** Rows the first submit covers, before anything has been measured. */
|
|
13
|
+
initialRows?: number;
|
|
14
|
+
/** Rows in the whole image. */
|
|
15
|
+
maxRows: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class DispatchScheduler {
|
|
18
|
+
private readonly targetMs;
|
|
19
|
+
private readonly initialRows;
|
|
20
|
+
private readonly maxRows;
|
|
21
|
+
private rows;
|
|
22
|
+
/** Rows a millisecond, as far as anything has shown. Zero until the first submit says something. */
|
|
23
|
+
private throughput;
|
|
24
|
+
constructor(options: DispatchSchedulerOptions);
|
|
25
|
+
/** Rows the next submit should cover, never more than what is left. */
|
|
26
|
+
next(remaining: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Feeds back how long the last submit took, which moves the band towards the target time.
|
|
29
|
+
*
|
|
30
|
+
* What it estimates is a rate, not a direction. Every submit also costs a fixed round trip to the device, and on a
|
|
31
|
+
* small or simple image that round trip is most of what the clock sees. A rule that only asked whether the submit
|
|
32
|
+
* came back late would then read every band as too slow and shrink until each one covered a single row, which is the
|
|
33
|
+
* worst possible way to spend that round trip and is a hole it could never climb out of. A rate notices instead that
|
|
34
|
+
* a wider band cost no more, and widens.
|
|
35
|
+
*
|
|
36
|
+
* The rate is measured from the rows actually submitted rather than from the current guess, so one band clamped by
|
|
37
|
+
* what was left of the image does not stop the next one from growing.
|
|
38
|
+
*/
|
|
39
|
+
record(durationMs: number, rows: number): void;
|
|
40
|
+
/** Back to the unmeasured band, for a new scene or a new size. */
|
|
41
|
+
reset(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=dispatchScheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatchScheduler.d.ts","sourceRoot":"","sources":["../src/dispatchScheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,IAAI,CAAS;IACrB,oGAAoG;IACpG,OAAO,CAAC,UAAU,CAAK;gBAEX,OAAO,EAAE,wBAAwB;IAO7C,uEAAuE;IACvE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI/B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAWvC,kEAAkE;IAClE,KAAK;CAIN"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The most one measurement may widen the band, so an unmeasured machine meets the watchdog gradually. */
|
|
2
|
+
const GROW = 4;
|
|
3
|
+
/** How quickly the estimate gives ground when submits come back slower than they used to. */
|
|
4
|
+
const DECAY = 0.8;
|
|
5
|
+
export class DispatchScheduler {
|
|
6
|
+
targetMs;
|
|
7
|
+
initialRows;
|
|
8
|
+
maxRows;
|
|
9
|
+
rows;
|
|
10
|
+
/** Rows a millisecond, as far as anything has shown. Zero until the first submit says something. */
|
|
11
|
+
throughput = 0;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.targetMs = options.targetMs ?? 12;
|
|
14
|
+
this.maxRows = Math.max(1, Math.floor(options.maxRows));
|
|
15
|
+
this.initialRows = Math.max(1, Math.min(this.maxRows, Math.floor(options.initialRows ?? 16)));
|
|
16
|
+
this.rows = this.initialRows;
|
|
17
|
+
}
|
|
18
|
+
/** Rows the next submit should cover, never more than what is left. */
|
|
19
|
+
next(remaining) {
|
|
20
|
+
return Math.max(1, Math.min(Math.floor(this.rows), Math.floor(remaining)));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Feeds back how long the last submit took, which moves the band towards the target time.
|
|
24
|
+
*
|
|
25
|
+
* What it estimates is a rate, not a direction. Every submit also costs a fixed round trip to the device, and on a
|
|
26
|
+
* small or simple image that round trip is most of what the clock sees. A rule that only asked whether the submit
|
|
27
|
+
* came back late would then read every band as too slow and shrink until each one covered a single row, which is the
|
|
28
|
+
* worst possible way to spend that round trip and is a hole it could never climb out of. A rate notices instead that
|
|
29
|
+
* a wider band cost no more, and widens.
|
|
30
|
+
*
|
|
31
|
+
* The rate is measured from the rows actually submitted rather than from the current guess, so one band clamped by
|
|
32
|
+
* what was left of the image does not stop the next one from growing.
|
|
33
|
+
*/
|
|
34
|
+
record(durationMs, rows) {
|
|
35
|
+
if (!Number.isFinite(durationMs) || durationMs <= 0 || rows <= 0)
|
|
36
|
+
return;
|
|
37
|
+
const observed = rows / durationMs;
|
|
38
|
+
// Good news is believed at once, since it can only mean the band was cheaper than the clock suggested. Bad news
|
|
39
|
+
// is taken gradually, so one submit that happened to land behind something else does not collapse the band.
|
|
40
|
+
this.throughput = observed > this.throughput ? observed : this.throughput * DECAY + observed * (1 - DECAY);
|
|
41
|
+
this.rows = Math.max(1, Math.min(this.maxRows, rows * GROW, this.targetMs * this.throughput));
|
|
42
|
+
}
|
|
43
|
+
/** Back to the unmeasured band, for a new scene or a new size. */
|
|
44
|
+
reset() {
|
|
45
|
+
this.rows = this.initialRows;
|
|
46
|
+
this.throughput = 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=dispatchScheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatchScheduler.js","sourceRoot":"","sources":["../src/dispatchScheduler.ts"],"names":[],"mappings":"AAiBA,0GAA0G;AAC1G,MAAM,IAAI,GAAG,CAAC,CAAC;AACf,6FAA6F;AAC7F,MAAM,KAAK,GAAG,GAAG,CAAC;AAElB,MAAM,OAAO,iBAAiB;IACX,QAAQ,CAAS;IACjB,WAAW,CAAS;IACpB,OAAO,CAAS;IACzB,IAAI,CAAS;IACrB,oGAAoG;IAC5F,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,OAAiC;QAC3C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;IAC/B,CAAC;IAED,uEAAuE;IACvE,IAAI,CAAC,SAAiB;QACpB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,UAAkB,EAAE,IAAY;QACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO;QAEzE,MAAM,QAAQ,GAAG,IAAI,GAAG,UAAU,CAAC;QAEnC,gHAAgH;QAChH,4GAA4G;QAC5G,IAAI,CAAC,UAAU,GAAG,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;QAC3G,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAChG,CAAC;IAED,kEAAkE;IAClE,KAAK;QACH,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IACtB,CAAC;CACF"}
|