diff --git a/native/shared/src/renderer/mod.rs b/native/shared/src/renderer/mod.rs index 45337402..bc9b8ac6 100644 --- a/native/shared/src/renderer/mod.rs +++ b/native/shared/src/renderer/mod.rs @@ -784,6 +784,7 @@ pub struct Renderer { pub taa_depth_history_views: [wgpu::TextureView; 2], pub taa_current_idx: usize, pub taa_pipeline: LazyRenderPipeline, + taa_pipeline_fast: LazyRenderPipeline, pub taa_layout: wgpu::BindGroupLayout, pub taa_uniform_buffer: wgpu::Buffer, taa_bind_group_cache: [Option; 2], @@ -805,6 +806,7 @@ pub struct Renderer { pub taa_enabled: bool, /// TAA also upscales below render scale 1; see `set_temporal_upscale`. temporal_upscale: bool, + taa_fast: bool, // fs_main_fast; see set_taa_fast /// Perspective/orthographic clip planes for begin_mode_3d. Defaults keep /// the historical 0.01..1000; a scene viewed from ~15 units away needs a /// larger near plane, or surfaces a millimetre apart z-fight. @@ -4814,49 +4816,53 @@ impl Renderer { bind_group_layouts: &[Some(&taa_layout)], immediate_size: 0, }); - let taa_pipeline = LazyRenderPipeline::new( - &device, - &wgpu::RenderPipelineDescriptor { - label: Some("taa_pipeline"), - layout: Some(&taa_pl_layout), - vertex: wgpu::VertexState { - module: &taa_shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &taa_shader, - entry_point: Some("fs_main"), - targets: &[ - Some(wgpu::ColorTargetState { - format: HDR_FORMAT, - blend: None, - write_mask: wgpu::ColorWrites::ALL, - }), - Some(wgpu::ColorTargetState { - format: TAA_DEPTH_HISTORY_FORMAT, - blend: None, - write_mask: wgpu::ColorWrites::RED | wgpu::ColorWrites::GREEN, - }), - ], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, + let create_taa_pipeline = |label: &'static str, entry: &'static str| { + LazyRenderPipeline::new( + &device, + &wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(&taa_pl_layout), + vertex: wgpu::VertexState { + module: &taa_shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &taa_shader, + entry_point: Some(entry), + targets: &[ + Some(wgpu::ColorTargetState { + format: HDR_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + }), + Some(wgpu::ColorTargetState { + format: TAA_DEPTH_HISTORY_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::RED | wgpu::ColorWrites::GREEN, + }), + ], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview_mask: None, - cache: None, - }, - ); + ) + }; + let taa_pipeline = create_taa_pipeline("taa_pipeline", "fs_main"); + let taa_pipeline_fast = create_taa_pipeline("taa_pipeline_fast", "fs_main_fast"); let taa_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("taa_uniform_buffer"), size: std::mem::size_of::() as u64, @@ -8277,6 +8283,7 @@ impl Renderer { taa_depth_history_views, taa_current_idx: 0, taa_pipeline, + taa_pipeline_fast, taa_layout, taa_uniform_buffer, taa_bind_group_cache: [None, None], @@ -8289,6 +8296,7 @@ impl Renderer { taa_frame_index: 0, taa_enabled: true, temporal_upscale: !cfg!(target_os = "android"), + taa_fast: cfg!(target_os = "android"), camera_near: 0.01, camera_far: 1000.0, taa_history_valid: false, diff --git a/native/shared/src/renderer/postfx_chain.rs b/native/shared/src/renderer/postfx_chain.rs index 817fc947..e3aab34f 100644 --- a/native/shared/src/renderer/postfx_chain.rs +++ b/native/shared/src/renderer/postfx_chain.rs @@ -1062,6 +1062,8 @@ impl Renderer { .as_ref() .expect("reactive TAA pipeline initialized"), ); + } else if self.taa_fast { + pass.set_pipeline(&self.taa_pipeline_fast); } else { pass.set_pipeline(&self.taa_pipeline); } diff --git a/native/shared/src/renderer/quality_preset.rs b/native/shared/src/renderer/quality_preset.rs index 8b7f53e4..ec55bdc5 100644 --- a/native/shared/src/renderer/quality_preset.rs +++ b/native/shared/src/renderer/quality_preset.rs @@ -128,6 +128,19 @@ impl Renderer { } } + /// The fast TAA: the current pixel as rendered, history clamped to its + /// 5-pixel cross, one bilinear history fetch (`fs_main_fast`), instead of + /// the full reconstruction with its provenance and detail-lock + /// heuristics. The low tier of temporal AA, for TAA resolving at the + /// render resolution (`set_temporal_upscale(false)`). Default on Android + /// (Pixel 4a: 16 -> 4 ms at 0.5 render scale). + pub fn set_taa_fast(&mut self, on: bool) { + if on != self.taa_fast { + self.taa_fast = on; + self.taa_history_valid = false; + } + } + /// The sun shadow's filter: `true` takes 4 hardware-PCF taps (a /// 3x3-texel tent: the filter mobile renderers default to, a quarter of /// the cost), `false` the 16-tap Poisson disc. Default 4-tap on Android. diff --git a/native/shared/src/renderer/shaders/post.rs b/native/shared/src/renderer/shaders/post.rs index bf97df57..eb7ab1b5 100644 --- a/native/shared/src/renderer/shaders/post.rs +++ b/native/shared/src/renderer/shaders/post.rs @@ -1383,6 +1383,78 @@ fn fs_main(in: VsOut) -> TaaOut { ), ); } + +// The fast TAA (set_taa_fast; the mobile default), for TAA resolving at its +// input's resolution: the current pixel as rendered, history clamped to the +// variance of the pixel and its 4-neighbour cross in YCoCg, one bilinear +// history fetch reprojected by the closest-depth velocity (the sky by its +// direction), and the same blend weight. The low-quality tier of desktop +// temporal AA (Unreal's lowest r.TemporalAA.Quality): without the +// reconstruction filters, provenance and lock heuristics above, a fraction of +// their registers and fetches. Writes the same targets; the provenance keeps +// the depth key and no confidence. +@fragment +fn fs_main_fast(in: VsOut) -> TaaOut { + let dims = vec2(textureDimensions(composed_tex)); + let max_coord = dims - vec2(1); + let coord = clamp(vec2(in.uv * vec2(dims)), vec2(0), max_coord); + let current_sample = textureLoad(composed_tex, coord, 0); + + var closest_coord = coord; + var depth = textureLoad(depth_tex, coord, 0); + var m1 = rgb_to_ycocg(current_sample.rgb); + var m2 = m1 * m1; + for (var i = 0; i < 4; i = i + 1) { + let c = clamp(coord + cross_offset(i), vec2(0), max_coord); + let d = textureLoad(depth_tex, c, 0); + if (d < depth) { + depth = d; + closest_coord = c; + } + let s = rgb_to_ycocg(textureLoad(composed_tex, c, 0).rgb); + m1 = m1 + s; + m2 = m2 + s * s; + } + let mean = m1 / 5.0; + let stddev = sqrt(max(m2 / 5.0 - mean * mean, vec3(0.0)) * (5.0 / 3.0)); + + let ndc = vec4(in.uv.x * 2.0 - 1.0, (1.0 - in.uv.y) * 2.0 - 1.0, depth, 1.0); + let world_h = u.inv_vp * ndc; + let perspective = u.params.w > 0.0; + let current_depth_key = select( + depth, + select(1.0 / max(abs(world_h.w), 0.000001), 10000.0, depth >= 0.9999), + perspective, + ); + let vel = textureLoad(velocity_tex, closest_coord, 0).rg; + var prev_uv = vec2(in.uv.x - vel.x, in.uv.y + vel.y); + if (depth >= 0.9999) { + let prev_clip = u.prev_vp * vec4(world_h.xyz, 0.0); + prev_uv = in.uv; + if (prev_clip.w > 0.00001) { + let prev_ndc = prev_clip.xyz / prev_clip.w; + prev_uv = vec2(prev_ndc.x * 0.5 + 0.5, 1.0 - (prev_ndc.y * 0.5 + 0.5)); + } + } + + var alpha = abs(u.params.x); + var history_sample = current_sample; + if (any(prev_uv < vec2(0.0)) || any(prev_uv > vec2(1.0))) { + alpha = 1.0; + } else { + history_sample = textureSampleLevel(history_tex, history_samp, prev_uv, 0.0); + } + let gamma = mix(1.25, 0.25, smoothstep(0.0005, 0.008, length(vel))); + let h = rgb_to_ycocg(history_sample.rgb); + let lo = mean - stddev * vec3(gamma, gamma * 3.0, gamma * 3.0); + let hi = mean + stddev * vec3(gamma, gamma * 3.0, gamma * 3.0); + let clamped_history = ycocg_to_rgb(clamp(h, lo, hi)); + let blended = max(mix(clamped_history, current_sample.rgb, alpha), vec3(0.0)); + return TaaOut( + vec4(blended, mix(history_sample.a, current_sample.a, alpha)), + vec2(current_depth_key, pack_temporal_history(0.0, 0.0, 0.0)), + ); +} ", );