1use pfx_gpu::wgpu;
2
3pub const CYCLE: u32 = 8;
4pub const COLOUR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
5pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Float;
6pub const MOTION_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg16Float;
7pub const REACTIVE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
8pub const ID_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint;
9pub const NORMAL_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Snorm;
10
11const HISTORY_VALID: u32 = 1;
12const HAS_ID: u32 = 2;
13const HAS_NORMAL: u32 = 4;
14const WORKGROUP: u32 = 8;
15
16const OFFSETS: [[f32; 2]; CYCLE as usize] = offsets();
17
18const fn halton_f64(mut index: u32, base: u32) -> f64 {
19 let mut fraction = 1.0f64;
20 let mut result = 0.0f64;
21 let step = base as f64;
22 while index > 0 {
23 fraction /= step;
24 result += fraction * (index % base) as f64;
25 index /= base;
26 }
27 result
28}
29
30const fn offsets() -> [[f32; 2]; CYCLE as usize] {
31 let mut raw = [(0.0f64, 0.0f64); CYCLE as usize];
32 let mut sum_x = 0.0f64;
33 let mut sum_y = 0.0f64;
34 let mut index = 0u32;
35 while index < CYCLE {
36 let x = halton_f64(index, 2);
37 let y = halton_f64(index, 3);
38 raw[index as usize] = (x, y);
39 sum_x += x;
40 sum_y += y;
41 index += 1;
42 }
43 let mut out = [[0.0f32; 2]; CYCLE as usize];
44 index = 0;
45 while index < CYCLE {
46 out[index as usize] = [
47 (raw[index as usize].0 - sum_x / CYCLE as f64) as f32,
48 (raw[index as usize].1 - sum_y / CYCLE as f64) as f32,
49 ];
50 index += 1;
51 }
52 out
53}
54
55pub fn halton(index: u32, base: u32) -> f32 {
56 if base < 2 {
57 return 0.0;
58 }
59 halton_f64(index, base) as f32
60}
61
62pub fn jitter_pixels(frame: u32) -> [f32; 2] {
63 OFFSETS[(frame % CYCLE) as usize]
64}
65
66pub fn jitter_clip(frame: u32, width: u32, height: u32) -> [f32; 2] {
67 if width == 0 || height == 0 {
68 return [0.0, 0.0];
69 }
70 let pixels = jitter_pixels(frame);
71 [
72 pixels[0] * 2.0 / width as f32,
73 pixels[1] * 2.0 / height as f32,
74 ]
75}
76
77#[derive(Clone, Copy, Debug, PartialEq)]
78pub struct Settings {
79 pub history_weight: f32,
80 pub variance_gamma: f32,
81 pub depth_threshold: f32,
82 pub normal_min_dot: f32,
83}
84
85impl Default for Settings {
86 fn default() -> Self {
87 Self {
88 history_weight: 1.0 - 1.0 / CYCLE as f32,
89 variance_gamma: 1.0,
90 depth_threshold: 0.1,
91 normal_min_dot: 0.9,
92 }
93 }
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub struct Inputs {
98 pub width: u32,
99 pub height: u32,
100 pub colour: Vec<[f32; 3]>,
101 pub depth: Vec<f32>,
102 pub motion: Vec<[f32; 2]>,
103 pub reactive: Vec<f32>,
104 pub id: Option<Vec<u32>>,
105 pub normal: Option<Vec<[f32; 3]>>,
106 pub exposure: f32,
107}
108
109#[derive(Clone, Debug, PartialEq)]
110pub struct History {
111 pub width: u32,
112 pub height: u32,
113 pub rgba: Vec<[f32; 4]>,
114 pub depth: Vec<f32>,
115 pub id: Vec<u32>,
116 pub normal: Vec<[f32; 3]>,
117 pub exposure: f32,
118 pub valid: bool,
119}
120
121impl History {
122 pub fn empty() -> Self {
123 Self {
124 width: 0,
125 height: 0,
126 rgba: Vec::new(),
127 depth: Vec::new(),
128 id: Vec::new(),
129 normal: Vec::new(),
130 exposure: 1.0,
131 valid: false,
132 }
133 }
134}
135
136#[derive(Clone, Debug, PartialEq)]
137pub struct Output {
138 pub colour: Vec<[f32; 3]>,
139 pub history: History,
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum Error {
144 Size,
145 Length,
146 Exposure,
147 Settings,
148}
149
150pub const RESOLVE_WGSL: &str = r#"
151struct Uniforms {
152 width: u32,
153 height: u32,
154 flags: u32,
155 exposure: f32,
156 history_exposure: f32,
157 depth_threshold: f32,
158 normal_min_dot: f32,
159 variance_gamma: f32,
160 history_weight: f32,
161 history_width: u32,
162 history_height: u32,
163}
164
165@group(0) @binding(0) var<uniform> u: Uniforms;
166@group(0) @binding(1) var colour_tex: texture_2d<f32>;
167@group(0) @binding(2) var depth_tex: texture_2d<f32>;
168@group(0) @binding(3) var motion_tex: texture_2d<f32>;
169@group(0) @binding(4) var id_tex: texture_2d<u32>;
170@group(0) @binding(5) var normal_tex: texture_2d<f32>;
171@group(0) @binding(6) var reactive_tex: texture_2d<f32>;
172@group(0) @binding(7) var history_colour: texture_2d<f32>;
173@group(0) @binding(8) var history_depth: texture_2d<f32>;
174@group(0) @binding(9) var history_id: texture_2d<u32>;
175@group(0) @binding(10) var history_normal: texture_2d<f32>;
176@group(0) @binding(11) var out_colour: texture_storage_2d<rgba16float, write>;
177@group(0) @binding(12) var out_depth: texture_storage_2d<r32float, write>;
178@group(0) @binding(13) var out_id: texture_storage_2d<r32uint, write>;
179@group(0) @binding(14) var out_normal: texture_storage_2d<rgba8snorm, write>;
180
181fn rgb_to_ycocg(c: vec3<f32>) -> vec3<f32> {
182 return vec3<f32>(
183 0.25 * c.r + 0.5 * c.g + 0.25 * c.b,
184 0.5 * c.r - 0.5 * c.b,
185 -0.25 * c.r + 0.5 * c.g - 0.25 * c.b,
186 );
187}
188
189fn ycocg_to_rgb(c: vec3<f32>) -> vec3<f32> {
190 return vec3<f32>(c.x + c.y - c.z, c.x + c.z, c.x - c.y - c.z);
191}
192
193fn outside(uv: vec2<f32>) -> bool {
194 return uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0;
195}
196
197fn clamp_pixel(p: vec2<i32>, size: vec2<i32>) -> vec2<i32> {
198 return clamp(p, vec2<i32>(0), size - vec2<i32>(1));
199}
200
201fn load3(tex: texture_2d<f32>, p: vec2<i32>, size: vec2<i32>) -> vec3<f32> {
202 return textureLoad(tex, clamp_pixel(p, size), 0).rgb;
203}
204
205fn bilinear3(tex: texture_2d<f32>, uv: vec2<f32>, size: vec2<u32>) -> vec3<f32> {
206 let s = vec2<i32>(size);
207 let pf = uv * vec2<f32>(size) - vec2<f32>(0.5);
208 let base = vec2<i32>(floor(pf));
209 let f = pf - floor(pf);
210 let c00 = load3(tex, base, s);
211 let c10 = load3(tex, base + vec2<i32>(1, 0), s);
212 let c01 = load3(tex, base + vec2<i32>(0, 1), s);
213 let c11 = load3(tex, base + vec2<i32>(1, 1), s);
214 return mix(mix(c00, c10, f.x), mix(c01, c11, f.x), f.y);
215}
216
217fn nearest(uv: vec2<f32>, size: vec2<u32>) -> vec2<i32> {
218 return clamp_pixel(vec2<i32>(floor(uv * vec2<f32>(size))), vec2<i32>(size));
219}
220
221fn clip_aabb(lo: vec3<f32>, hi: vec3<f32>, p: vec3<f32>) -> vec3<f32> {
222 let center = 0.5 * (hi + lo);
223 let extent = max(0.5 * (hi - lo), vec3<f32>(1.0e-8));
224 let delta = p - center;
225 let unit = delta / extent;
226 let m = max(abs(unit.x), max(abs(unit.y), abs(unit.z)));
227 if (m > 1.0) {
228 return center + delta / m;
229 }
230 return p;
231}
232
233@compute @workgroup_size(8, 8)
234fn resolve(@builtin(global_invocation_id) gid: vec3<u32>) {
235 let size = vec2<u32>(u.width, u.height);
236 if (gid.x >= size.x || gid.y >= size.y) {
237 return;
238 }
239 let pix = vec2<i32>(gid.xy);
240 let uv = (vec2<f32>(gid.xy) + vec2<f32>(0.5)) / vec2<f32>(size);
241 let current = textureLoad(colour_tex, pix, 0).rgb;
242 let depth = textureLoad(depth_tex, pix, 0).r;
243 let motion = textureLoad(motion_tex, pix, 0).rg;
244 let reactive = clamp(textureLoad(reactive_tex, pix, 0).r, 0.0, 1.0);
245 var pixel_id = 0u;
246 if ((u.flags & 2u) != 0u) {
247 pixel_id = textureLoad(id_tex, pix, 0).r;
248 }
249 var nrm = vec3<f32>(0.0);
250 if ((u.flags & 4u) != 0u) {
251 nrm = textureLoad(normal_tex, pix, 0).rgb;
252 }
253
254 var acc = vec3<f32>(0.0);
255 var acc2 = vec3<f32>(0.0);
256 let s = vec2<i32>(size);
257 for (var oy = -1; oy <= 1; oy = oy + 1) {
258 for (var ox = -1; ox <= 1; ox = ox + 1) {
259 let texel = load3(colour_tex, pix + vec2<i32>(ox, oy), s) * u.exposure;
260 let ycocg = rgb_to_ycocg(texel);
261 acc = acc + ycocg;
262 acc2 = acc2 + ycocg * ycocg;
263 }
264 }
265 let mean = acc / 9.0;
266 let sigma = sqrt(max(acc2 / 9.0 - mean * mean, vec3<f32>(0.0))) * u.variance_gamma;
267 let lo = mean - sigma;
268 let hi = mean + sigma;
269 let current_e = current * u.exposure;
270 var history_e = current_e;
271 var weight = 0.0;
272 if ((u.flags & 1u) != 0u) {
273 let prev = uv + motion;
274 if (!outside(prev)) {
275 let stored = max(u.history_exposure, 1.0e-8);
276 let history_size = vec2<u32>(u.history_width, u.history_height);
277 let history_hdr = bilinear3(history_colour, prev, history_size);
278 history_e = (history_hdr * stored) * (u.exposure / stored);
279 var accept = true;
280 let at = nearest(prev, history_size);
281 let previous_depth = textureLoad(history_depth, at, 0).r;
282 let scale = max(max(abs(depth), abs(previous_depth)), 1.0e-5);
283 if (abs(depth - previous_depth) / scale > u.depth_threshold) {
284 accept = false;
285 }
286 if ((u.flags & 2u) != 0u) {
287 let previous_id = textureLoad(history_id, at, 0).r;
288 if (previous_id != pixel_id) {
289 accept = false;
290 }
291 }
292 if ((u.flags & 4u) != 0u) {
293 let previous_normal = textureLoad(history_normal, at, 0).rgb;
294 let current_len2 = dot(nrm, nrm);
295 let previous_len2 = dot(previous_normal, previous_normal);
296 if (current_len2 > 1.0e-12 && previous_len2 > 1.0e-12) {
297 let aligned = dot(nrm, previous_normal) * inverseSqrt(current_len2 * previous_len2);
298 if (aligned < u.normal_min_dot) {
299 accept = false;
300 }
301 }
302 }
303 if (accept) {
304 history_e = ycocg_to_rgb(clip_aabb(lo, hi, rgb_to_ycocg(history_e)));
305 weight = u.history_weight * (1.0 - reactive);
306 }
307 }
308 }
309 let resolved_e = history_e * weight + current_e * (1.0 - weight);
310 let resolved = resolved_e / u.exposure;
311 textureStore(out_colour, pix, vec4<f32>(resolved, u.exposure));
312 textureStore(out_depth, pix, vec4<f32>(depth, 0.0, 0.0, 0.0));
313 textureStore(out_id, pix, vec4<u32>(pixel_id, 0u, 0u, 0u));
314 textureStore(out_normal, pix, vec4<f32>(nrm, 0.0));
315}
316"#;
317
318fn finite_settings(settings: &Settings) -> Result<(f32, f32, f32, f32), Error> {
319 if !settings.history_weight.is_finite()
320 || !settings.variance_gamma.is_finite()
321 || !settings.depth_threshold.is_finite()
322 || !settings.normal_min_dot.is_finite()
323 {
324 return Err(Error::Settings);
325 }
326 Ok((
327 settings.history_weight.clamp(0.0, 1.0),
328 settings.variance_gamma.max(0.0),
329 settings.depth_threshold.max(0.0),
330 settings.normal_min_dot,
331 ))
332}
333
334fn pixel_count(width: u32, height: u32) -> Result<usize, Error> {
335 usize::try_from(u64::from(width) * u64::from(height)).map_err(|_| Error::Size)
336}
337
338fn rgb_to_ycocg(c: [f32; 3]) -> [f32; 3] {
339 [
340 0.25 * c[0] + 0.5 * c[1] + 0.25 * c[2],
341 0.5 * c[0] - 0.5 * c[2],
342 -0.25 * c[0] + 0.5 * c[1] - 0.25 * c[2],
343 ]
344}
345
346fn ycocg_to_rgb(c: [f32; 3]) -> [f32; 3] {
347 [c[0] + c[1] - c[2], c[0] + c[2], c[0] - c[1] - c[2]]
348}
349
350fn outside(uv: [f32; 2]) -> bool {
351 uv[0] < 0.0 || uv[1] < 0.0 || uv[0] >= 1.0 || uv[1] >= 1.0
352}
353
354fn clamp_index(value: i32, size: i32) -> i32 {
355 value.clamp(0, size - 1)
356}
357
358fn load3(tex: &[[f32; 3]], width: i32, height: i32, x: i32, y: i32) -> [f32; 3] {
359 let x = clamp_index(x, width);
360 let y = clamp_index(y, height);
361 tex[(y as u32 * width as u32 + x as u32) as usize]
362}
363
364fn bilinear3(tex: &[[f32; 3]], width: u32, height: u32, uv: [f32; 2]) -> [f32; 3] {
365 let w = width as i32;
366 let h = height as i32;
367 let px = uv[0] * width as f32 - 0.5;
368 let py = uv[1] * height as f32 - 0.5;
369 let x0 = px.floor();
370 let y0 = py.floor();
371 let fx = px - x0;
372 let fy = py - y0;
373 let x0 = x0 as i32;
374 let y0 = y0 as i32;
375 let c00 = load3(tex, w, h, x0, y0);
376 let c10 = load3(tex, w, h, x0 + 1, y0);
377 let c01 = load3(tex, w, h, x0, y0 + 1);
378 let c11 = load3(tex, w, h, x0 + 1, y0 + 1);
379 let mut top = [0.0; 3];
380 let mut bottom = [0.0; 3];
381 let mut out = [0.0; 3];
382 for channel in 0..3 {
383 top[channel] = c00[channel] * (1.0 - fx) + c10[channel] * fx;
384 bottom[channel] = c01[channel] * (1.0 - fx) + c11[channel] * fx;
385 out[channel] = top[channel] * (1.0 - fy) + bottom[channel] * fy;
386 }
387 out
388}
389
390fn nearest(uv: f32, size: u32) -> i32 {
391 clamp_index((uv * size as f32).floor() as i32, size as i32)
392}
393
394fn clip_aabb(lo: [f32; 3], hi: [f32; 3], p: [f32; 3]) -> [f32; 3] {
395 let mut center = [0.0; 3];
396 let mut extent = [0.0; 3];
397 let mut delta = [0.0; 3];
398 let mut unit = [0.0; 3];
399 for channel in 0..3 {
400 center[channel] = 0.5 * (hi[channel] + lo[channel]);
401 extent[channel] = (0.5 * (hi[channel] - lo[channel])).max(1.0e-8);
402 delta[channel] = p[channel] - center[channel];
403 unit[channel] = delta[channel] / extent[channel];
404 }
405 let m = unit[0].abs().max(unit[1].abs()).max(unit[2].abs());
406 if m > 1.0 {
407 [
408 center[0] + delta[0] / m,
409 center[1] + delta[1] / m,
410 center[2] + delta[2] / m,
411 ]
412 } else {
413 p
414 }
415}
416
417fn depth_rejects(current: f32, previous: f32, threshold: f32) -> bool {
418 let scale = current.abs().max(previous.abs()).max(1.0e-5);
419 (current - previous).abs() / scale > threshold
420}
421
422fn normal_rejects(current: [f32; 3], previous: [f32; 3], min_dot: f32) -> bool {
423 let current_len =
424 (current[0] * current[0] + current[1] * current[1] + current[2] * current[2]).sqrt();
425 let previous_len =
426 (previous[0] * previous[0] + previous[1] * previous[1] + previous[2] * previous[2]).sqrt();
427 if current_len <= 1.0e-6 || previous_len <= 1.0e-6 {
428 return false;
429 }
430 let aligned = (current[0] / current_len) * (previous[0] / previous_len)
431 + (current[1] / current_len) * (previous[1] / previous_len)
432 + (current[2] / current_len) * (previous[2] / previous_len);
433 aligned < min_dot
434}
435
436pub fn resolve(inputs: &Inputs, history: &History, settings: &Settings) -> Result<Output, Error> {
437 let (history_weight, variance_gamma, depth_threshold, normal_min_dot) =
438 finite_settings(settings)?;
439 if !inputs.exposure.is_finite() || inputs.exposure <= 0.0 {
440 return Err(Error::Exposure);
441 }
442 let n = pixel_count(inputs.width, inputs.height)?;
443 if inputs.width == 0 || inputs.height == 0 {
444 return Ok(Output {
445 colour: Vec::new(),
446 history: History::empty(),
447 });
448 }
449 if inputs.colour.len() != n
450 || inputs.depth.len() != n
451 || inputs.motion.len() != n
452 || inputs.reactive.len() != n
453 || inputs.id.as_ref().is_some_and(|id| id.len() != n)
454 || inputs
455 .normal
456 .as_ref()
457 .is_some_and(|normal| normal.len() != n)
458 {
459 return Err(Error::Length);
460 }
461 let stored = pixel_count(history.width, history.height)?;
462 let use_history = history.valid
463 && history.width > 0
464 && history.height > 0
465 && history.rgba.len() == stored
466 && history.depth.len() == stored
467 && history.id.len() == stored
468 && history.normal.len() == stored
469 && history.exposure.is_finite()
470 && history.exposure > 0.0;
471 let stored_exposure = if use_history {
472 history.exposure.max(1.0e-8)
473 } else {
474 1.0
475 };
476 let history_hdr: Vec<[f32; 3]> = if use_history {
477 history
478 .rgba
479 .iter()
480 .map(|pixel| [pixel[0], pixel[1], pixel[2]])
481 .collect()
482 } else {
483 Vec::new()
484 };
485 let width = inputs.width as i32;
486 let height = inputs.height as i32;
487 let mut colour = Vec::with_capacity(n);
488 let mut rgba = Vec::with_capacity(n);
489 let mut depth_out = Vec::with_capacity(n);
490 let mut id_out = Vec::with_capacity(n);
491 let mut normal_out = Vec::with_capacity(n);
492 for y in 0..inputs.height {
493 for x in 0..inputs.width {
494 let index = (y * inputs.width + x) as usize;
495 let uv = [
496 (x as f32 + 0.5) / inputs.width as f32,
497 (y as f32 + 0.5) / inputs.height as f32,
498 ];
499 let current = inputs.colour[index];
500 let depth = inputs.depth[index];
501 let motion = inputs.motion[index];
502 let reactive = inputs.reactive[index].clamp(0.0, 1.0);
503 let pixel_id = inputs.id.as_ref().map(|id| id[index]).unwrap_or(0);
504 let nrm = inputs
505 .normal
506 .as_ref()
507 .map(|normal| normal[index])
508 .unwrap_or([0.0; 3]);
509 let mut acc = [0.0; 3];
510 let mut acc2 = [0.0; 3];
511 for oy in -1..=1 {
512 for ox in -1..=1 {
513 let texel = load3(&inputs.colour, width, height, x as i32 + ox, y as i32 + oy);
514 let exposed = [
515 texel[0] * inputs.exposure,
516 texel[1] * inputs.exposure,
517 texel[2] * inputs.exposure,
518 ];
519 let ycocg = rgb_to_ycocg(exposed);
520 for channel in 0..3 {
521 acc[channel] += ycocg[channel];
522 acc2[channel] += ycocg[channel] * ycocg[channel];
523 }
524 }
525 }
526 let mut mean = [0.0; 3];
527 let mut lo = [0.0; 3];
528 let mut hi = [0.0; 3];
529 for channel in 0..3 {
530 mean[channel] = acc[channel] / 9.0;
531 let variance = (acc2[channel] / 9.0 - mean[channel] * mean[channel]).max(0.0);
532 let sigma = variance.sqrt() * variance_gamma;
533 lo[channel] = mean[channel] - sigma;
534 hi[channel] = mean[channel] + sigma;
535 }
536 let current_e = [
537 current[0] * inputs.exposure,
538 current[1] * inputs.exposure,
539 current[2] * inputs.exposure,
540 ];
541 let mut history_e = current_e;
542 let mut weight = 0.0;
543 if use_history {
544 let prev = [uv[0] + motion[0], uv[1] + motion[1]];
545 if !outside(prev) {
546 let history_hdr = bilinear3(&history_hdr, history.width, history.height, prev);
547 history_e = [
548 (history_hdr[0] * stored_exposure) * (inputs.exposure / stored_exposure),
549 (history_hdr[1] * stored_exposure) * (inputs.exposure / stored_exposure),
550 (history_hdr[2] * stored_exposure) * (inputs.exposure / stored_exposure),
551 ];
552 let at_x = nearest(prev[0], history.width);
553 let at_y = nearest(prev[1], history.height);
554 let at = (at_y as u32 * history.width + at_x as u32) as usize;
555 let mut accept = !depth_rejects(depth, history.depth[at], depth_threshold);
556 if accept && inputs.id.is_some() && history.id[at] != pixel_id {
557 accept = false;
558 }
559 if accept
560 && inputs.normal.is_some()
561 && normal_rejects(nrm, history.normal[at], normal_min_dot)
562 {
563 accept = false;
564 }
565 if accept {
566 history_e = ycocg_to_rgb(clip_aabb(lo, hi, rgb_to_ycocg(history_e)));
567 weight = history_weight * (1.0 - reactive);
568 }
569 }
570 }
571 let resolved_e = [
572 history_e[0] * weight + current_e[0] * (1.0 - weight),
573 history_e[1] * weight + current_e[1] * (1.0 - weight),
574 history_e[2] * weight + current_e[2] * (1.0 - weight),
575 ];
576 let resolved = [
577 resolved_e[0] / inputs.exposure,
578 resolved_e[1] / inputs.exposure,
579 resolved_e[2] / inputs.exposure,
580 ];
581 colour.push(resolved);
582 rgba.push([resolved[0], resolved[1], resolved[2], inputs.exposure]);
583 depth_out.push(depth);
584 id_out.push(pixel_id);
585 normal_out.push(nrm);
586 }
587 }
588 Ok(Output {
589 colour,
590 history: History {
591 width: inputs.width,
592 height: inputs.height,
593 rgba,
594 depth: depth_out,
595 id: id_out,
596 normal: normal_out,
597 exposure: inputs.exposure,
598 valid: true,
599 },
600 })
601}
602
603struct Slot {
604 colour: wgpu::Texture,
605 colour_view: wgpu::TextureView,
606 depth_view: wgpu::TextureView,
607 id_view: wgpu::TextureView,
608 normal_view: wgpu::TextureView,
609 textures: [wgpu::Texture; 3],
610}
611
612pub struct GpuInputs<'a> {
613 pub colour: &'a wgpu::TextureView,
614 pub depth: &'a wgpu::TextureView,
615 pub motion: &'a wgpu::TextureView,
616 pub reactive: &'a wgpu::TextureView,
617 pub id: Option<&'a wgpu::TextureView>,
618 pub normal: Option<&'a wgpu::TextureView>,
619 pub exposure: f32,
620}
621
622pub struct Pass {
623 layout: wgpu::BindGroupLayout,
624 pipeline: wgpu::ComputePipeline,
625 uniform: wgpu::Buffer,
626 dummy_id: wgpu::Texture,
627 dummy_id_view: wgpu::TextureView,
628 dummy_normal: wgpu::Texture,
629 dummy_normal_view: wgpu::TextureView,
630 slots: [Option<Slot>; 2],
631 source: usize,
632 width: u32,
633 height: u32,
634 viewport: (u32, u32),
635 history_size: (u32, u32),
636 restarts: u64,
637 exposure: f32,
638 valid: bool,
639 dummies_ready: bool,
640}
641
642fn texture(
643 device: &wgpu::Device,
644 label: &'static str,
645 width: u32,
646 height: u32,
647 format: wgpu::TextureFormat,
648 usage: wgpu::TextureUsages,
649) -> (wgpu::Texture, wgpu::TextureView) {
650 let texture = device.create_texture(&wgpu::TextureDescriptor {
651 label: Some(label),
652 size: wgpu::Extent3d {
653 width,
654 height,
655 depth_or_array_layers: 1,
656 },
657 mip_level_count: 1,
658 sample_count: 1,
659 dimension: wgpu::TextureDimension::D2,
660 format,
661 usage,
662 view_formats: &[],
663 });
664 let view = texture.create_view(&Default::default());
665 (texture, view)
666}
667
668fn float_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
669 wgpu::BindGroupLayoutEntry {
670 binding,
671 visibility: wgpu::ShaderStages::COMPUTE,
672 ty: wgpu::BindingType::Texture {
673 sample_type: wgpu::TextureSampleType::Float { filterable: false },
674 view_dimension: wgpu::TextureViewDimension::D2,
675 multisampled: false,
676 },
677 count: None,
678 }
679}
680
681fn uint_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
682 wgpu::BindGroupLayoutEntry {
683 binding,
684 visibility: wgpu::ShaderStages::COMPUTE,
685 ty: wgpu::BindingType::Texture {
686 sample_type: wgpu::TextureSampleType::Uint,
687 view_dimension: wgpu::TextureViewDimension::D2,
688 multisampled: false,
689 },
690 count: None,
691 }
692}
693
694fn storage_entry(binding: u32, format: wgpu::TextureFormat) -> wgpu::BindGroupLayoutEntry {
695 wgpu::BindGroupLayoutEntry {
696 binding,
697 visibility: wgpu::ShaderStages::COMPUTE,
698 ty: wgpu::BindingType::StorageTexture {
699 access: wgpu::StorageTextureAccess::WriteOnly,
700 format,
701 view_dimension: wgpu::TextureViewDimension::D2,
702 },
703 count: None,
704 }
705}
706
707fn history_slot(device: &wgpu::Device, width: u32, height: u32, index: usize) -> Slot {
708 let usage = wgpu::TextureUsages::TEXTURE_BINDING
709 | wgpu::TextureUsages::STORAGE_BINDING
710 | wgpu::TextureUsages::COPY_SRC;
711 let (colour, colour_view) = texture(
712 device,
713 if index == 0 {
714 "taa history colour 0"
715 } else {
716 "taa history colour 1"
717 },
718 width,
719 height,
720 COLOUR_FORMAT,
721 usage,
722 );
723 let (depth, depth_view) = texture(
724 device,
725 if index == 0 {
726 "taa history depth 0"
727 } else {
728 "taa history depth 1"
729 },
730 width,
731 height,
732 DEPTH_FORMAT,
733 usage,
734 );
735 let (id, id_view) = texture(
736 device,
737 if index == 0 {
738 "taa history id 0"
739 } else {
740 "taa history id 1"
741 },
742 width,
743 height,
744 ID_FORMAT,
745 usage,
746 );
747 let (normal, normal_view) = texture(
748 device,
749 if index == 0 {
750 "taa history normal 0"
751 } else {
752 "taa history normal 1"
753 },
754 width,
755 height,
756 NORMAL_FORMAT,
757 usage,
758 );
759 Slot {
760 colour,
761 colour_view,
762 depth_view,
763 id_view,
764 normal_view,
765 textures: [depth, id, normal],
766 }
767}
768
769impl Pass {
770 pub fn new(device: &wgpu::Device) -> Self {
771 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
772 label: Some("taa resolve"),
773 entries: &[
774 wgpu::BindGroupLayoutEntry {
775 binding: 0,
776 visibility: wgpu::ShaderStages::COMPUTE,
777 ty: wgpu::BindingType::Buffer {
778 ty: wgpu::BufferBindingType::Uniform,
779 has_dynamic_offset: false,
780 min_binding_size: None,
781 },
782 count: None,
783 },
784 float_entry(1),
785 float_entry(2),
786 float_entry(3),
787 uint_entry(4),
788 float_entry(5),
789 float_entry(6),
790 float_entry(7),
791 float_entry(8),
792 uint_entry(9),
793 float_entry(10),
794 storage_entry(11, COLOUR_FORMAT),
795 storage_entry(12, DEPTH_FORMAT),
796 storage_entry(13, ID_FORMAT),
797 storage_entry(14, NORMAL_FORMAT),
798 ],
799 });
800 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
801 label: Some("taa resolve"),
802 bind_group_layouts: &[Some(&layout)],
803 immediate_size: 0,
804 });
805 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
806 label: Some("taa resolve"),
807 source: wgpu::ShaderSource::Wgsl(RESOLVE_WGSL.into()),
808 });
809 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
810 label: Some("taa resolve"),
811 layout: Some(&pipeline_layout),
812 module: &module,
813 entry_point: Some("resolve"),
814 compilation_options: Default::default(),
815 cache: None,
816 });
817 let uniform = device.create_buffer(&wgpu::BufferDescriptor {
818 label: Some("taa uniforms"),
819 size: 256,
820 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
821 mapped_at_creation: false,
822 });
823 let (dummy_id, dummy_id_view) = texture(
824 device,
825 "taa dummy id",
826 1,
827 1,
828 ID_FORMAT,
829 wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
830 );
831 let (dummy_normal, dummy_normal_view) = texture(
832 device,
833 "taa dummy normal",
834 1,
835 1,
836 NORMAL_FORMAT,
837 wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
838 );
839 Self {
840 layout,
841 pipeline,
842 uniform,
843 dummy_id,
844 dummy_id_view,
845 dummy_normal,
846 dummy_normal_view,
847 slots: [None, None],
848 source: 0,
849 width: 0,
850 height: 0,
851 viewport: (0, 0),
852 history_size: (0, 0),
853 restarts: 0,
854 exposure: 1.0,
855 valid: false,
856 dummies_ready: false,
857 }
858 }
859
860 pub fn width(&self) -> u32 {
861 self.viewport.0
862 }
863
864 pub fn height(&self) -> u32 {
865 self.viewport.1
866 }
867
868 pub fn capacity(&self) -> (u32, u32) {
869 (self.width, self.height)
870 }
871
872 pub fn history_valid(&self) -> bool {
873 self.valid
874 }
875
876 pub fn textures(&self) -> Vec<wgpu::Texture> {
877 self.slots
878 .iter()
879 .flatten()
880 .flat_map(|slot| std::iter::once(&slot.colour).chain(&slot.textures))
881 .cloned()
882 .collect()
883 }
884
885 pub fn restarts(&self) -> u64 {
886 self.restarts
887 }
888
889 pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
890 if width == self.width && height == self.height && self.slots[0].is_some() {
891 self.viewport = (width, height);
892 return;
893 }
894 self.invalidate();
895 self.source = 0;
896 self.exposure = 1.0;
897 if width == 0 || height == 0 {
898 self.slots = [None, None];
899 self.width = 0;
900 self.height = 0;
901 self.viewport = (0, 0);
902 self.history_size = (0, 0);
903 return;
904 }
905 self.slots = [
906 Some(history_slot(device, width, height, 0)),
907 Some(history_slot(device, width, height, 1)),
908 ];
909 self.width = width;
910 self.height = height;
911 self.viewport = (width, height);
912 self.history_size = (width, height);
913 }
914
915 pub fn set_viewport(&mut self, width: u32, height: u32) -> Result<(), String> {
916 crate::viewport::check([width, height], [self.width, self.height])?;
917 self.viewport = (width, height);
918 Ok(())
919 }
920
921 pub fn invalidate(&mut self) {
922 if self.valid {
923 self.restarts += 1;
924 }
925 self.valid = false;
926 }
927
928 pub fn history_view(&self) -> Option<&wgpu::TextureView> {
929 if !self.valid {
930 return None;
931 }
932 self.slots[self.source]
933 .as_ref()
934 .map(|slot| &slot.colour_view)
935 }
936
937 pub fn resolve(
938 &mut self,
939 device: &wgpu::Device,
940 queue: &wgpu::Queue,
941 encoder: &mut wgpu::CommandEncoder,
942 inputs: &GpuInputs<'_>,
943 settings: &Settings,
944 ) -> Result<(), String> {
945 self.resolve_timed(device, queue, encoder, inputs, settings, None)
946 }
947
948 pub fn resolve_timed(
949 &mut self,
950 device: &wgpu::Device,
951 queue: &wgpu::Queue,
952 encoder: &mut wgpu::CommandEncoder,
953 inputs: &GpuInputs<'_>,
954 settings: &Settings,
955 mut profiler: Option<&mut pfx_gpu::GpuProfiler>,
956 ) -> Result<(), String> {
957 let (width, height) = self.viewport;
958 if width == 0 || height == 0 || self.slots[0].is_none() {
959 return Err("viewport is empty".into());
960 }
961 if !inputs.exposure.is_finite() || inputs.exposure <= 0.0 {
962 return Err("exposure must be positive".into());
963 }
964 let (history_weight, variance_gamma, depth_threshold, normal_min_dot) =
965 finite_settings(settings).map_err(|_| "settings must be finite".to_string())?;
966 if !self.dummies_ready {
967 queue.write_texture(
968 self.dummy_id.as_image_copy(),
969 &[0, 0, 0, 0],
970 wgpu::TexelCopyBufferLayout {
971 offset: 0,
972 bytes_per_row: Some(4),
973 rows_per_image: Some(1),
974 },
975 self.dummy_id.size(),
976 );
977 queue.write_texture(
978 self.dummy_normal.as_image_copy(),
979 &[0u8; 4],
980 wgpu::TexelCopyBufferLayout {
981 offset: 0,
982 bytes_per_row: Some(4),
983 rows_per_image: Some(1),
984 },
985 self.dummy_normal.size(),
986 );
987 self.dummies_ready = true;
988 }
989 let mut flags = 0;
990 if self.valid {
991 flags |= HISTORY_VALID;
992 }
993 if inputs.id.is_some() {
994 flags |= HAS_ID;
995 }
996 if inputs.normal.is_some() {
997 flags |= HAS_NORMAL;
998 }
999 let mut bytes = Vec::with_capacity(44);
1000 for value in [
1001 width.to_le_bytes(),
1002 height.to_le_bytes(),
1003 flags.to_le_bytes(),
1004 ] {
1005 bytes.extend_from_slice(&value);
1006 }
1007 for value in [
1008 inputs.exposure,
1009 if self.valid { self.exposure } else { 1.0 },
1010 depth_threshold,
1011 normal_min_dot,
1012 variance_gamma,
1013 history_weight,
1014 ] {
1015 bytes.extend_from_slice(&value.to_le_bytes());
1016 }
1017 for value in [self.history_size.0, self.history_size.1] {
1018 bytes.extend_from_slice(&value.to_le_bytes());
1019 }
1020 queue.write_buffer(&self.uniform, 0, &bytes);
1021 let dest = 1 - self.source;
1022 let source = self.slots[self.source].as_ref().unwrap();
1023 let target = self.slots[dest].as_ref().unwrap();
1024 let id_view = inputs.id.unwrap_or(&self.dummy_id_view);
1025 let normal_view = inputs.normal.unwrap_or(&self.dummy_normal_view);
1026 let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1027 label: Some("taa resolve"),
1028 layout: &self.layout,
1029 entries: &[
1030 wgpu::BindGroupEntry {
1031 binding: 0,
1032 resource: self.uniform.as_entire_binding(),
1033 },
1034 view_entry(1, inputs.colour),
1035 view_entry(2, inputs.depth),
1036 view_entry(3, inputs.motion),
1037 view_entry(4, id_view),
1038 view_entry(5, normal_view),
1039 view_entry(6, inputs.reactive),
1040 view_entry(7, &source.colour_view),
1041 view_entry(8, &source.depth_view),
1042 view_entry(9, &source.id_view),
1043 view_entry(10, &source.normal_view),
1044 view_entry(11, &target.colour_view),
1045 view_entry(12, &target.depth_view),
1046 view_entry(13, &target.id_view),
1047 view_entry(14, &target.normal_view),
1048 ],
1049 });
1050 let timing = profiler
1051 .as_deref_mut()
1052 .and_then(|timer| timer.pass("TAA resolve"));
1053 {
1054 let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1055 label: Some("taa resolve"),
1056 timestamp_writes: profiler
1057 .as_deref()
1058 .and_then(|timer| timer.compute_writes(timing)),
1059 });
1060 pass.set_pipeline(&self.pipeline);
1061 pass.set_bind_group(0, &bind, &[]);
1062 pass.dispatch_workgroups(width.div_ceil(WORKGROUP), height.div_ceil(WORKGROUP), 1);
1063 }
1064 self.source = dest;
1065 self.exposure = inputs.exposure;
1066 self.valid = true;
1067 self.history_size = (width, height);
1068 Ok(())
1069 }
1070
1071 pub fn read_colour(
1072 &self,
1073 device: &wgpu::Device,
1074 queue: &wgpu::Queue,
1075 ) -> Result<Vec<[f32; 4]>, String> {
1076 if !self.valid {
1077 return Err("history is empty".into());
1078 }
1079 let slot = self.slots[self.source].as_ref().ok_or("history is empty")?;
1080 read_rgba16(
1081 device,
1082 queue,
1083 &slot.colour,
1084 self.history_size.0,
1085 self.history_size.1,
1086 )
1087 }
1088}
1089
1090fn view_entry(binding: u32, view: &wgpu::TextureView) -> wgpu::BindGroupEntry<'_> {
1091 wgpu::BindGroupEntry {
1092 binding,
1093 resource: wgpu::BindingResource::TextureView(view),
1094 }
1095}
1096
1097fn read_rgba16(
1098 device: &wgpu::Device,
1099 queue: &wgpu::Queue,
1100 texture: &wgpu::Texture,
1101 width: u32,
1102 height: u32,
1103) -> Result<Vec<[f32; 4]>, String> {
1104 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1105 let raw = width * 8;
1106 let row = raw.div_ceil(align) * align;
1107 let size = u64::from(row) * u64::from(height);
1108 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
1109 label: Some("taa readback"),
1110 size,
1111 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1112 mapped_at_creation: false,
1113 });
1114 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1115 label: Some("taa readback"),
1116 });
1117 encoder.copy_texture_to_buffer(
1118 texture.as_image_copy(),
1119 wgpu::TexelCopyBufferInfo {
1120 buffer: &buffer,
1121 layout: wgpu::TexelCopyBufferLayout {
1122 offset: 0,
1123 bytes_per_row: Some(row),
1124 rows_per_image: Some(height),
1125 },
1126 },
1127 wgpu::Extent3d {
1128 width,
1129 height,
1130 depth_or_array_layers: 1,
1131 },
1132 );
1133 queue.submit(Some(encoder.finish()));
1134 let (send, receive) = std::sync::mpsc::channel();
1135 let slice = buffer.slice(..);
1136 slice.map_async(wgpu::MapMode::Read, move |result| {
1137 let _ = send.send(result);
1138 });
1139 device
1140 .poll(wgpu::PollType::wait_indefinitely())
1141 .map_err(|error| format!("{error:?}"))?;
1142 receive
1143 .recv()
1144 .map_err(|error| error.to_string())?
1145 .map_err(|error| error.to_string())?;
1146 let mapped = slice.get_mapped_range().expect("a mapped range");
1147 let mut pixels = Vec::with_capacity((width * height) as usize);
1148 for padded in mapped.chunks_exact(row as usize) {
1149 for texel in padded[..raw as usize].chunks_exact(8) {
1150 pixels.push([
1151 half::f16::from_le_bytes(texel[0..2].try_into().unwrap()).to_f32(),
1152 half::f16::from_le_bytes(texel[2..4].try_into().unwrap()).to_f32(),
1153 half::f16::from_le_bytes(texel[4..6].try_into().unwrap()).to_f32(),
1154 half::f16::from_le_bytes(texel[6..8].try_into().unwrap()).to_f32(),
1155 ]);
1156 }
1157 }
1158 drop(mapped);
1159 buffer.unmap();
1160 Ok(pixels)
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165 use super::*;
1166
1167 fn assert_near(actual: f32, expected: f32, tol: f32) {
1168 assert!(
1169 (actual - expected).abs() <= tol,
1170 "{actual} != {expected} within {tol}"
1171 );
1172 }
1173
1174 fn pattern(reactive: f32, depth: f32, motion: [f32; 2], exposure: f32) -> Inputs {
1175 let values = [1.0, 0.0, 1.0, 0.0, 0.25, 0.0, 1.0, 0.0, 1.0];
1176 Inputs {
1177 width: 3,
1178 height: 3,
1179 colour: values.into_iter().map(|v| [v, v, v]).collect(),
1180 depth: (0..9).map(|i| if i == 4 { depth } else { 0.5 }).collect(),
1181 motion: (0..9)
1182 .map(|i| if i == 4 { motion } else { [0.0, 0.0] })
1183 .collect(),
1184 reactive: (0..9)
1185 .map(|i| if i == 4 { reactive } else { 0.0 })
1186 .collect(),
1187 id: None,
1188 normal: None,
1189 exposure,
1190 }
1191 }
1192
1193 fn filled(hdr: f32, exposure: f32, depth: f32) -> History {
1194 History {
1195 width: 3,
1196 height: 3,
1197 rgba: vec![[hdr, hdr, hdr, exposure]; 9],
1198 depth: vec![depth; 9],
1199 id: vec![0; 9],
1200 normal: vec![[0.0, 0.0, 1.0]; 9],
1201 exposure,
1202 valid: true,
1203 }
1204 }
1205
1206 #[test]
1207 fn halton_values_and_cycle() {
1208 assert_eq!(halton(0, 2).to_bits(), 0.0f32.to_bits());
1209 assert_eq!(halton(1, 2).to_bits(), 0.5f32.to_bits());
1210 assert_eq!(halton(2, 2).to_bits(), 0.25f32.to_bits());
1211 assert_eq!(halton(3, 2).to_bits(), 0.75f32.to_bits());
1212 assert_eq!(halton(4, 2).to_bits(), 0.125f32.to_bits());
1213 assert_eq!(halton(7, 2).to_bits(), 0.875f32.to_bits());
1214 assert_near(halton(1, 3), 1.0 / 3.0, 1e-6);
1215 assert_near(halton(2, 3), 2.0 / 3.0, 1e-6);
1216 assert_near(halton(3, 3), 1.0 / 9.0, 1e-6);
1217 assert_near(halton(4, 3), 4.0 / 9.0, 1e-6);
1218 assert_near(halton(5, 3), 7.0 / 9.0, 1e-6);
1219 assert_near(halton(6, 3), 2.0 / 9.0, 1e-6);
1220 assert_near(halton(7, 3), 5.0 / 9.0, 1e-6);
1221 assert_eq!(halton(1, 0).to_bits(), 0.0f32.to_bits());
1222 let expected_x = [
1223 -0.4375f32, 0.0625, -0.1875, 0.3125, -0.3125, 0.1875, -0.0625, 0.4375,
1224 ];
1225 for frame in 0..CYCLE {
1226 assert_eq!(
1227 jitter_pixels(frame)[0].to_bits(),
1228 expected_x[frame as usize].to_bits()
1229 );
1230 assert_eq!(
1231 jitter_pixels(frame)[0].to_bits(),
1232 jitter_pixels(frame + CYCLE)[0].to_bits()
1233 );
1234 assert_eq!(
1235 jitter_pixels(frame)[1].to_bits(),
1236 jitter_pixels(frame + CYCLE * 3)[1].to_bits()
1237 );
1238 }
1239 for frame in 0..CYCLE {
1240 for other in 0..frame {
1241 let a = jitter_pixels(frame);
1242 let b = jitter_pixels(other);
1243 assert!(a[0].to_bits() != b[0].to_bits() || a[1].to_bits() != b[1].to_bits());
1244 }
1245 }
1246 }
1247
1248 #[test]
1249 fn jitter_averages_to_zero() {
1250 let mut sum = [0.0f32; 2];
1251 for frame in 0..CYCLE {
1252 let jitter = jitter_pixels(frame);
1253 assert!(jitter[0].abs() <= 0.5 && jitter[1].abs() <= 0.5);
1254 sum[0] += jitter[0];
1255 sum[1] += jitter[1];
1256 let clip = jitter_clip(frame, 320, 180);
1257 assert_near(clip[0], jitter[0] * 2.0 / 320.0, 1e-7);
1258 assert_near(clip[1], jitter[1] * 2.0 / 180.0, 1e-7);
1259 }
1260 assert_near(sum[0], 0.0, 1e-6);
1261 assert_near(sum[1], 0.0, 1e-6);
1262 assert_eq!(jitter_clip(0, 0, 10)[0].to_bits(), 0.0f32.to_bits());
1263 assert_eq!(jitter_clip(0, 10, 0)[1].to_bits(), 0.0f32.to_bits());
1264 }
1265
1266 #[test]
1267 fn ycocg_roundtrip_and_clip() {
1268 let colour = [0.2, 0.5, 0.8];
1269 let back = ycocg_to_rgb(rgb_to_ycocg(colour));
1270 for channel in 0..3 {
1271 assert_near(back[channel], colour[channel], 1e-6);
1272 }
1273 let inside = clip_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.25, 0.5, 0.75]);
1274 assert_eq!(inside[0].to_bits(), 0.25f32.to_bits());
1275 let clipped = clip_aabb([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]);
1276 assert_near(clipped[0], 1.0, 1e-5);
1277 assert_near(clipped[1], 0.0, 1e-5);
1278 }
1279
1280 fn coverage(pixel: u32, edge: f32) -> f32 {
1281 let start = pixel as f32;
1282 let end = start + 1.0;
1283 if end <= edge {
1284 0.0
1285 } else if start >= edge {
1286 1.0
1287 } else {
1288 end - edge
1289 }
1290 }
1291
1292 fn edge_frame(frame: u32) -> Inputs {
1293 let width = 8;
1294 let height = 4;
1295 let edge = 3.35;
1296 let jitter = jitter_pixels(frame);
1297 let mut colour = Vec::with_capacity((width * height) as usize);
1298 for _y in 0..height {
1299 for x in 0..width {
1300 let sample = x as f32 + 0.5 + jitter[0];
1301 let value = if sample >= edge { 1.0 } else { 0.0 };
1302 colour.push([value, value, value]);
1303 }
1304 }
1305 Inputs {
1306 width,
1307 height,
1308 colour,
1309 depth: vec![1.0; (width * height) as usize],
1310 motion: vec![[0.0, 0.0]; (width * height) as usize],
1311 reactive: vec![0.0; (width * height) as usize],
1312 id: None,
1313 normal: None,
1314 exposure: 1.0,
1315 }
1316 }
1317
1318 #[test]
1319 fn static_jittered_edge_converges() {
1320 let edge = 3.35;
1321 let index = 8 + 3;
1322 let mut saw_black = false;
1323 let mut saw_white = false;
1324 for frame in 0..CYCLE {
1325 let value = edge_frame(frame).colour[index][0];
1326 saw_black |= value.to_bits() == 0.0f32.to_bits();
1327 saw_white |= value.to_bits() == 1.0f32.to_bits();
1328 }
1329 assert!(saw_black && saw_white);
1330 let mut history = History::empty();
1331 let mut tail = Vec::new();
1332 for frame in 0..64 {
1333 let output = resolve(&edge_frame(frame), &history, &Settings::default()).unwrap();
1334 if frame >= 56 {
1335 tail.push(output.colour[index][0]);
1336 }
1337 history = output.history;
1338 }
1339 let mean = tail.iter().sum::<f32>() / tail.len() as f32;
1340 let expected = coverage(3, edge);
1341 assert_near(mean, expected, 0.05);
1342 assert!(mean > 0.4 && mean < 0.9);
1343 let flat_black = history.rgba[8][0];
1344 let flat_white = history.rgba[8 + 7][0];
1345 assert_near(flat_black, coverage(0, edge), 0.02);
1346 assert_near(flat_white, coverage(7, edge), 0.02);
1347 }
1348
1349 #[test]
1350 fn depth_discontinuity_rejects_history() {
1351 let current = pattern(0.0, 0.8, [0.0, 0.0], 1.0);
1352 let rejected = resolve(¤t, &filled(0.75, 1.0, 0.2), &Settings::default()).unwrap();
1353 assert_near(rejected.colour[4][0], 0.25, 1e-5);
1354 let kept = resolve(
1355 &pattern(0.0, 0.5, [0.0, 0.0], 1.0),
1356 &filled(0.75, 1.0, 0.5),
1357 &Settings::default(),
1358 )
1359 .unwrap();
1360 assert_near(kept.colour[4][0], 0.6875, 1e-5);
1361 let mut resized = filled(0.75, 1.0, 0.5);
1362 resized.width = 2;
1363 let dropped = resolve(
1364 &pattern(0.0, 0.5, [0.0, 0.0], 1.0),
1365 &resized,
1366 &Settings::default(),
1367 )
1368 .unwrap();
1369 assert_near(dropped.colour[4][0], 0.25, 1e-5);
1370 }
1371
1372 #[test]
1373 fn a_history_of_another_size_is_resampled_not_dropped() {
1374 let smaller = History {
1375 width: 2,
1376 height: 2,
1377 rgba: vec![[0.75, 0.75, 0.75, 1.0]; 4],
1378 depth: vec![0.5; 4],
1379 id: vec![0; 4],
1380 normal: vec![[0.0, 0.0, 1.0]; 4],
1381 exposure: 1.0,
1382 valid: true,
1383 };
1384 let kept = resolve(
1385 &pattern(0.0, 0.5, [0.0, 0.0], 1.0),
1386 &smaller,
1387 &Settings::default(),
1388 )
1389 .unwrap();
1390 assert_near(kept.colour[4][0], 0.6875, 1e-5);
1391 assert_eq!((kept.history.width, kept.history.height), (3, 3));
1392 let mut ramp = smaller.clone();
1393 ramp.width = 4;
1394 ramp.height = 1;
1395 ramp.rgba = (0..4)
1396 .map(|x| {
1397 let v = x as f32 / 3.0;
1398 [v, v, v, 1.0]
1399 })
1400 .collect();
1401 let mut current = pattern(1.0, 0.5, [0.0, 0.0], 1.0);
1402 current.reactive = vec![0.0; 9];
1403 let settings = Settings {
1404 history_weight: 1.0,
1405 variance_gamma: 1.0e6,
1406 ..Settings::default()
1407 };
1408 let resampled = resolve(¤t, &ramp, &settings).unwrap();
1409 for x in 0..3 {
1410 let u = (x as f32 + 0.5) / 3.0 * 4.0 - 0.5;
1411 assert_near(resampled.colour[x][0], u.clamp(0.0, 3.0) / 3.0, 1e-5);
1412 }
1413 }
1414
1415 #[test]
1416 fn reactive_mask_limits_history() {
1417 let history = filled(0.75, 1.0, 0.5);
1418 let open = resolve(
1419 &pattern(0.0, 0.5, [0.0, 0.0], 1.0),
1420 &history,
1421 &Settings::default(),
1422 )
1423 .unwrap();
1424 let shut = resolve(
1425 &pattern(1.0, 0.5, [0.0, 0.0], 1.0),
1426 &history,
1427 &Settings::default(),
1428 )
1429 .unwrap();
1430 let over = resolve(
1431 &pattern(2.0, 0.5, [0.0, 0.0], 1.0),
1432 &history,
1433 &Settings::default(),
1434 )
1435 .unwrap();
1436 let half = resolve(
1437 &pattern(0.5, 0.5, [0.0, 0.0], 1.0),
1438 &history,
1439 &Settings::default(),
1440 )
1441 .unwrap();
1442 assert_near(open.colour[4][0], 0.6875, 1e-5);
1443 assert_near(shut.colour[4][0], 0.25, 1e-5);
1444 assert_near(over.colour[4][0], 0.25, 1e-5);
1445 assert!((half.colour[4][0] - 0.25).abs() < (open.colour[4][0] - 0.25).abs());
1446 assert!((half.colour[4][0] - 0.25).abs() > 1e-4);
1447 }
1448
1449 #[test]
1450 fn id_and_normal_rejection_and_exposure() {
1451 let mut current = pattern(0.0, 0.5, [0.0, 0.0], 1.0);
1452 current.id = Some(vec![7; 9]);
1453 let mut matched = filled(0.75, 1.0, 0.5);
1454 matched.id = vec![7; 9];
1455 let kept = resolve(¤t, &matched, &Settings::default()).unwrap();
1456 assert_near(kept.colour[4][0], 0.6875, 1e-5);
1457 matched.id = vec![8; 9];
1458 let rejected = resolve(¤t, &matched, &Settings::default()).unwrap();
1459 assert_near(rejected.colour[4][0], 0.25, 1e-5);
1460
1461 current.id = None;
1462 current.normal = Some(vec![[0.0, 0.0, 1.0]; 9]);
1463 let mut aligned = filled(0.75, 1.0, 0.5);
1464 aligned.normal = vec![[0.0, 0.0, 1.0]; 9];
1465 let kept = resolve(¤t, &aligned, &Settings::default()).unwrap();
1466 assert_near(kept.colour[4][0], 0.6875, 1e-5);
1467 aligned.normal = vec![[0.0, 1.0, 0.0]; 9];
1468 let turned = resolve(¤t, &aligned, &Settings::default()).unwrap();
1469 assert_near(turned.colour[4][0], 0.25, 1e-5);
1470 aligned.normal = vec![[0.0; 3]; 9];
1471 let missing = resolve(¤t, &aligned, &Settings::default()).unwrap();
1472 assert_near(missing.colour[4][0], 0.6875, 1e-5);
1473
1474 let history = filled(0.75, 1.0, 0.5);
1475 let rescaled = resolve(
1476 &pattern(0.0, 0.5, [0.0, 0.0], 4.0),
1477 &history,
1478 &Settings::default(),
1479 )
1480 .unwrap();
1481 assert_near(rescaled.colour[4][0], 0.6875, 1e-5);
1482 assert_near(rescaled.history.rgba[4][3], 4.0, 1e-6);
1483 assert_near(rescaled.history.exposure, 4.0, 1e-6);
1484 let mut kept_hdr = history.clone();
1485 kept_hdr.exposure = 2.0;
1486 let still = resolve(
1487 &pattern(0.0, 0.5, [0.0, 0.0], 4.0),
1488 &kept_hdr,
1489 &Settings::default(),
1490 )
1491 .unwrap();
1492 assert_near(still.colour[4][0], 0.6875, 1e-5);
1493 }
1494
1495 fn wide_pattern(motion: [f32; 2]) -> Inputs {
1496 let mut colour = vec![[0.5; 3]; 12];
1497 let mut depth = vec![0.5; 12];
1498 let mut reactive = vec![0.0; 12];
1499 for y in 0..3 {
1500 for x in 0..3 {
1501 let value = match (x, y) {
1502 (1, 1) => 0.25,
1503 (1, _) | (_, 1) => 0.0,
1504 _ => 1.0,
1505 };
1506 colour[y * 4 + x] = [value, value, value];
1507 depth[y * 4 + x] = 0.5;
1508 reactive[y * 4 + x] = 0.0;
1509 }
1510 }
1511 Inputs {
1512 width: 4,
1513 height: 3,
1514 colour,
1515 depth,
1516 motion: (0..12)
1517 .map(|index| if index == 5 { motion } else { [0.0, 0.0] })
1518 .collect(),
1519 reactive,
1520 id: None,
1521 normal: None,
1522 exposure: 1.0,
1523 }
1524 }
1525
1526 #[test]
1527 fn motion_reprojects_or_rejects() {
1528 let mut history = History {
1529 width: 4,
1530 height: 3,
1531 rgba: vec![[0.5, 0.5, 0.5, 1.0]; 12],
1532 depth: vec![0.5; 12],
1533 id: vec![0; 12],
1534 normal: vec![[0.0; 3]; 12],
1535 exposure: 1.0,
1536 valid: true,
1537 };
1538 history.rgba[5] = [0.5, 0.5, 0.5, 1.0];
1539 history.rgba[6] = [0.75, 0.75, 0.75, 1.0];
1540 let stayed = resolve(&wide_pattern([0.0, 0.0]), &history, &Settings::default()).unwrap();
1541 let shifted = resolve(&wide_pattern([0.25, 0.0]), &history, &Settings::default()).unwrap();
1542 let between = resolve(&wide_pattern([0.125, 0.0]), &history, &Settings::default()).unwrap();
1543 let left = resolve(&wide_pattern([2.0, 0.0]), &history, &Settings::default()).unwrap();
1544 let border = resolve(&wide_pattern([0.625, 0.0]), &history, &Settings::default()).unwrap();
1545 assert_near(stayed.colour[5][0], 0.46875, 1e-5);
1546 assert_near(shifted.colour[5][0], 0.6875, 1e-5);
1547 assert_near(between.colour[5][0], 0.578125, 1e-5);
1548 assert_near(left.colour[5][0], 0.25, 1e-5);
1549 assert_near(border.colour[5][0], 0.25, 1e-5);
1550 }
1551
1552 #[test]
1553 fn bad_inputs_are_rejected() {
1554 let mut inputs = pattern(0.0, 0.5, [0.0, 0.0], 1.0);
1555 inputs.colour.pop();
1556 assert_eq!(
1557 resolve(&inputs, &History::empty(), &Settings::default()).unwrap_err(),
1558 Error::Length
1559 );
1560 inputs = pattern(0.0, 0.5, [0.0, 0.0], 0.0);
1561 assert_eq!(
1562 resolve(&inputs, &History::empty(), &Settings::default()).unwrap_err(),
1563 Error::Exposure
1564 );
1565 let settings = Settings {
1566 history_weight: f32::NAN,
1567 ..Settings::default()
1568 };
1569 assert_eq!(
1570 resolve(
1571 &pattern(0.0, 0.5, [0.0, 0.0], 1.0),
1572 &History::empty(),
1573 &settings
1574 )
1575 .unwrap_err(),
1576 Error::Settings
1577 );
1578 assert!(RESOLVE_WGSL.contains("fn resolve"));
1579 assert!(RESOLVE_WGSL.contains("rgb_to_ycocg"));
1580 }
1581
1582 fn upload(queue: &wgpu::Queue, texture: &wgpu::Texture, width: u32, height: u32, bytes: &[u8]) {
1583 let row = bytes.len() as u32 / height;
1584 queue.write_texture(
1585 texture.as_image_copy(),
1586 bytes,
1587 wgpu::TexelCopyBufferLayout {
1588 offset: 0,
1589 bytes_per_row: Some(row),
1590 rows_per_image: Some(height),
1591 },
1592 wgpu::Extent3d {
1593 width,
1594 height,
1595 depth_or_array_layers: 1,
1596 },
1597 );
1598 }
1599
1600 fn f32_bytes(values: &[f32]) -> Vec<u8> {
1601 values
1602 .iter()
1603 .flat_map(|value| value.to_le_bytes())
1604 .collect()
1605 }
1606
1607 fn f16_bytes(values: &[f32]) -> Vec<u8> {
1608 values
1609 .iter()
1610 .flat_map(|value| half::f16::from_f32(*value).to_le_bytes())
1611 .collect()
1612 }
1613
1614 struct GpuFrame {
1615 colour: wgpu::Texture,
1616 colour_view: wgpu::TextureView,
1617 depth: wgpu::Texture,
1618 depth_view: wgpu::TextureView,
1619 motion: wgpu::Texture,
1620 motion_view: wgpu::TextureView,
1621 reactive: wgpu::Texture,
1622 reactive_view: wgpu::TextureView,
1623 id: wgpu::Texture,
1624 id_view: wgpu::TextureView,
1625 normal: wgpu::Texture,
1626 normal_view: wgpu::TextureView,
1627 }
1628
1629 impl GpuFrame {
1630 fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
1631 let sampled = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
1632 let (colour, colour_view) =
1633 texture(device, "taa colour", width, height, COLOUR_FORMAT, sampled);
1634 let (depth, depth_view) =
1635 texture(device, "taa depth", width, height, DEPTH_FORMAT, sampled);
1636 let (motion, motion_view) =
1637 texture(device, "taa motion", width, height, MOTION_FORMAT, sampled);
1638 let (reactive, reactive_view) = texture(
1639 device,
1640 "taa reactive",
1641 width,
1642 height,
1643 REACTIVE_FORMAT,
1644 sampled,
1645 );
1646 let (id, id_view) = texture(device, "taa id", width, height, ID_FORMAT, sampled);
1647 let (normal, normal_view) =
1648 texture(device, "taa normal", width, height, NORMAL_FORMAT, sampled);
1649 Self {
1650 colour,
1651 colour_view,
1652 depth,
1653 depth_view,
1654 motion,
1655 motion_view,
1656 reactive,
1657 reactive_view,
1658 id,
1659 id_view,
1660 normal,
1661 normal_view,
1662 }
1663 }
1664
1665 fn upload(&self, queue: &wgpu::Queue, inputs: &Inputs) {
1666 let width = inputs.width;
1667 let height = inputs.height;
1668 let mut colour = Vec::new();
1669 for pixel in &inputs.colour {
1670 colour.extend_from_slice(&[pixel[0], pixel[1], pixel[2], 0.0]);
1671 }
1672 upload(queue, &self.colour, width, height, &f16_bytes(&colour));
1673 upload(queue, &self.depth, width, height, &f32_bytes(&inputs.depth));
1674 let mut motion = Vec::new();
1675 for pixel in &inputs.motion {
1676 motion.extend_from_slice(pixel);
1677 }
1678 upload(queue, &self.motion, width, height, &f16_bytes(&motion));
1679 upload(
1680 queue,
1681 &self.reactive,
1682 width,
1683 height,
1684 &inputs
1685 .reactive
1686 .iter()
1687 .map(|value| (value.clamp(0.0, 1.0) * 255.0).round() as u8)
1688 .collect::<Vec<_>>(),
1689 );
1690 let ids = inputs
1691 .id
1692 .clone()
1693 .unwrap_or_else(|| vec![0; inputs.colour.len()]);
1694 let id_bytes: Vec<u8> = ids.iter().flat_map(|id| id.to_le_bytes()).collect();
1695 upload(queue, &self.id, width, height, &id_bytes);
1696 let mut normal = Vec::new();
1697 let normals = inputs
1698 .normal
1699 .clone()
1700 .unwrap_or_else(|| vec![[0.0; 3]; inputs.colour.len()]);
1701 for pixel in normals {
1702 normal.extend_from_slice(&[pixel[0], pixel[1], pixel[2], 0.0]);
1703 }
1704 let normal_bytes: Vec<u8> = normal
1705 .iter()
1706 .map(|value| (value.clamp(-1.0, 1.0) * 127.0).round() as i8 as u8)
1707 .collect();
1708 upload(queue, &self.normal, width, height, &normal_bytes);
1709 }
1710 }
1711
1712 fn gpu_resolve(
1713 pass: &mut Pass,
1714 gpu: &pfx_gpu::Gpu,
1715 frame: &GpuFrame,
1716 inputs: &Inputs,
1717 ) -> Vec<[f32; 4]> {
1718 frame.upload(&gpu.queue, inputs);
1719 let mut encoder = gpu.device.create_command_encoder(&Default::default());
1720 pass.resolve(
1721 &gpu.device,
1722 &gpu.queue,
1723 &mut encoder,
1724 &GpuInputs {
1725 colour: &frame.colour_view,
1726 depth: &frame.depth_view,
1727 motion: &frame.motion_view,
1728 reactive: &frame.reactive_view,
1729 id: inputs.id.as_ref().map(|_| &frame.id_view),
1730 normal: inputs.normal.as_ref().map(|_| &frame.normal_view),
1731 exposure: inputs.exposure,
1732 },
1733 &Settings::default(),
1734 )
1735 .unwrap();
1736 gpu.queue.submit(Some(encoder.finish()));
1737 pass.read_colour(&gpu.device, &gpu.queue).unwrap()
1738 }
1739
1740 fn sized_frame(step: u32, width: u32, height: u32) -> Inputs {
1741 let n = (width * height) as usize;
1742 Inputs {
1743 width,
1744 height,
1745 colour: (0..n)
1746 .map(|i| {
1747 let x = (i as u32 % width) as f32 / width as f32;
1748 let y = (i as u32 / width) as f32 / height as f32;
1749 let wave = ((x * 7.0 + y * 3.0 + step as f32 * 0.7).sin() + 1.0) * 0.5;
1750 [wave, x, y]
1751 })
1752 .collect(),
1753 depth: (0..n).map(|i| 0.4 + 0.01 * (i % 3) as f32).collect(),
1754 motion: vec![[0.03, -0.02]; n],
1755 reactive: vec![0.0; n],
1756 id: Some(vec![2; n]),
1757 normal: None,
1758 exposure: 1.0,
1759 }
1760 }
1761
1762 #[test]
1763 #[ignore = "needs a GPU; run under pgpu"]
1764 fn gpu_resamples_history_across_viewport_changes() {
1765 let gpu = pollster::block_on(pfx_gpu::Gpu::headless()).unwrap();
1766 let mut pass = Pass::new(&gpu.device);
1767 pass.resize(&gpu.device, 8, 4);
1768 let frame = GpuFrame::new(&gpu.device, 8, 4);
1769 let mut history = History::empty();
1770 let sizes = [(8, 4), (8, 4), (5, 3), (7, 4), (8, 4), (3, 2), (6, 4)];
1771 for (step, (width, height)) in sizes.into_iter().enumerate() {
1772 pass.set_viewport(width, height).unwrap();
1773 let inputs = sized_frame(step as u32, width, height);
1774 let cpu = resolve(&inputs, &history, &Settings::default()).unwrap();
1775 let got = gpu_resolve(&mut pass, &gpu, &frame, &inputs);
1776 assert_eq!(got.len(), cpu.colour.len());
1777 for (index, pixel) in cpu.colour.iter().enumerate() {
1778 for channel in 0..3 {
1779 assert_near(got[index][channel], pixel[channel], 1.5e-3);
1780 }
1781 }
1782 history = cpu.history;
1783 }
1784 assert!(pass.history_valid());
1785 assert_eq!(pass.restarts(), 0);
1786 assert!(pass.set_viewport(9, 4).is_err());
1787 pass.invalidate();
1788 assert_eq!(pass.restarts(), 1);
1789 }
1790
1791 #[test]
1792 #[ignore = "needs a GPU; run under pgpu"]
1793 fn gpu_resolves_a_sequence() {
1794 let gpu = pollster::block_on(pfx_gpu::Gpu::headless()).unwrap();
1795 let mut pass = Pass::new(&gpu.device);
1796 pass.resize(&gpu.device, 8, 4);
1797 let frame = GpuFrame::new(&gpu.device, 8, 4);
1798 let mut history = History::empty();
1799 for step in 0..8 {
1800 let inputs = edge_frame(step);
1801 let cpu = resolve(&inputs, &history, &Settings::default()).unwrap();
1802 let got = gpu_resolve(&mut pass, &gpu, &frame, &inputs);
1803 for (index, pixel) in cpu.colour.iter().enumerate() {
1804 for channel in 0..3 {
1805 assert_near(got[index][channel], pixel[channel], 1.5e-3);
1806 }
1807 assert_near(got[index][3], inputs.exposure, 1.0e-5);
1808 }
1809 history = cpu.history;
1810 }
1811 assert!(pass.history_valid());
1812 pass.resize(&gpu.device, 8, 4);
1813 assert!(pass.history_valid());
1814 pass.resize(&gpu.device, 5, 4);
1815 assert!(!pass.history_valid());
1816 let small = GpuFrame::new(&gpu.device, 5, 4);
1817 let mut inputs = pattern(1.0, 0.8, [2.0, 0.0], 4.0);
1818 inputs.width = 5;
1819 inputs.height = 4;
1820 inputs.colour = vec![[0.25; 3]; 20];
1821 inputs.depth = vec![0.8; 20];
1822 inputs.motion = vec![[0.0; 2]; 20];
1823 inputs.reactive = vec![1.0; 20];
1824 inputs.reactive[0] = 0.0;
1825 inputs.id = Some(vec![3; 20]);
1826 inputs.normal = None;
1827 let cpu = resolve(&inputs, &History::empty(), &Settings::default()).unwrap();
1828 let got = gpu_resolve(&mut pass, &gpu, &small, &inputs);
1829 for (index, pixel) in cpu.colour.iter().enumerate() {
1830 for channel in 0..3 {
1831 assert_near(got[index][channel], pixel[channel], 1.5e-3);
1832 }
1833 assert_near(got[index][3], 4.0, 1.0e-5);
1834 }
1835 let mut second = pattern(0.0, 0.5, [0.125, 0.0], 2.0);
1836 second.normal = Some(vec![[0.0, 0.0, 1.0]; 9]);
1837 let mut pass = Pass::new(&gpu.device);
1838 pass.resize(&gpu.device, 3, 3);
1839 let frame = GpuFrame::new(&gpu.device, 3, 3);
1840 let mut history = History::empty();
1841 let mut first = pattern(0.0, 0.5, [0.0, 0.0], 1.0);
1842 first.normal = Some(vec![[0.0, 0.0, 1.0]; 9]);
1843 for inputs in [first, second] {
1844 let cpu = resolve(&inputs, &history, &Settings::default()).unwrap();
1845 let got = gpu_resolve(&mut pass, &gpu, &frame, &inputs);
1846 for (index, pixel) in cpu.colour.iter().enumerate() {
1847 for channel in 0..3 {
1848 assert_near(got[index][channel], pixel[channel], 1.5e-3);
1849 }
1850 }
1851 history = cpu.history;
1852 }
1853 }
1854
1855 #[test]
1856 #[ignore = "needs a GPU; run under pgpu"]
1857 fn gpu_resolve_4k_timing() {
1858 let gpu = pollster::block_on(pfx_gpu::Gpu::headless()).unwrap();
1859 assert!(
1860 gpu.device
1861 .features()
1862 .contains(wgpu::Features::TIMESTAMP_QUERY)
1863 );
1864 assert!(
1865 gpu.device
1866 .features()
1867 .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS)
1868 );
1869 let mut pass = Pass::new(&gpu.device);
1870 pass.resize(&gpu.device, 3840, 2160);
1871 let frame = GpuFrame::new(&gpu.device, 3840, 2160);
1872 let mut colour = Vec::with_capacity(3840 * 2160);
1873 let mut depth = Vec::with_capacity(3840 * 2160);
1874 let mut motion = Vec::with_capacity(3840 * 2160);
1875 let mut reactive = Vec::with_capacity(3840 * 2160);
1876 let mut id = Vec::with_capacity(3840 * 2160);
1877 let mut normal = Vec::with_capacity(3840 * 2160);
1878 for y in 0..2160 {
1879 for x in 0..3840 {
1880 colour.push([
1881 x as f32 / 3840.0,
1882 y as f32 / 2160.0,
1883 ((x ^ y) & 31) as f32 / 16.0,
1884 ]);
1885 depth.push(0.1 + (x % 127) as f32 / 128.0);
1886 motion.push([1.0 / 3840.0, (y % 3) as f32 / 2160.0]);
1887 reactive.push((x % 17) as f32 / 17.0);
1888 id.push(x / 32);
1889 normal.push([0.0, (y % 5) as f32 / 5.0, 1.0]);
1890 }
1891 }
1892 frame.upload(
1893 &gpu.queue,
1894 &Inputs {
1895 width: 3840,
1896 height: 2160,
1897 colour,
1898 depth,
1899 motion,
1900 reactive,
1901 id: Some(id),
1902 normal: Some(normal),
1903 exposure: 1.0,
1904 },
1905 );
1906 let inputs = GpuInputs {
1907 colour: &frame.colour_view,
1908 depth: &frame.depth_view,
1909 motion: &frame.motion_view,
1910 reactive: &frame.reactive_view,
1911 id: Some(&frame.id_view),
1912 normal: None,
1913 exposure: 1.0,
1914 };
1915 let mut encoder = gpu.device.create_command_encoder(&Default::default());
1916 pass.resolve(
1917 &gpu.device,
1918 &gpu.queue,
1919 &mut encoder,
1920 &inputs,
1921 &Settings::default(),
1922 )
1923 .unwrap();
1924 gpu.queue.submit(Some(encoder.finish()));
1925 gpu.device
1926 .poll(wgpu::PollType::wait_indefinitely())
1927 .unwrap();
1928 let queries = gpu.device.create_query_set(&wgpu::QuerySetDescriptor {
1929 label: Some("taa 4k timing"),
1930 ty: wgpu::QueryType::Timestamp,
1931 count: 2,
1932 });
1933 let resolved = gpu.device.create_buffer(&wgpu::BufferDescriptor {
1934 label: Some("taa 4k timing resolved"),
1935 size: 16,
1936 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
1937 mapped_at_creation: false,
1938 });
1939 let read = gpu.device.create_buffer(&wgpu::BufferDescriptor {
1940 label: Some("taa 4k timing read"),
1941 size: 16,
1942 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1943 mapped_at_creation: false,
1944 });
1945 let mut timings = Vec::new();
1946 let mut turns = pfx_gpu::pace::Turns::default();
1947 for _ in 0..12 {
1948 let mut encoder = gpu.device.create_command_encoder(&Default::default());
1949 encoder.write_timestamp(&queries, 0);
1950 pass.resolve(
1951 &gpu.device,
1952 &gpu.queue,
1953 &mut encoder,
1954 &inputs,
1955 &Settings::default(),
1956 )
1957 .unwrap();
1958 encoder.write_timestamp(&queries, 1);
1959 encoder.resolve_query_set(&queries, 0..2, &resolved, 0);
1960 encoder.copy_buffer_to_buffer(&resolved, 0, &read, 0, 16);
1961 gpu.queue.submit(Some(encoder.finish()));
1962 let (send, receive) = std::sync::mpsc::channel();
1963 let slice = read.slice(..);
1964 slice.map_async(wgpu::MapMode::Read, move |result| {
1965 send.send(result).unwrap();
1966 });
1967 gpu.device
1968 .poll(wgpu::PollType::wait_indefinitely())
1969 .unwrap();
1970 receive.recv().unwrap().unwrap();
1971 let data = slice.get_mapped_range().expect("a mapped range");
1972 let start = u64::from_le_bytes(data[0..8].try_into().unwrap());
1973 let end = u64::from_le_bytes(data[8..16].try_into().unwrap());
1974 timings.push((end - start) as f64 * f64::from(gpu.queue.get_timestamp_period()) / 1e6);
1975 turns.add(timings[timings.len() - 1]);
1976 drop(data);
1977 read.unmap();
1978 }
1979 timings.sort_by(f64::total_cmp);
1980 println!("4K TAA GPU ms: {timings:?}");
1981 }
1982}