rayzee 8.3.0 → 8.4.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/README.md +54 -2
- package/dist/DLSSNeural-BNSJ8V3z.js +202 -0
- package/dist/DLSSSuperRes-BoEW89Cl.js +266 -0
- package/dist/ToneMapGPU-DzqSyzTx.js +341 -0
- package/dist/rayzee.es.js +4523 -4577
- package/dist/rayzee.umd.js +260 -66
- package/dist/rolldown-runtime-CAFD8bLK.js +11 -0
- package/package.json +1 -1
- package/src/AssetConfig.js +7 -0
- package/src/EngineDefaults.js +5 -1
- package/src/PathTracerApp.js +6 -4
- package/src/Processor/ToneMapGPU.js +317 -0
- package/src/dlss/DLSSNeural.js +530 -0
- package/src/dlss/DLSSSuperRes.js +584 -0
- package/src/index.js +6 -0
- package/src/managers/DenoisingManager.js +272 -7
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var e = Object.defineProperty, t = (t, n) => {
|
|
3
|
+
let r = {};
|
|
4
|
+
for (var i in t) e(r, i, {
|
|
5
|
+
get: t[i],
|
|
6
|
+
enumerable: !0
|
|
7
|
+
});
|
|
8
|
+
return n || e(r, Symbol.toStringTag, { value: "Module" }), r;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { t };
|
package/package.json
CHANGED
package/src/AssetConfig.js
CHANGED
|
@@ -32,6 +32,13 @@ const config = {
|
|
|
32
32
|
// AI upscaler ONNX model base URL. Quality presets resolve relative paths against this.
|
|
33
33
|
upscalerModelBaseUrl: 'https://huggingface.co/notaneimu/onnx-image-models/resolve/main/',
|
|
34
34
|
|
|
35
|
+
// DLSS runtime bundle. A plain script the host serves, not a module: it installs
|
|
36
|
+
// `globalThis.DLSSRuntime`, so it cannot be imported and the host must publish it somewhere.
|
|
37
|
+
dlssRuntimeUrl: '/dlss/dlss-runtime.js',
|
|
38
|
+
|
|
39
|
+
// Base for the DLSS weight manifests the runtime fetches (`generated/...` beneath this).
|
|
40
|
+
dlssAssetBaseUrl: 'https://pub-6e048ff014374f11bef33d76b6c2b5ef.r2.dev/v1',
|
|
41
|
+
|
|
35
42
|
// Prefix used when the engine writes to client-side stores (IndexedDB, etc).
|
|
36
43
|
// Set to a unique value to avoid collisions when multiple apps embed the engine on the same origin.
|
|
37
44
|
cacheNamespace: 'rayzee',
|
package/src/EngineDefaults.js
CHANGED
|
@@ -1079,7 +1079,11 @@ export const INTERACTIVE_RENDER_CONFIG = {
|
|
|
1079
1079
|
renderMode: ENGINE_DEFAULTS.renderMode, enableAlphaShadows: ENGINE_DEFAULTS.enableAlphaShadows,
|
|
1080
1080
|
transmissiveBounces: ENGINE_DEFAULTS.transmissiveBounces,
|
|
1081
1081
|
maxSubsurfaceSteps: ENGINE_DEFAULTS.maxSubsurfaceSteps,
|
|
1082
|
-
|
|
1082
|
+
// On, like production. The final pass costs one cheap denoise when the preview settles, and both
|
|
1083
|
+
// neural passes need a denoised frame to be worth running — on Monte-Carlo noise the upscaler
|
|
1084
|
+
// measured worse than a plain resize. ⚠️ `ENGINE_DEFAULTS.enableOIDN` stays false: the bench
|
|
1085
|
+
// renders against it and every quality golden would move.
|
|
1086
|
+
enableOIDN: true, oidnQuality: 'fast',
|
|
1083
1087
|
interactionModeEnabled: true,
|
|
1084
1088
|
useAdaptiveSampling: true, // idle refine stops early when converged; frozen during motion
|
|
1085
1089
|
noiseThreshold: 0.1, // loose: preview wants a fast settle, not a clean one
|
package/src/PathTracerApp.js
CHANGED
|
@@ -2463,11 +2463,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2463
2463
|
|
|
2464
2464
|
if ( ! this.renderer?.domElement ) return null;
|
|
2465
2465
|
|
|
2466
|
+
// Whatever is on the overlay is what the viewport shows, so it is also what a save must
|
|
2467
|
+
// write. Gating on `upscaler.enabled` instead missed the neural-rendering pass, which puts
|
|
2468
|
+
// a picture there without the ONNX upscaler being on at all — saves silently wrote the
|
|
2469
|
+
// un-enhanced render.
|
|
2466
2470
|
const dm = this.denoisingManager;
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
if ( upscaled ) return dm.upscalerCanvas;
|
|
2471
|
+
const overlay = dm?.upscalerCanvas;
|
|
2472
|
+
if ( overlay && overlay.style.display !== 'none' ) return overlay;
|
|
2471
2473
|
|
|
2472
2474
|
// A presented WebGPU canvas only reads back what was drawn immediately before, so draw.
|
|
2473
2475
|
// This also puts the denoised picture on it: the Compositor prefers it over the raw render.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine's tone curve in WGSL, and a pass that turns a packed rgba16float buffer into RGBA
|
|
3
|
+
* bytes on the card.
|
|
4
|
+
*
|
|
5
|
+
* A mirror of `ToneMapCPU.js` — same curves, same order (exposure, saturation, curve, sRGB
|
|
6
|
+
* transfer), same rounding. It exists because reading half-floats back and converting them in
|
|
7
|
+
* JavaScript costs more than the neural upscale itself at production sizes. `bench:tonemap`
|
|
8
|
+
* compares the two implementations on a real device and fails on any drift.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ Rounding is deliberately bug-compatible. `toneMapToRGBA8` writes `srgb * 255 + 0.5` into a
|
|
11
|
+
* `Uint8ClampedArray`, which rounds again — so the CPU has always been half a level bright. WGSL's
|
|
12
|
+
* `round()` is round-half-to-even like the clamped array, so `round( srgb * 255 + 0.5 )` reproduces
|
|
13
|
+
* it exactly, including black staying at 0. Dropping the extra half would shift every image against
|
|
14
|
+
* the OIDN and Real-ESRGAN readbacks, which still go through the CPU function.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
NoToneMapping, LinearToneMapping, ReinhardToneMapping,
|
|
19
|
+
CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping
|
|
20
|
+
} from 'three';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* WGSL functions: `rayzee_tone_map( linearRGB, mode, exposure, saturation )` and
|
|
24
|
+
* `rayzee_linear_to_srgb( rgb )`, plus `rayzee_to_u8( channel )`.
|
|
25
|
+
*
|
|
26
|
+
* `mode` is the Three.js ToneMapping constant, interpolated in from the import above so the two
|
|
27
|
+
* sides cannot disagree about which number means which curve.
|
|
28
|
+
*/
|
|
29
|
+
export const TONE_MAP_WGSL = /* wgsl */ `
|
|
30
|
+
const TM_NONE: u32 = ${NoToneMapping}u;
|
|
31
|
+
const TM_LINEAR: u32 = ${LinearToneMapping}u;
|
|
32
|
+
const TM_REINHARD: u32 = ${ReinhardToneMapping}u;
|
|
33
|
+
const TM_CINEON: u32 = ${CineonToneMapping}u;
|
|
34
|
+
const TM_ACES: u32 = ${ACESFilmicToneMapping}u;
|
|
35
|
+
const TM_AGX: u32 = ${AgXToneMapping}u;
|
|
36
|
+
const TM_NEUTRAL: u32 = ${NeutralToneMapping}u;
|
|
37
|
+
|
|
38
|
+
fn tm_reinhard( c: vec3<f32> ) -> vec3<f32> {
|
|
39
|
+
return clamp( c / ( c + vec3<f32>( 1.0 ) ), vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn tm_cineon( c0: vec3<f32> ) -> vec3<f32> {
|
|
43
|
+
let c = max( c0 - vec3<f32>( 0.004 ), vec3<f32>( 0.0 ) );
|
|
44
|
+
let v = ( c * ( 6.2 * c + vec3<f32>( 0.5 ) ) ) / ( c * ( 6.2 * c + vec3<f32>( 1.7 ) ) + vec3<f32>( 0.06 ) );
|
|
45
|
+
return pow( max( v, vec3<f32>( 0.0 ) ), vec3<f32>( 2.2 ) );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn tm_aces( c0: vec3<f32> ) -> vec3<f32> {
|
|
49
|
+
let c = c0 / 0.6;
|
|
50
|
+
let m_in = mat3x3<f32>(
|
|
51
|
+
vec3<f32>( 0.59719, 0.07600, 0.02840 ),
|
|
52
|
+
vec3<f32>( 0.35458, 0.90834, 0.13383 ),
|
|
53
|
+
vec3<f32>( 0.04823, 0.01566, 0.83777 ) );
|
|
54
|
+
let v = m_in * c;
|
|
55
|
+
let a = v * ( v + vec3<f32>( 0.0245786 ) ) - vec3<f32>( 0.000090537 );
|
|
56
|
+
let b = v * ( 0.983729 * v + vec3<f32>( 0.4329510 ) ) + vec3<f32>( 0.238081 );
|
|
57
|
+
let m_out = mat3x3<f32>(
|
|
58
|
+
vec3<f32>( 1.60475, -0.10208, -0.00327 ),
|
|
59
|
+
vec3<f32>( -0.53108, 1.10813, -0.07276 ),
|
|
60
|
+
vec3<f32>( -0.07367, -0.00605, 1.07602 ) );
|
|
61
|
+
return clamp( m_out * ( a / b ), vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn tm_agx( c0: vec3<f32> ) -> vec3<f32> {
|
|
65
|
+
let m_in = mat3x3<f32>(
|
|
66
|
+
vec3<f32>( 0.6274, 0.0691, 0.0164 ),
|
|
67
|
+
vec3<f32>( 0.3293, 0.9195, 0.0880 ),
|
|
68
|
+
vec3<f32>( 0.0433, 0.0113, 0.8956 ) );
|
|
69
|
+
let m_agx = mat3x3<f32>(
|
|
70
|
+
vec3<f32>( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),
|
|
71
|
+
vec3<f32>( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),
|
|
72
|
+
vec3<f32>( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) );
|
|
73
|
+
|
|
74
|
+
var v = m_agx * ( m_in * c0 );
|
|
75
|
+
|
|
76
|
+
let minEv = -12.47393;
|
|
77
|
+
let maxEv = 4.026069;
|
|
78
|
+
v = clamp( ( log2( max( v, vec3<f32>( 1e-10 ) ) ) - vec3<f32>( minEv ) ) / ( maxEv - minEv ),
|
|
79
|
+
vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
80
|
+
|
|
81
|
+
let x2 = v * v;
|
|
82
|
+
let x4 = x2 * x2;
|
|
83
|
+
v = 15.5 * x4 * x2 - 40.14 * x4 * v + 31.96 * x4 - 6.868 * x2 * v + 0.4298 * x2 + 0.1191 * v
|
|
84
|
+
- vec3<f32>( 0.00232 );
|
|
85
|
+
|
|
86
|
+
let m_out = mat3x3<f32>(
|
|
87
|
+
vec3<f32>( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ),
|
|
88
|
+
vec3<f32>( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ),
|
|
89
|
+
vec3<f32>( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) );
|
|
90
|
+
let o = pow( max( m_out * v, vec3<f32>( 0.0 ) ), vec3<f32>( 2.2 ) );
|
|
91
|
+
|
|
92
|
+
let m_srgb = mat3x3<f32>(
|
|
93
|
+
vec3<f32>( 1.6605, -0.1246, -0.0182 ),
|
|
94
|
+
vec3<f32>( -0.5876, 1.1329, -0.1006 ),
|
|
95
|
+
vec3<f32>( -0.0728, -0.0083, 1.1187 ) );
|
|
96
|
+
return clamp( m_srgb * o, vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn tm_neutral( c0: vec3<f32> ) -> vec3<f32> {
|
|
100
|
+
let startCompression = 0.8 - 0.04;
|
|
101
|
+
let desaturation = 0.15;
|
|
102
|
+
|
|
103
|
+
let x = min( c0.r, min( c0.g, c0.b ) );
|
|
104
|
+
var offset = 0.04;
|
|
105
|
+
if ( x < 0.08 ) { offset = x - 6.25 * x * x; }
|
|
106
|
+
var c = c0 - vec3<f32>( offset );
|
|
107
|
+
|
|
108
|
+
let peak = max( c.r, max( c.g, c.b ) );
|
|
109
|
+
if ( peak < startCompression ) { return c; }
|
|
110
|
+
|
|
111
|
+
let d = 1.0 - startCompression;
|
|
112
|
+
let newPeak = 1.0 - d * d / ( peak + d - startCompression );
|
|
113
|
+
c = c * ( newPeak / peak );
|
|
114
|
+
let gFactor = 1.0 - 1.0 / ( desaturation * ( peak - newPeak ) + 1.0 );
|
|
115
|
+
return mix( c, vec3<f32>( newPeak ), gFactor );
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Three.js clamps the fragment output with max(0) before tone mapping, so the curves never see a
|
|
119
|
+
// negative channel. The saturation grade drives channels below zero on much of a typical frame, and
|
|
120
|
+
// AgX/Neutral mix negatives across channels instead of clipping them.
|
|
121
|
+
fn rayzee_tone_curve( color: vec3<f32>, mode: u32 ) -> vec3<f32> {
|
|
122
|
+
let c = max( color, vec3<f32>( 0.0 ) );
|
|
123
|
+
if ( mode == TM_REINHARD ) { return tm_reinhard( c ); }
|
|
124
|
+
if ( mode == TM_CINEON ) { return tm_cineon( c ); }
|
|
125
|
+
if ( mode == TM_ACES ) { return tm_aces( c ); }
|
|
126
|
+
if ( mode == TM_AGX ) { return tm_agx( c ); }
|
|
127
|
+
if ( mode == TM_NEUTRAL ) { return tm_neutral( c ); }
|
|
128
|
+
return clamp( c, vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fn rayzee_tone_map( linearRGB: vec3<f32>, mode: u32, exposure: f32, saturation: f32 ) -> vec3<f32> {
|
|
132
|
+
// Three.js returns early for NoToneMapping without applying exposure, so a readback that applied
|
|
133
|
+
// it would paint brighter than the viewport it replaces.
|
|
134
|
+
var c = linearRGB * select( exposure, 1.0, mode == TM_NONE );
|
|
135
|
+
if ( saturation != 1.0 ) {
|
|
136
|
+
let luma = vec3<f32>( dot( c, vec3<f32>( 0.2126, 0.7152, 0.0722 ) ) );
|
|
137
|
+
c = luma + ( c - luma ) * saturation;
|
|
138
|
+
}
|
|
139
|
+
return rayzee_tone_curve( c, mode );
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn rayzee_linear_to_srgb( c: vec3<f32> ) -> vec3<f32> {
|
|
143
|
+
return select(
|
|
144
|
+
1.055 * pow( max( c, vec3<f32>( 0.0 ) ), vec3<f32>( 1.0 / 2.4 ) ) - vec3<f32>( 0.055 ),
|
|
145
|
+
12.92 * c,
|
|
146
|
+
c <= vec3<f32>( 0.0031308 ) );
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn rayzee_to_u8( srgb: vec3<f32> ) -> vec3<u32> {
|
|
150
|
+
return vec3<u32>( clamp( round( srgb * 255.0 + vec3<f32>( 0.5 ) ), vec3<f32>( 0.0 ), vec3<f32>( 255.0 ) ) );
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
const PACKED_WGSL = /* wgsl */ `
|
|
155
|
+
struct Params {
|
|
156
|
+
width: u32,
|
|
157
|
+
height: u32,
|
|
158
|
+
mode: u32,
|
|
159
|
+
flipY: u32,
|
|
160
|
+
exposure: f32,
|
|
161
|
+
saturation: f32,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
@group(0) @binding(0) var<storage, read> src: array<u32>;
|
|
165
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<u32>;
|
|
166
|
+
@group(0) @binding(2) var<uniform> params: Params;
|
|
167
|
+
|
|
168
|
+
${TONE_MAP_WGSL}
|
|
169
|
+
|
|
170
|
+
@compute @workgroup_size(8, 8)
|
|
171
|
+
fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
172
|
+
if ( gid.x >= params.width || gid.y >= params.height ) { return; }
|
|
173
|
+
|
|
174
|
+
var srcY = gid.y;
|
|
175
|
+
if ( params.flipY == 1u ) { srcY = params.height - 1u - gid.y; }
|
|
176
|
+
|
|
177
|
+
let a = ( srcY * params.width + gid.x ) * 2u;
|
|
178
|
+
let rg = unpack2x16float( src[ a ] );
|
|
179
|
+
let ba = unpack2x16float( src[ a + 1u ] );
|
|
180
|
+
|
|
181
|
+
let mapped = rayzee_tone_map( vec3<f32>( rg.x, rg.y, ba.x ), params.mode, params.exposure, params.saturation );
|
|
182
|
+
let b8 = rayzee_to_u8( rayzee_linear_to_srgb( mapped ) );
|
|
183
|
+
dst[ gid.y * params.width + gid.x ] = b8.x | ( b8.y << 8u ) | ( b8.z << 16u ) | ( 255u << 24u );
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
/** Uniform blocks round up to 16 bytes; the struct above is 24. */
|
|
188
|
+
const PARAMS_BYTES = 32;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Tone-maps a tightly packed rgba16float buffer (two `u32` per pixel) into RGBA bytes.
|
|
192
|
+
*
|
|
193
|
+
* Bound to one device and one image size; `ensureSize()` reallocates when the size changes.
|
|
194
|
+
*/
|
|
195
|
+
export class PackedToneMapper {
|
|
196
|
+
|
|
197
|
+
constructor( device, label = 'rayzee:tonemap' ) {
|
|
198
|
+
|
|
199
|
+
this.device = device;
|
|
200
|
+
this.label = label;
|
|
201
|
+
this.width = 0;
|
|
202
|
+
this.height = 0;
|
|
203
|
+
this._pipeline = null;
|
|
204
|
+
this._storage = null;
|
|
205
|
+
this._map = null;
|
|
206
|
+
this._params = null;
|
|
207
|
+
this._paramData = new ArrayBuffer( PARAMS_BYTES );
|
|
208
|
+
this._paramU32 = new Uint32Array( this._paramData );
|
|
209
|
+
this._paramF32 = new Float32Array( this._paramData );
|
|
210
|
+
this.disposed = false;
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
ensureSize( width, height ) {
|
|
215
|
+
|
|
216
|
+
if ( this.width === width && this.height === height && this._pipeline ) return;
|
|
217
|
+
|
|
218
|
+
this._releaseBuffers();
|
|
219
|
+
|
|
220
|
+
const bytes = width * height * 4;
|
|
221
|
+
this._storage = this.device.createBuffer( {
|
|
222
|
+
label: `${this.label}-out`,
|
|
223
|
+
size: bytes,
|
|
224
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
|
225
|
+
} );
|
|
226
|
+
this._map = this.device.createBuffer( {
|
|
227
|
+
label: `${this.label}-map`,
|
|
228
|
+
size: bytes,
|
|
229
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
230
|
+
} );
|
|
231
|
+
this._params = this.device.createBuffer( {
|
|
232
|
+
label: `${this.label}-params`,
|
|
233
|
+
size: PARAMS_BYTES,
|
|
234
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
235
|
+
} );
|
|
236
|
+
|
|
237
|
+
this._pipeline ??= this.device.createComputePipeline( {
|
|
238
|
+
label: this.label,
|
|
239
|
+
layout: 'auto',
|
|
240
|
+
compute: { module: this.device.createShaderModule( { label: this.label, code: PACKED_WGSL } ), entryPoint: 'main' },
|
|
241
|
+
} );
|
|
242
|
+
|
|
243
|
+
this.width = width;
|
|
244
|
+
this.height = height;
|
|
245
|
+
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param {GPUBuffer} src packed rgba16float, `width * height * 8` bytes, STORAGE-capable
|
|
250
|
+
* @param {object} tone
|
|
251
|
+
* @param {number} tone.exposure raw `renderer.toneMappingExposure`
|
|
252
|
+
* @param {number} tone.toneMapping Three.js ToneMapping constant
|
|
253
|
+
* @param {number} [tone.saturation=1]
|
|
254
|
+
* @param {boolean} [tone.flipY=false]
|
|
255
|
+
* @returns {Promise<Uint8ClampedArray>} RGBA bytes, `width * height * 4`
|
|
256
|
+
*/
|
|
257
|
+
async toRGBA8( src, { exposure = 1, toneMapping = 0, saturation = 1, flipY = false } = {} ) {
|
|
258
|
+
|
|
259
|
+
if ( this.disposed ) throw new Error( 'PackedToneMapper: disposed' );
|
|
260
|
+
if ( ! this._pipeline ) throw new Error( 'PackedToneMapper: call ensureSize() first' );
|
|
261
|
+
|
|
262
|
+
const { width, height } = this;
|
|
263
|
+
|
|
264
|
+
this._paramU32[ 0 ] = width;
|
|
265
|
+
this._paramU32[ 1 ] = height;
|
|
266
|
+
this._paramU32[ 2 ] = toneMapping >>> 0;
|
|
267
|
+
this._paramU32[ 3 ] = flipY ? 1 : 0;
|
|
268
|
+
this._paramF32[ 4 ] = exposure;
|
|
269
|
+
this._paramF32[ 5 ] = saturation;
|
|
270
|
+
this.device.queue.writeBuffer( this._params, 0, this._paramData );
|
|
271
|
+
|
|
272
|
+
const group = this.device.createBindGroup( {
|
|
273
|
+
layout: this._pipeline.getBindGroupLayout( 0 ),
|
|
274
|
+
entries: [
|
|
275
|
+
{ binding: 0, resource: { buffer: src, size: width * height * 8 } },
|
|
276
|
+
{ binding: 1, resource: { buffer: this._storage } },
|
|
277
|
+
{ binding: 2, resource: { buffer: this._params } },
|
|
278
|
+
],
|
|
279
|
+
} );
|
|
280
|
+
|
|
281
|
+
const encoder = this.device.createCommandEncoder( { label: this.label } );
|
|
282
|
+
const pass = encoder.beginComputePass();
|
|
283
|
+
pass.setPipeline( this._pipeline );
|
|
284
|
+
pass.setBindGroup( 0, group );
|
|
285
|
+
pass.dispatchWorkgroups( Math.ceil( width / 8 ), Math.ceil( height / 8 ) );
|
|
286
|
+
pass.end();
|
|
287
|
+
encoder.copyBufferToBuffer( this._storage, 0, this._map, 0, width * height * 4 );
|
|
288
|
+
this.device.queue.submit( [ encoder.finish() ] );
|
|
289
|
+
|
|
290
|
+
await this._map.mapAsync( GPUMapMode.READ );
|
|
291
|
+
const bytes = new Uint8ClampedArray( this._map.getMappedRange().slice( 0 ) );
|
|
292
|
+
this._map.unmap();
|
|
293
|
+
return bytes;
|
|
294
|
+
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_releaseBuffers() {
|
|
298
|
+
|
|
299
|
+
this._storage?.destroy();
|
|
300
|
+
this._map?.destroy();
|
|
301
|
+
this._params?.destroy();
|
|
302
|
+
this._storage = null;
|
|
303
|
+
this._map = null;
|
|
304
|
+
this._params = null;
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
dispose() {
|
|
309
|
+
|
|
310
|
+
if ( this.disposed ) return;
|
|
311
|
+
this.disposed = true;
|
|
312
|
+
this._releaseBuffers();
|
|
313
|
+
this._pipeline = null;
|
|
314
|
+
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
}
|