From 55927c2f582f0c08a3f63d1ef42e00ab6d7bfd22 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 27 Oct 2025 19:01:59 -0400 Subject: [PATCH] WIP: FSR3 --- crates/bevy_anti_alias/Cargo.toml | 3 + crates/bevy_anti_alias/src/fsr3/mod.rs | 549 +++++++++++++++++++ crates/bevy_anti_alias/src/lib.rs | 5 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 1 + examples/3d/anti_aliasing.rs | 44 +- 5 files changed, 596 insertions(+), 6 deletions(-) create mode 100644 crates/bevy_anti_alias/src/fsr3/mod.rs diff --git a/crates/bevy_anti_alias/Cargo.toml b/crates/bevy_anti_alias/Cargo.toml index 8fbcf88fdc23e..81a92d57e0419 100644 --- a/crates/bevy_anti_alias/Cargo.toml +++ b/crates/bevy_anti_alias/Cargo.toml @@ -31,11 +31,14 @@ bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.18.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.18.0-dev" } +bevy_time = { path = "../bevy_time", version = "0.18.0-dev" } # other tracing = { version = "0.1", default-features = false, features = ["std"] } dlss_wgpu = { version = "1", optional = true } uuid = { version = "1", optional = true } +wgpu-ffx = { git = "https://github.com/cwfitzgerald/wgpu-ffx", rev = "ad7ce17c" } +wgpu = { version = "26" } [lints] workspace = true diff --git a/crates/bevy_anti_alias/src/fsr3/mod.rs b/crates/bevy_anti_alias/src/fsr3/mod.rs new file mode 100644 index 0000000000000..29e7c52ff7c7d --- /dev/null +++ b/crates/bevy_anti_alias/src/fsr3/mod.rs @@ -0,0 +1,549 @@ +//! AMD FidelityFX Super Resolution 3 (FSR3). +//! +//! FSR3 is a temporal upscaling and anti-aliasing technique that uses +//! machine learning-based upscaling to render at a lower resolution +//! and upscale to the target resolution. +//! +//! # Usage +//! 1. Add the `Fsr3` component to your camera entity +//! 2. Optionally set a specific `Fsr3QualityMode` (defaults to `Quality`) +//! 3. The camera must have HDR enabled +//! 4. Optionally adjust sharpening settings +//! +//! # Example +//! ```ignore +//! commands.spawn(( +//! Camera3d::default(), +//! Fsr3 { +//! quality_mode: Fsr3QualityMode::Quality, +//! enable_sharpening: true, +//! sharpness: 0.8, +//! ..default() +//! }, +//! )); +//! ``` + +use bevy_app::{App, Plugin}; +use bevy_camera::{ + Camera, Camera3d, CameraMainTextureUsages, MainPassResolutionOverride, Projection, +}; +use bevy_core_pipeline::{ + core_3d::graph::{Core3d, Node3d}, + prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, +}; +use bevy_diagnostic::FrameCount; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::{QueryItem, With}, + reflect::ReflectComponent, + schedule::IntoScheduleConfigs, + system::{Commands, Query, Res, ResMut}, + world::World, +}; +use bevy_image::ToExtents; +use bevy_math::{UVec2, Vec2, Vec4Swizzles}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectFromReflect}; +use bevy_render::{ + camera::{ExtractedCamera, MipBias, TemporalJitter}, + render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, + render_resource::{ + Buffer, BufferDescriptor, BufferUsages, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, + }, + renderer::{RenderDevice, RenderQueue}, + sync_component::SyncComponentPlugin, + sync_world::RenderEntity, + texture::{CachedTexture, TextureCache}, + view::{ExtractedView, Hdr, Msaa, ViewTarget}, + ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems, +}; +use bevy_time::Time; +use std::{mem::size_of, sync::Mutex}; +use tracing::warn; +use wgpu_ffx::{FsrContext, FsrContextFlags, FsrContextInfo, FsrDispatchFlags, FsrDispatchInfo}; + +/// Plugin for AMD FidelityFX Super Resolution 3. +/// +/// See [`Fsr3`] for more details. +#[derive(Default)] +pub struct Fsr3Plugin; + +impl Plugin for Fsr3Plugin { + fn build(&self, app: &mut App) { + app.add_plugins(SyncComponentPlugin::::default()); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .add_systems(ExtractSchedule, extract_fsr3_settings) + .add_systems( + Render, + ( + prepare_fsr3_jitter_and_context.in_set(RenderSystems::ManageViews), + prepare_fsr3_textures.in_set(RenderSystems::PrepareResources), + ), + ) + .add_render_graph_node::>(Core3d, Node3d::Fsr3) + .add_render_graph_edges( + Core3d, + ( + Node3d::EndMainPass, + Node3d::MotionBlur, // Running before FSR3 reduces edge artifacts and noise + Node3d::Fsr3, + Node3d::Bloom, + Node3d::Tonemapping, + ), + ); + } +} + +/// Component to apply FSR3 temporal upscaling and anti-aliasing to a 3D camera. +/// +/// FSR3 is AMD's temporal upscaling solution that renders at a lower resolution +/// and upscales to the target resolution using temporal data. +/// +/// # Tradeoffs +/// +/// Pros: +/// * Much better performance by rendering at lower resolution +/// * High quality temporal anti-aliasing +/// * Works on AMD, NVIDIA, and Intel GPUs +/// * Includes optional sharpening pass +/// +/// Cons: +/// * Requires HDR rendering +/// * May exhibit ghosting artifacts with fast motion +/// * Requires accurate motion vectors +/// +/// # Usage Notes +/// +/// Any camera with this component must have HDR enabled (the `Hdr` component). +/// The camera must also disable [`Msaa`] by setting it to [`Msaa::Off`]. +/// +/// FSR3 requires accurate motion vectors for everything on screen. Custom +/// rendering code must write proper motion vectors to work correctly with FSR3. +#[derive(Component, Reflect, Clone)] +#[reflect(Component, Default, FromReflect)] +#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass, Hdr)] +pub struct Fsr3 { + /// Quality mode controlling the render resolution and upscaling ratio. + #[reflect(default)] + pub quality_mode: Fsr3QualityMode, + + /// Set to true to delete the saved temporal history (past frames). + /// + /// Useful for preventing ghosting when the history is no longer + /// representative of the current frame, such as in sudden camera cuts. + /// + /// After setting this to true, it will automatically be toggled + /// back to false at the end of the frame. + pub reset: bool, + + /// Enable the sharpening pass. + pub enable_sharpening: bool, + + /// Sharpening strength (0.0 = no sharpening, 1.0 = maximum sharpening). + pub sharpness: f32, +} + +impl Default for Fsr3 { + fn default() -> Self { + Self { + quality_mode: Fsr3QualityMode::Quality, + reset: true, + enable_sharpening: true, + sharpness: 0.8, + } + } +} + +/// Quality modes for FSR3, controlling the render resolution and upscaling ratio. +#[derive(Reflect, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum Fsr3QualityMode { + /// Native rendering with no upscaling (1.0x). + /// Provides maximum quality but no performance benefit. + NativeAA, + + /// Quality mode with 1.5x upscaling. + /// Best balance of quality and performance for most use cases. + #[default] + Quality, + + /// Balanced mode with 1.7x upscaling. + /// Good quality with better performance than Quality mode. + Balanced, + + /// Performance mode with 2.0x upscaling. + /// Significant performance improvement with acceptable quality loss. + Performance, + + /// Ultra Performance mode with 3.0x upscaling. + /// Maximum performance improvement with most quality loss. + UltraPerformance, +} + +impl Fsr3QualityMode { + /// Get the upscaling ratio for this quality mode. + pub fn scale_factor(self) -> f32 { + match self { + Fsr3QualityMode::NativeAA => 1.0, + Fsr3QualityMode::Quality => 1.5, + Fsr3QualityMode::Balanced => 1.7, + Fsr3QualityMode::Performance => 2.0, + Fsr3QualityMode::UltraPerformance => 3.0, + } + } + + /// Calculate the render resolution from the target (upscaled) resolution. + pub fn render_resolution(self, upscale_resolution: UVec2) -> UVec2 { + let scale = self.scale_factor(); + UVec2::new( + (upscale_resolution.x as f32 / scale).round() as u32, + (upscale_resolution.y as f32 / scale).round() as u32, + ) + } +} + +/// Render context for FSR3, stored per camera. +#[derive(Component)] +struct Fsr3RenderContext { + context: Mutex, + quality_mode: Fsr3QualityMode, + max_upscale_size: [u32; 2], +} + +/// Textures needed for FSR3 rendering. +#[derive(Component)] +struct Fsr3Textures { + dilated_depth: CachedTexture, + dilated_motion_vectors: CachedTexture, + reconstructed_previous_depth: Buffer, +} + +/// Render graph node for FSR3. +#[derive(Default)] +struct Fsr3Node; + +impl ViewNode for Fsr3Node { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewTarget, + &'static ViewPrepassTextures, + &'static Fsr3, + &'static Fsr3RenderContext, + &'static Fsr3Textures, + &'static TemporalJitter, + &'static MainPassResolutionOverride, + &'static Msaa, + ); + + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut bevy_render::renderer::RenderContext, + ( + camera, + view, + view_target, + prepass_textures, + fsr3, + fsr3_context, + fsr3_textures, + temporal_jitter, + resolution_override, + msaa, + ): QueryItem, + world: &World, + ) -> Result<(), NodeRunError> { + if *msaa != Msaa::Off { + warn!("FSR3 requires MSAA to be disabled"); + return Ok(()); + } + + let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = + (&prepass_textures.motion_vectors, &prepass_textures.depth) + else { + return Ok(()); + }; + + let render_queue = world.resource::(); + let time = world.resource::