diff --git a/Cargo.toml b/Cargo.toml index 7d4dd19cef186..1ddf7c11daccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -475,6 +475,12 @@ tonemapping_luts = ["bevy_internal/tonemapping_luts", "ktx2", "bevy_image/zstd"] # Include SMAA Look Up Tables KTX2 Files smaa_luts = ["bevy_internal/smaa_luts"] +# NVIDIA Deep Learning Super Sampling +dlss = ["bevy_internal/dlss"] + +# Forcibly disable DLSS so that cargo build --all-features works without the DLSS SDK being installed. Not meant for users. +force_disable_dlss = ["bevy_internal/force_disable_dlss"] + # Enable AccessKit on Unix backends (currently only works with experimental screen readers and forks.) accesskit_unix = ["bevy_internal/accesskit_unix"] @@ -1030,7 +1036,7 @@ doc-scrape-examples = true [package.metadata.example.anti_aliasing] name = "Anti-aliasing" -description = "Compares different anti-aliasing methods" +description = "Compares different anti-aliasing techniques supported by Bevy" category = "3D Rendering" # TAA not supported by WebGL wasm = false diff --git a/crates/bevy_anti_aliasing/Cargo.toml b/crates/bevy_anti_aliasing/Cargo.toml index 764f51405911c..696aa026ee24a 100644 --- a/crates/bevy_anti_aliasing/Cargo.toml +++ b/crates/bevy_anti_aliasing/Cargo.toml @@ -13,6 +13,8 @@ trace = [] webgl = [] webgpu = [] smaa_luts = ["bevy_image/ktx2", "bevy_image/zstd"] +dlss = ["dep:dlss_wgpu", "dep:uuid", "bevy_render/raw_vulkan_init"] +force_disable_dlss = ["dlss_wgpu?/mock"] [dependencies] # bevy @@ -32,6 +34,8 @@ bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" } # other tracing = { version = "0.1", default-features = false, features = ["std"] } +dlss_wgpu = { version = "1", optional = true } +uuid = { version = "1", optional = true } [lints] workspace = true diff --git a/crates/bevy_anti_aliasing/src/dlss/extract.rs b/crates/bevy_anti_aliasing/src/dlss/extract.rs new file mode 100644 index 0000000000000..f909df6b5b996 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/extract.rs @@ -0,0 +1,29 @@ +use super::{prepare::DlssRenderContext, Dlss, DlssFeature}; +use bevy_camera::{Camera, MainPassResolutionOverride, Projection}; +use bevy_ecs::{ + query::{Has, With}, + system::{Commands, Query, ResMut}, +}; +use bevy_render::{sync_world::RenderEntity, view::Hdr, MainWorld}; + +pub fn extract_dlss( + mut commands: Commands, + mut main_world: ResMut, + cleanup_query: Query>>, +) { + let mut cameras_3d = main_world + .query_filtered::<(RenderEntity, &Camera, &Projection, Option<&mut Dlss>), With>(); + + for (entity, camera, camera_projection, mut dlss) in cameras_3d.iter_mut(&mut main_world) { + let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_)); + let mut entity_commands = commands + .get_entity(entity) + .expect("Camera entity wasn't synced."); + if dlss.is_some() && camera.is_active && has_perspective_projection { + entity_commands.insert(dlss.as_deref().unwrap().clone()); + dlss.as_mut().unwrap().reset = false; + } else if cleanup_query.get(entity) == Ok(true) { + entity_commands.remove::<(Dlss, DlssRenderContext, MainPassResolutionOverride)>(); + } + } +} diff --git a/crates/bevy_anti_aliasing/src/dlss/mod.rs b/crates/bevy_anti_aliasing/src/dlss/mod.rs new file mode 100644 index 0000000000000..50586f5d03d02 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/mod.rs @@ -0,0 +1,404 @@ +//! NVIDIA Deep Learning Super Sampling (DLSS). +//! +//! DLSS uses machine learning models to upscale and anti-alias images. +//! +//! Requires a NVIDIA RTX GPU, and the Windows/Linux Vulkan rendering backend. Does not work on other platforms. +//! +//! See https://github.com/bevyengine/dlss_wgpu for licensing requirements and setup instructions. +//! +//! # Usage +//! 1. Enable Bevy's `dlss` feature +//! 2. During app setup, insert the `DlssProjectId` resource before `DefaultPlugins` +//! 3. Check for the presence of `Option>` at runtime to see if DLSS is supported on the current machine +//! 4. Add the `Dlss` component to your camera entity, optionally setting a specific `DlssPerfQualityMode` (defaults to `Auto`) +//! 5. Optionally add sharpening via `ContrastAdaptiveSharpening` +//! 6. Custom rendering code, including third party crates, should account for the optional `MainPassResolutionOverride` to work with DLSS (see the `custom_render_phase` example) + +mod extract; +mod node; +mod prepare; + +pub use dlss_wgpu::DlssPerfQualityMode; + +use bevy_app::{App, Plugin}; +use bevy_core_pipeline::{ + core_3d::graph::{Core3d, Node3d}, + prepass::{DepthPrepass, MotionVectorPrepass}, +}; +use bevy_ecs::prelude::*; +use bevy_math::{UVec2, Vec2}; +use bevy_reflect::{reflect_remote, Reflect}; +use bevy_render::{ + camera::{MipBias, TemporalJitter}, + render_graph::{RenderGraphExt, ViewNodeRunner}, + renderer::{ + raw_vulkan_init::{AdditionalVulkanFeatures, RawVulkanInitSettings}, + RenderDevice, RenderQueue, + }, + texture::CachedTexture, + view::{prepare_view_targets, Hdr}, + ExtractSchedule, Render, RenderApp, RenderSystems, +}; +use dlss_wgpu::{ + ray_reconstruction::{ + DlssRayReconstruction, DlssRayReconstructionDepthMode, DlssRayReconstructionRoughnessMode, + }, + super_resolution::DlssSuperResolution, + FeatureSupport, +}; +use std::{ + marker::PhantomData, + ops::Deref, + sync::{Arc, Mutex}, +}; +use tracing::info; +use uuid::Uuid; + +/// Initializes DLSS support in the renderer. This must be registered before [`RenderPlugin`](bevy_render::RenderPlugin) because +/// it configures render init code. +#[derive(Default)] +pub struct DlssInitPlugin; + +impl Plugin for DlssInitPlugin { + #[allow(unsafe_code)] + fn build(&self, app: &mut App) { + let dlss_project_id = app.world().get_resource::() + .expect("The `dlss` feature is enabled, but DlssProjectId was not added to the App before DlssInitPlugin.").0; + let mut raw_vulkan_settings = app + .world_mut() + .get_resource_or_init::(); + + // SAFETY: this does not remove any instance features and only enables features that are supported + unsafe { + raw_vulkan_settings.add_create_instance_callback( + move |mut args, additional_vulkan_features| { + let mut feature_support = FeatureSupport::default(); + match dlss_wgpu::register_instance_extensions( + dlss_project_id, + &mut args, + &mut feature_support, + ) { + Ok(_) => { + if feature_support.super_resolution_supported { + additional_vulkan_features.insert::(); + } + if feature_support.ray_reconstruction_supported { + additional_vulkan_features + .insert::(); + } + } + Err(_) => {} + } + }, + ); + } + + // SAFETY: this does not remove any device features and only enables features that are supported + unsafe { + raw_vulkan_settings.add_create_device_callback( + move |mut args, adapter, additional_vulkan_features| { + let mut feature_support = FeatureSupport::default(); + match dlss_wgpu::register_device_extensions( + dlss_project_id, + &mut args, + adapter, + &mut feature_support, + ) { + Ok(_) => { + if feature_support.super_resolution_supported { + additional_vulkan_features.insert::(); + } else { + additional_vulkan_features.remove::(); + } + if feature_support.ray_reconstruction_supported { + additional_vulkan_features + .insert::(); + } else { + additional_vulkan_features + .remove::(); + } + } + Err(_) => {} + } + }, + ) + }; + } +} + +/// Enables DLSS support. This requires [`DlssInitPlugin`] to function, which must be manually registered in the correct order +/// prior to registering this plugin. +#[derive(Default)] +pub struct DlssPlugin; + +impl Plugin for DlssPlugin { + fn build(&self, app: &mut App) { + app.register_type::>() + .register_type::>(); + } + + fn finish(&self, app: &mut App) { + let (super_resolution_supported, ray_reconstruction_supported) = { + let features = app + .sub_app_mut(RenderApp) + .world() + .resource::(); + ( + features.has::(), + features.has::(), + ) + }; + if !super_resolution_supported { + return; + } + + let wgpu_device = { + let render_world = app.sub_app(RenderApp).world(); + let render_device = render_world.resource::().wgpu_device(); + render_device.clone() + }; + let project_id = app.world().get_resource::() + .expect("The `dlss` feature is enabled, but DlssProjectId was not added to the App before DlssPlugin."); + let dlss_sdk = dlss_wgpu::DlssSdk::new(project_id.0, wgpu_device); + if dlss_sdk.is_err() { + info!("DLSS is not supported on this system"); + return; + } + + app.insert_resource(DlssSuperResolutionSupported); + if ray_reconstruction_supported { + app.insert_resource(DlssRayReconstructionSupported); + } + + app.sub_app_mut(RenderApp) + .insert_resource(DlssSdk(dlss_sdk.unwrap())) + .add_systems( + ExtractSchedule, + ( + extract::extract_dlss::, + extract::extract_dlss::, + ), + ) + .add_systems( + Render, + ( + prepare::prepare_dlss::, + prepare::prepare_dlss::, + ) + .in_set(RenderSystems::ManageViews) + .before(prepare_view_targets), + ) + .add_render_graph_node::>>( + Core3d, + Node3d::DlssSuperResolution, + ) + .add_render_graph_node::>>( + Core3d, + Node3d::DlssRayReconstruction, + ) + .add_render_graph_edges( + Core3d, + ( + Node3d::EndMainPass, + Node3d::MotionBlur, // Running before DLSS reduces edge artifacts and noise + Node3d::DlssSuperResolution, + Node3d::DlssRayReconstruction, + Node3d::Bloom, + Node3d::Tonemapping, + ), + ); + } +} + +/// Camera component to enable DLSS. +#[derive(Component, Reflect, Clone)] +#[reflect(Component)] +#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass, Hdr)] +pub struct Dlss { + /// How much upscaling should be applied. + #[reflect(remote = DlssPerfQualityModeRemoteReflect)] + pub perf_quality_mode: DlssPerfQualityMode, + /// 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, + #[reflect(ignore)] + pub _phantom_data: PhantomData, +} + +impl Default for Dlss { + fn default() -> Self { + Self { + perf_quality_mode: Default::default(), + reset: Default::default(), + _phantom_data: Default::default(), + } + } +} + +pub trait DlssFeature: Reflect + Clone + Default { + type Context: Send; + + fn upscaled_resolution(context: &Self::Context) -> UVec2; + + fn render_resolution(context: &Self::Context) -> UVec2; + + fn suggested_jitter( + context: &Self::Context, + frame_number: u32, + render_resolution: UVec2, + ) -> Vec2; + + fn suggested_mip_bias(context: &Self::Context, render_resolution: UVec2) -> f32; + + fn new_context( + upscaled_resolution: UVec2, + perf_quality_mode: DlssPerfQualityMode, + feature_flags: dlss_wgpu::DlssFeatureFlags, + sdk: Arc>, + device: &RenderDevice, + queue: &RenderQueue, + ) -> Result; +} + +/// DLSS Super Resolution. +/// +/// Only available when the [`DlssSuperResolutionSupported`] resource exists. +#[derive(Reflect, Clone, Default)] +pub struct DlssSuperResolutionFeature; + +impl DlssFeature for DlssSuperResolutionFeature { + type Context = DlssSuperResolution; + + fn upscaled_resolution(context: &Self::Context) -> UVec2 { + context.upscaled_resolution() + } + + fn render_resolution(context: &Self::Context) -> UVec2 { + context.render_resolution() + } + + fn suggested_jitter( + context: &Self::Context, + frame_number: u32, + render_resolution: UVec2, + ) -> Vec2 { + context.suggested_jitter(frame_number, render_resolution) + } + + fn suggested_mip_bias(context: &Self::Context, render_resolution: UVec2) -> f32 { + context.suggested_mip_bias(render_resolution) + } + + fn new_context( + upscaled_resolution: UVec2, + perf_quality_mode: DlssPerfQualityMode, + feature_flags: dlss_wgpu::DlssFeatureFlags, + sdk: Arc>, + device: &RenderDevice, + queue: &RenderQueue, + ) -> Result { + DlssSuperResolution::new( + upscaled_resolution, + perf_quality_mode, + feature_flags, + sdk, + device.wgpu_device(), + queue.deref(), + ) + } +} + +/// DLSS Ray Reconstruction. +/// +/// Only available when the [`DlssRayReconstructionSupported`] resource exists. +#[derive(Reflect, Clone, Default)] +pub struct DlssRayReconstructionFeature; + +impl DlssFeature for DlssRayReconstructionFeature { + type Context = DlssRayReconstruction; + + fn upscaled_resolution(context: &Self::Context) -> UVec2 { + context.upscaled_resolution() + } + + fn render_resolution(context: &Self::Context) -> UVec2 { + context.render_resolution() + } + + fn suggested_jitter( + context: &Self::Context, + frame_number: u32, + render_resolution: UVec2, + ) -> Vec2 { + context.suggested_jitter(frame_number, render_resolution) + } + + fn suggested_mip_bias(context: &Self::Context, render_resolution: UVec2) -> f32 { + context.suggested_mip_bias(render_resolution) + } + + fn new_context( + upscaled_resolution: UVec2, + perf_quality_mode: DlssPerfQualityMode, + feature_flags: dlss_wgpu::DlssFeatureFlags, + sdk: Arc>, + device: &RenderDevice, + queue: &RenderQueue, + ) -> Result { + DlssRayReconstruction::new( + upscaled_resolution, + perf_quality_mode, + feature_flags, + DlssRayReconstructionRoughnessMode::Packed, + DlssRayReconstructionDepthMode::Hardware, + sdk, + device.wgpu_device(), + queue.deref(), + ) + } +} + +/// Additional textures needed as inputs for [`DlssRayReconstructionFeature`]. +#[derive(Component)] +pub struct ViewDlssRayReconstructionTextures { + pub diffuse_albedo: CachedTexture, + pub specular_albedo: CachedTexture, + pub normal_roughness: CachedTexture, + pub specular_motion_vectors: CachedTexture, +} + +#[reflect_remote(DlssPerfQualityMode)] +#[derive(Default)] +enum DlssPerfQualityModeRemoteReflect { + #[default] + Auto, + Dlaa, + Quality, + Balanced, + Performance, + UltraPerformance, +} + +#[derive(Resource)] +struct DlssSdk(Arc>); + +/// Application-specific ID for DLSS. +/// +/// See the DLSS programming guide for more info. +#[derive(Resource, Clone)] +pub struct DlssProjectId(pub Uuid); + +/// When DLSS Super Resolution is supported by the current system, this resource will exist in the main world. +/// Otherwise this resource will be absent. +#[derive(Resource, Clone, Copy)] +pub struct DlssSuperResolutionSupported; + +/// When DLSS Ray Reconstruction is supported by the current system, this resource will exist in the main world. +/// Otherwise this resource will be absent. +#[derive(Resource, Clone, Copy)] +pub struct DlssRayReconstructionSupported; diff --git a/crates/bevy_anti_aliasing/src/dlss/node.rs b/crates/bevy_anti_aliasing/src/dlss/node.rs new file mode 100644 index 0000000000000..aae59fa95b821 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/node.rs @@ -0,0 +1,165 @@ +use super::{ + prepare::DlssRenderContext, Dlss, DlssFeature, DlssRayReconstructionFeature, + DlssSuperResolutionFeature, ViewDlssRayReconstructionTextures, +}; +use bevy_camera::MainPassResolutionOverride; +use bevy_core_pipeline::prepass::ViewPrepassTextures; +use bevy_ecs::{query::QueryItem, world::World}; +use bevy_render::{ + camera::TemporalJitter, + diagnostic::RecordDiagnostics, + render_graph::{NodeRunError, RenderGraphContext, ViewNode}, + renderer::{RenderAdapter, RenderContext}, + view::ViewTarget, +}; +use dlss_wgpu::{ + ray_reconstruction::{ + DlssRayReconstructionRenderParameters, DlssRayReconstructionSpecularGuide, + }, + super_resolution::{DlssSuperResolutionExposure, DlssSuperResolutionRenderParameters}, +}; +use std::marker::PhantomData; + +#[derive(Default)] +pub struct DlssNode(PhantomData); + +impl ViewNode for DlssNode { + type ViewQuery = ( + &'static Dlss, + &'static DlssRenderContext, + &'static MainPassResolutionOverride, + &'static TemporalJitter, + &'static ViewTarget, + &'static ViewPrepassTextures, + ); + + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + ( + dlss, + dlss_context, + resolution_override, + temporal_jitter, + view_target, + prepass_textures, + ): QueryItem, + world: &World, + ) -> Result<(), NodeRunError> { + let adapter = world.resource::(); + let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = + (&prepass_textures.depth, &prepass_textures.motion_vectors) + else { + return Ok(()); + }; + + let view_target = view_target.post_process_write(); + + let render_resolution = resolution_override.0; + let render_parameters = DlssSuperResolutionRenderParameters { + color: &view_target.source, + depth: &prepass_depth_texture.texture.default_view, + motion_vectors: &prepass_motion_vectors_texture.texture.default_view, + exposure: DlssSuperResolutionExposure::Automatic, // TODO + bias: None, // TODO + dlss_output: &view_target.destination, + reset: dlss.reset, + jitter_offset: -temporal_jitter.offset, + partial_texture_size: Some(render_resolution), + motion_vector_scale: Some(-render_resolution.as_vec2()), + }; + + let diagnostics = render_context.diagnostic_recorder(); + let command_encoder = render_context.command_encoder(); + let mut dlss_context = dlss_context.context.lock().unwrap(); + + command_encoder.push_debug_group("dlss_super_resolution"); + let time_span = diagnostics.time_span(command_encoder, "dlss_super_resolution"); + + dlss_context + .render(render_parameters, command_encoder, &adapter) + .expect("Failed to render DLSS Super Resolution"); + + time_span.end(command_encoder); + command_encoder.pop_debug_group(); + + Ok(()) + } +} + +impl ViewNode for DlssNode { + type ViewQuery = ( + &'static Dlss, + &'static DlssRenderContext, + &'static MainPassResolutionOverride, + &'static TemporalJitter, + &'static ViewTarget, + &'static ViewPrepassTextures, + &'static ViewDlssRayReconstructionTextures, + ); + + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + ( + dlss, + dlss_context, + resolution_override, + temporal_jitter, + view_target, + prepass_textures, + ray_reconstruction_textures, + ): QueryItem, + world: &World, + ) -> Result<(), NodeRunError> { + let adapter = world.resource::(); + let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = + (&prepass_textures.depth, &prepass_textures.motion_vectors) + else { + return Ok(()); + }; + + let view_target = view_target.post_process_write(); + + let render_resolution = resolution_override.0; + let render_parameters = DlssRayReconstructionRenderParameters { + diffuse_albedo: &ray_reconstruction_textures.diffuse_albedo.default_view, + specular_albedo: &ray_reconstruction_textures.specular_albedo.default_view, + normals: &ray_reconstruction_textures.normal_roughness.default_view, + roughness: None, + color: &view_target.source, + depth: &prepass_depth_texture.texture.default_view, + motion_vectors: &prepass_motion_vectors_texture.texture.default_view, + specular_guide: DlssRayReconstructionSpecularGuide::SpecularMotionVectors( + &ray_reconstruction_textures + .specular_motion_vectors + .default_view, + ), + screen_space_subsurface_scattering_guide: None, // TODO + bias: None, // TODO + dlss_output: &view_target.destination, + reset: dlss.reset, + jitter_offset: -temporal_jitter.offset, + partial_texture_size: Some(render_resolution), + motion_vector_scale: Some(-render_resolution.as_vec2()), + }; + + let diagnostics = render_context.diagnostic_recorder(); + let command_encoder = render_context.command_encoder(); + let mut dlss_context = dlss_context.context.lock().unwrap(); + + command_encoder.push_debug_group("dlss_ray_reconstruction"); + let time_span = diagnostics.time_span(command_encoder, "dlss_ray_reconstruction"); + + dlss_context + .render(render_parameters, command_encoder, &adapter) + .expect("Failed to render DLSS Ray Reconstruction"); + + time_span.end(command_encoder); + command_encoder.pop_debug_group(); + + Ok(()) + } +} diff --git a/crates/bevy_anti_aliasing/src/dlss/prepare.rs b/crates/bevy_anti_aliasing/src/dlss/prepare.rs new file mode 100644 index 0000000000000..a8e88f57d012e --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/prepare.rs @@ -0,0 +1,116 @@ +use super::{Dlss, DlssFeature, DlssSdk}; +use bevy_camera::{Camera3d, CameraMainTextureUsages, MainPassResolutionOverride}; +use bevy_core_pipeline::prepass::{DepthPrepass, MotionVectorPrepass}; +use bevy_diagnostic::FrameCount; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::With, + system::{Commands, Query, Res}, +}; +use bevy_math::Vec4Swizzles; +use bevy_render::{ + camera::{MipBias, TemporalJitter}, + render_resource::TextureUsages, + renderer::{RenderDevice, RenderQueue}, + view::ExtractedView, +}; +use dlss_wgpu::{DlssFeatureFlags, DlssPerfQualityMode}; +use std::sync::{Arc, Mutex}; + +#[derive(Component)] +pub struct DlssRenderContext { + pub context: Mutex, + pub perf_quality_mode: DlssPerfQualityMode, + pub feature_flags: DlssFeatureFlags, +} + +pub fn prepare_dlss( + mut query: Query< + ( + Entity, + &ExtractedView, + &Dlss, + &mut Camera3d, + &mut CameraMainTextureUsages, + &mut TemporalJitter, + &mut MipBias, + Option<&mut DlssRenderContext>, + ), + ( + With, + With, + With, + With, + ), + >, + dlss_sdk: Res, + render_device: Res, + render_queue: Res, + frame_count: Res, + mut commands: Commands, +) { + for ( + entity, + view, + dlss, + mut camera_3d, + mut camera_main_texture_usages, + mut temporal_jitter, + mut mip_bias, + mut dlss_context, + ) in &mut query + { + camera_main_texture_usages.0 |= TextureUsages::STORAGE_BINDING; + + let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages); + depth_texture_usages |= TextureUsages::TEXTURE_BINDING; + camera_3d.depth_texture_usages = depth_texture_usages.into(); + + let upscaled_resolution = view.viewport.zw(); + + let dlss_feature_flags = DlssFeatureFlags::LowResolutionMotionVectors + | DlssFeatureFlags::InvertedDepth + | DlssFeatureFlags::HighDynamicRange + | DlssFeatureFlags::AutoExposure; // TODO + + match dlss_context.as_deref_mut() { + Some(dlss_context) + if upscaled_resolution + == F::upscaled_resolution(&dlss_context.context.lock().unwrap()) + && dlss.perf_quality_mode == dlss_context.perf_quality_mode + && dlss_feature_flags == dlss_context.feature_flags => + { + let dlss_context = dlss_context.context.lock().unwrap(); + let render_resolution = F::render_resolution(&dlss_context); + temporal_jitter.offset = + F::suggested_jitter(&dlss_context, frame_count.0, render_resolution); + } + _ => { + let dlss_context = F::new_context( + upscaled_resolution, + dlss.perf_quality_mode, + dlss_feature_flags, + Arc::clone(&dlss_sdk.0), + &render_device, + &render_queue, + ) + .expect("Failed to create DlssRenderContext"); + + let render_resolution = F::render_resolution(&dlss_context); + temporal_jitter.offset = + F::suggested_jitter(&dlss_context, frame_count.0, render_resolution); + mip_bias.0 = F::suggested_mip_bias(&dlss_context, render_resolution); + + commands.entity(entity).insert(( + DlssRenderContext:: { + context: Mutex::new(dlss_context), + perf_quality_mode: dlss.perf_quality_mode, + feature_flags: dlss_feature_flags, + }, + MainPassResolutionOverride(render_resolution), + )); + } + } + } +} diff --git a/crates/bevy_anti_aliasing/src/lib.rs b/crates/bevy_anti_aliasing/src/lib.rs index 12b7982cb57a1..40d5085ddcba5 100644 --- a/crates/bevy_anti_aliasing/src/lib.rs +++ b/crates/bevy_anti_aliasing/src/lib.rs @@ -1,5 +1,4 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevy.org/assets/icon.png", @@ -13,6 +12,8 @@ use smaa::SmaaPlugin; use taa::TemporalAntiAliasPlugin; pub mod contrast_adaptive_sharpening; +#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] +pub mod dlss; pub mod fxaa; pub mod smaa; pub mod taa; @@ -21,6 +22,13 @@ pub mod taa; pub struct AntiAliasingPlugin; impl Plugin for AntiAliasingPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_plugins((FxaaPlugin, SmaaPlugin, TemporalAntiAliasPlugin, CasPlugin)); + app.add_plugins(( + FxaaPlugin, + SmaaPlugin, + TemporalAntiAliasPlugin, + CasPlugin, + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + dlss::DlssPlugin, + )); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 5351c08726f9f..95b72eb6f6278 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -29,8 +29,10 @@ pub mod graph { EndMainPass, Wireframe, LateDownsampleDepth, - Taa, MotionBlur, + Taa, + DlssSuperResolution, + DlssRayReconstruction, Bloom, AutoExposure, DepthOfField, diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index f7b98bef606ac..eb82ca9a0d242 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -73,6 +73,12 @@ bluenoise_texture = ["bevy_pbr?/bluenoise_texture"] # Include SMAA LUT KTX2 Files smaa_luts = ["bevy_anti_aliasing/smaa_luts"] +# NVIDIA Deep Learning Super Sampling +dlss = ["bevy_anti_aliasing/dlss"] + +# Forcibly disable DLSS so that cargo build --all-features works without the DLSS SDK being installed. Not meant for users. +force_disable_dlss = ["bevy_anti_aliasing?/force_disable_dlss"] + # Audio format support (vorbis is enabled by default) flac = ["bevy_audio/flac"] mp3 = ["bevy_audio/mp3"] @@ -184,7 +190,7 @@ webgpu = [ "bevy_sprite?/webgpu", ] -# enable systems that allow for automated testing on CI +# Enable systems that allow for automated testing on CI bevy_ci_testing = ["bevy_dev_tools/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 5a7b4773e4d4a..ead5460a5fddb 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -27,6 +27,8 @@ plugin_group! { bevy_scene:::ScenePlugin, #[cfg(feature = "bevy_winit")] bevy_winit:::WinitPlugin, + #[custom(cfg(all(feature = "dlss", not(feature = "force_disable_dlss"))))] + bevy_anti_aliasing::dlss:::DlssInitPlugin, #[cfg(feature = "bevy_render")] bevy_render:::RenderPlugin, // NOTE: Load this after renderer initialization so that it knows about the supported diff --git a/crates/bevy_render/src/renderer/raw_vulkan_init.rs b/crates/bevy_render/src/renderer/raw_vulkan_init.rs index 2dee01d914f06..f9d1ded7a303c 100644 --- a/crates/bevy_render/src/renderer/raw_vulkan_init.rs +++ b/crates/bevy_render/src/renderer/raw_vulkan_init.rs @@ -136,11 +136,15 @@ pub(crate) enum CreateRawVulkanDeviceError { pub struct AdditionalVulkanFeatures(HashSet); impl AdditionalVulkanFeatures { - pub fn register(&mut self) { + pub fn insert(&mut self) { self.0.insert(TypeId::of::()); } pub fn has(&self) -> bool { self.0.contains(&TypeId::of::()) } + + pub fn remove(&mut self) { + self.0.remove(&TypeId::of::()); + } } diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index cc1340658aa84..ac0ef457022f3 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -54,6 +54,7 @@ impl Plugin for SolariLightingPlugin { ); return; } + render_app .add_systems(ExtractSchedule, extract_solari_lighting) .add_systems( diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 967f1eaf5cd8f..7247362b3a577 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -83,6 +83,7 @@ The default feature set enables most of the expected features of a game engine, |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| |default_no_std|Recommended defaults for no_std applications| |detailed_trace|Enable detailed trace event logging. These trace events are expensive even when off, thus they require compile time opt-in| +|dlss|NVIDIA Deep Learning Super Sampling| |dynamic_linking|Force dynamic linking, which improves iterative compile times| |embedded_watcher|Enables watching in memory asset providers for Bevy Asset hot-reloading| |experimental_bevy_feathers|Feathers widget collection.| @@ -91,6 +92,7 @@ The default feature set enables most of the expected features of a game engine, |ff|Farbfeld image format support| |file_watcher|Enables watching the filesystem for Bevy Asset hot-reloading| |flac|FLAC audio format support| +|force_disable_dlss|Forcibly disable DLSS so that cargo build --all-features works without the DLSS SDK being installed. Not meant for users.| |ghost_nodes|Experimental support for nodes that are ignored for UI layouting| |gif|GIF image format support| |glam_assert|Enable assertions to check the validity of parameters passed to glam| diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index 74d03852bb96f..c132d9abf62cc 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -1,4 +1,4 @@ -//! This example compares MSAA (Multi-Sample Anti-aliasing), FXAA (Fast Approximate Anti-aliasing), and TAA (Temporal Anti-aliasing). +//! Compares different anti-aliasing techniques supported by Bevy. use std::{f32::consts::PI, fmt::Write}; @@ -21,12 +21,24 @@ use bevy::{ }, }; +#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] +use bevy::anti_aliasing::dlss::{ + Dlss, DlssPerfQualityMode, DlssProjectId, DlssSuperResolutionSupported, +}; + fn main() { - App::new() - .add_plugins(DefaultPlugins) + let mut app = App::new(); + + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + app.insert_resource(DlssProjectId(bevy_asset::uuid::uuid!( + "5417916c-0291-4e3f-8f65-326c1858ab96" // Don't copy paste this - generate your own UUID! + ))); + + app.add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems(Update, (modify_aa, modify_sharpening, update_ui)) - .run(); + .add_systems(Update, (modify_aa, modify_sharpening, update_ui)); + + app.run(); } type TaaComponents = ( @@ -37,9 +49,31 @@ type TaaComponents = ( MotionVectorPrepass, ); +#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] +type DlssComponents = ( + Dlss, + TemporalJitter, + MipBias, + DepthPrepass, + MotionVectorPrepass, +); +#[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] +type DlssComponents = (); + fn modify_aa( keys: Res>, - camera: Single< + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] camera: Single< + ( + Entity, + Option<&mut Fxaa>, + Option<&mut Smaa>, + Option<&TemporalAntiAliasing>, + &mut Msaa, + Option<&mut Dlss>, + ), + With, + >, + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] camera: Single< ( Entity, Option<&mut Fxaa>, @@ -49,8 +83,14 @@ fn modify_aa( ), With, >, + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_supported: Option< + Res, + >, mut commands: Commands, ) { + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + let (camera_entity, fxaa, smaa, taa, mut msaa, dlss) = camera.into_inner(); + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] let (camera_entity, fxaa, smaa, taa, mut msaa) = camera.into_inner(); let mut camera = commands.entity(camera_entity); @@ -60,7 +100,8 @@ fn modify_aa( camera .remove::() .remove::() - .remove::(); + .remove::() + .remove::(); } // MSAA @@ -68,7 +109,8 @@ fn modify_aa( camera .remove::() .remove::() - .remove::(); + .remove::() + .remove::(); *msaa = Msaa::Sample4; } @@ -92,6 +134,7 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(Fxaa::default()); } @@ -125,6 +168,7 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(Smaa::default()); } @@ -150,8 +194,43 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(TemporalAntiAliasing::default()); } + + // DLSS + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if keys.just_pressed(KeyCode::Digit6) && dlss.is_none() && dlss_supported.is_some() { + *msaa = Msaa::Off; + camera + .remove::() + .remove::() + .remove::() + .insert(Dlss::default()); + } + + // DLSS Settings + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if let Some(mut dlss) = dlss { + if keys.just_pressed(KeyCode::KeyZ) { + dlss.perf_quality_mode = DlssPerfQualityMode::Auto; + } + if keys.just_pressed(KeyCode::KeyX) { + dlss.perf_quality_mode = DlssPerfQualityMode::UltraPerformance; + } + if keys.just_pressed(KeyCode::KeyC) { + dlss.perf_quality_mode = DlssPerfQualityMode::Performance; + } + if keys.just_pressed(KeyCode::KeyV) { + dlss.perf_quality_mode = DlssPerfQualityMode::Balanced; + } + if keys.just_pressed(KeyCode::KeyB) { + dlss.perf_quality_mode = DlssPerfQualityMode::Quality; + } + if keys.just_pressed(KeyCode::KeyN) { + dlss.perf_quality_mode = DlssPerfQualityMode::Dlaa; + } + } } fn modify_sharpening( @@ -179,7 +258,18 @@ fn modify_sharpening( } fn update_ui( - camera: Single< + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] camera: Single< + ( + Option<&Fxaa>, + Option<&Smaa>, + Option<&TemporalAntiAliasing>, + &ContrastAdaptiveSharpening, + &Msaa, + Option<&Dlss>, + ), + With, + >, + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] camera: Single< ( Option<&Fxaa>, Option<&Smaa>, @@ -190,22 +280,37 @@ fn update_ui( With, >, mut ui: Single<&mut Text>, + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_supported: Option< + Res, + >, ) { + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + let (fxaa, smaa, taa, cas, msaa, dlss) = *camera; + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] let (fxaa, smaa, taa, cas, msaa) = *camera; let ui = &mut ui.0; *ui = "Antialias Method\n".to_string(); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + let dlss_none = dlss.is_none(); + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] + let dlss_none = true; + draw_selectable_menu_item( ui, "No AA", '1', - *msaa == Msaa::Off && fxaa.is_none() && taa.is_none() && smaa.is_none(), + *msaa == Msaa::Off && fxaa.is_none() && taa.is_none() && smaa.is_none() && dlss_none, ); draw_selectable_menu_item(ui, "MSAA", '2', *msaa != Msaa::Off); draw_selectable_menu_item(ui, "FXAA", '3', fxaa.is_some()); draw_selectable_menu_item(ui, "SMAA", '4', smaa.is_some()); draw_selectable_menu_item(ui, "TAA", '5', taa.is_some()); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if dlss_supported.is_some() { + draw_selectable_menu_item(ui, "DLSS", '6', dlss.is_some()); + } if *msaa != Msaa::Off { ui.push_str("\n----------\n\nSample Count\n"); @@ -241,6 +346,28 @@ fn update_ui( draw_selectable_menu_item(ui, "Ultra", 'R', smaa.preset == SmaaPreset::Ultra); } + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if let Some(dlss) = dlss { + let pqm = dlss.perf_quality_mode; + ui.push_str("\n----------\n\nQuality\n"); + draw_selectable_menu_item(ui, "Auto", 'Z', pqm == DlssPerfQualityMode::Auto); + draw_selectable_menu_item( + ui, + "UltraPerformance", + 'X', + pqm == DlssPerfQualityMode::UltraPerformance, + ); + draw_selectable_menu_item( + ui, + "Performance", + 'C', + pqm == DlssPerfQualityMode::Performance, + ); + draw_selectable_menu_item(ui, "Balanced", 'V', pqm == DlssPerfQualityMode::Balanced); + draw_selectable_menu_item(ui, "Quality", 'B', pqm == DlssPerfQualityMode::Quality); + draw_selectable_menu_item(ui, "DLAA", 'N', pqm == DlssPerfQualityMode::Dlaa); + } + ui.push_str("\n----------\n\n"); draw_selectable_menu_item(ui, "Sharpening", '0', cas.enabled); @@ -260,7 +387,7 @@ fn setup( ) { // Plane commands.spawn(( - Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0))), + Mesh3d(meshes.add(Plane3d::default().mesh().size(20.0, 20.0))), MeshMaterial3d(materials.add(Color::srgb(0.1, 0.2, 0.1))), )); diff --git a/examples/README.md b/examples/README.md index 51f59f0a9ebe3..6b8cd0f25114b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -142,7 +142,7 @@ Example | Description [3D Viewport To World](../examples/3d/3d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world` method [Animated Material](../examples/3d/animated_material.rs) | Shows how to animate material properties [Anisotropy](../examples/3d/anisotropy.rs) | Displays an example model with anisotropy -[Anti-aliasing](../examples/3d/anti_aliasing.rs) | Compares different anti-aliasing methods +[Anti-aliasing](../examples/3d/anti_aliasing.rs) | Compares different anti-aliasing techniques supported by Bevy [Atmosphere](../examples/3d/atmosphere.rs) | A scene showcasing pbr atmospheric scattering [Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect [Auto Exposure](../examples/3d/auto_exposure.rs) | A scene showcasing auto exposure diff --git a/release-content/release-notes/dlss.md b/release-content/release-notes/dlss.md new file mode 100644 index 0000000000000..6d67739da2bed --- /dev/null +++ b/release-content/release-notes/dlss.md @@ -0,0 +1,36 @@ +--- +title: Deep Learning Super Sampling (DLSS) +authors: ["@JMS55", "@cart"] +pull_requests: [19864, 19817, 20565] +--- + +For users with NVIDIA RTX GPUs, Bevy now offers yet another form of anti-aliasing: DLSS. + +Try it out by running Bevy's anti_aliasing example: `cargo run --example anti_aliasing --features dlss --release` (after performing setup from ). + +Additionally, we've open sourced as a standalone crate to help other wgpu-based renderers integrate DLSS. + +Compared to Bevy's built-in TAA, DLSS: + +* Is much higher quality +* Supports upscaling in addition to anti-aliasing, leading to much cheaper render times, particularly when used with GPU-heavy features like Bevy Solari +* Requires a NVIDIA RTX GPU +* Requires running via the Vulkan backend on Windows/Linux (no macOS, web, or mobile support) + +To use DLSS in your app: + +* See for licensing requirements and setup instructions +* Enable Bevy's `dlss` feature +* Insert the `DlssProjectId` resource before `DefaultPlugins` when setting up your app +* Check for the presence of `Option>` at runtime to see if DLSS is supported on the current machine +* Add the `Dlss` component to your camera entity, optionally setting a specific `DlssPerfQualityMode` (defaults to `Auto`) +* Optionally add sharpening via `ContrastAdaptiveSharpening` +* Custom rendering code, including third party crates, should account for the optional `MainPassResolutionOverride` to work with DLSS (see the `custom_render_phase` example) + +Note that DLSS integration is expected to have some bugs in this release related to certain rendering effects not respecting upscaling settings, and possible issues with transparencies or camera exposure. Please report any bugs encountered. + +Other temporal upscalers like AMD's FidelityFX™ Super Resolution (FSR), Intel's Xe Super Sampling XeSS (XeSS), and Apple's MTLFXTemporalScaler are not integrated in this release. However they all use similar APIs, and would not be a challenge to integrate in future releases. + +Support for other swapchain-related features like frame interpolation/extrapolation, latency reduction, or dynamic resolution scaling are not currently planned, but support for DLSS Ray Reconstruction for use in Bevy Solari _is_ planned for a future release. + +Special thanks to @cwfitzgerald for helping with the [`wgpu`](https://github.com/gfx-rs/wgpu) backend interop APIs.