diff --git a/Cargo.toml b/Cargo.toml index f047040bdc9f7..6390ceeed7777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -467,6 +467,9 @@ 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"] + # Enable AccessKit on Unix backends (currently only works with experimental screen readers and forks.) accesskit_unix = ["bevy_internal/accesskit_unix"] @@ -1012,7 +1015,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 8c32d70fff0bb..29cab2621c5f7 100644 --- a/crates/bevy_anti_aliasing/Cargo.toml +++ b/crates/bevy_anti_aliasing/Cargo.toml @@ -13,6 +13,7 @@ trace = [] webgl = [] webgpu = [] smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"] +dlss = ["dep:dlss_wgpu"] [dependencies] # bevy @@ -30,6 +31,7 @@ bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" } # other tracing = { version = "0.1", default-features = false, features = ["std"] } +dlss_wgpu = { git = "https://github.com/bevyengine/dlss_wgpu", 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..418c2fbf89458 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/extract.rs @@ -0,0 +1,29 @@ +use super::{prepare::ViewDlssContext, Dlss}; +use bevy_ecs::{ + query::With, + system::{Commands, ResMut}, +}; +use bevy_render::{ + camera::{Camera, MainPassResolutionOverride, Projection}, + sync_world::RenderEntity, + view::Hdr, + MainWorld, +}; + +pub fn extract_dlss(mut commands: Commands, mut main_world: ResMut) { + 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 { + entity_commands.remove::<(Dlss, ViewDlssContext, 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..1a1ec64e06e74 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/mod.rs @@ -0,0 +1,116 @@ +//! 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; + +use bevy_app::{App, Plugin}; +use bevy_core_pipeline::{ + core_3d::graph::{Core3d, Node3d}, + prepass::{DepthPrepass, MotionVectorPrepass}, +}; +use bevy_ecs::{ + component::Component, prelude::ReflectComponent, resource::Resource, + schedule::IntoScheduleConfigs, +}; +use bevy_reflect::{prelude::ReflectDefault, reflect_remote, Reflect}; +use bevy_render::{ + camera::{MipBias, TemporalJitter}, + render_graph::{RenderGraphExt, ViewNodeRunner}, + renderer::RenderDevice, + view::{prepare_view_targets, prepare_view_uniforms, Hdr}, + ExtractSchedule, Render, RenderApp, RenderSystems, +}; +use std::sync::{Arc, Mutex}; +use tracing::info; + +pub use bevy_render::{DlssProjectId, DlssSupported}; +pub use dlss_wgpu::DlssPerfQualityMode; + +pub struct DlssPlugin; + +impl Plugin for DlssPlugin { + fn build(&self, app: &mut App) { + app.register_type::(); + } + + fn finish(&self, app: &mut App) { + if app.world().get_resource::().is_none() { + info!("DLSS is not supported on this system"); + return; + } + + let dlss_project_id = app.world().resource::().0; + + let render_app = app.get_sub_app_mut(RenderApp).unwrap(); + let render_device = render_app.world().resource::().clone(); + + let dlss_sdk = + dlss_wgpu::DlssSdk::new(dlss_project_id, render_device.wgpu_device().clone()); + if dlss_sdk.is_err() { + app.world_mut().remove_resource::(); + info!("DLSS is not supported on this system"); + return; + } + + render_app + .insert_resource(DlssSdk(dlss_sdk.unwrap())) + .add_systems(ExtractSchedule, extract::extract_dlss) + .add_systems( + Render, + prepare::prepare_dlss + .in_set(RenderSystems::ManageViews) + .before(prepare_view_targets), + ) + .add_render_graph_node::>(Core3d, Node3d::Dlss) + .add_render_graph_edges( + Core3d, + ( + Node3d::EndMainPass, + Node3d::MotionBlur, // Running before DLSS reduces edge artifacts and noise + Node3d::Dlss, + Node3d::Bloom, + Node3d::Tonemapping, + ), + ); + } +} + +/// Camera component to enable DLSS. +#[derive(Component, Reflect, Clone, Default)] +#[reflect(Component, Default)] +#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass, Hdr)] +pub struct Dlss { + #[reflect(remote = DlssPerfQualityModeRemoteReflect)] + pub perf_quality_mode: DlssPerfQualityMode, + pub reset: bool, +} + +#[reflect_remote(DlssPerfQualityMode)] +#[derive(Default)] +enum DlssPerfQualityModeRemoteReflect { + #[default] + Auto, + Dlaa, + Quality, + Balanced, + Performance, + UltraPerformance, +} + +#[derive(Resource)] +struct DlssSdk(Arc>); 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..4ad6d4f10059f --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/node.rs @@ -0,0 +1,85 @@ +use super::{prepare::ViewDlssContext, Dlss}; +use bevy_core_pipeline::prepass::ViewPrepassTextures; +use bevy_ecs::{query::QueryItem, world::World}; +use bevy_render::{ + camera::{MainPassResolutionOverride, TemporalJitter}, + render_graph::{NodeRunError, RenderGraphContext, ViewNode}, + renderer::{RenderAdapter, RenderContext}, + view::ViewTarget, +}; +use dlss_wgpu::{DlssExposure, DlssRenderParameters, DlssTexture}; + +#[derive(Default)] +pub struct DlssNode; + +impl ViewNode for DlssNode { + type ViewQuery = ( + &'static Dlss, + &'static ViewDlssContext, + &'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_motion_vectors_texture), Some(prepass_depth_texture)) = + (&prepass_textures.motion_vectors, &prepass_textures.depth) + else { + return Ok(()); + }; + + let view_target = view_target.post_process_write(); + + let render_resolution = resolution_override.0; + let render_parameters = DlssRenderParameters { + color: DlssTexture { + texture: &view_target.source_texture, + view: &view_target.source, + }, + depth: DlssTexture { + texture: &prepass_depth_texture.texture.texture, + view: &prepass_depth_texture.texture.default_view, + }, + motion_vectors: DlssTexture { + texture: &prepass_motion_vectors_texture.texture.texture, + view: &prepass_motion_vectors_texture.texture.default_view, + }, + exposure: DlssExposure::Automatic, // TODO + bias: None, // TODO + dlss_output: DlssTexture { + texture: &view_target.destination_texture, + view: &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 command_encoder = render_context.command_encoder(); + let mut dlss_context = dlss_context.context.lock().unwrap(); + + command_encoder.push_debug_group("dlss"); + dlss_context + .render(render_parameters, command_encoder, &adapter) + .expect("Failed to render DLSS"); + 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..ad790fd31a62b --- /dev/null +++ b/crates/bevy_anti_aliasing/src/dlss/prepare.rs @@ -0,0 +1,117 @@ +use super::{Dlss, DlssSdk}; +use bevy_core_pipeline::{ + core_3d::Camera3d, + 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::{CameraMainTextureUsages, MainPassResolutionOverride, MipBias, TemporalJitter}, + render_resource::TextureUsages, + renderer::{RenderDevice, RenderQueue}, + view::ExtractedView, +}; +use dlss_wgpu::{DlssContext, DlssFeatureFlags, DlssPerfQualityMode}; +use std::sync::{Arc, Mutex}; + +#[derive(Component)] +pub struct ViewDlssContext { + 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 ViewDlssContext>, + ), + ( + 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 + == dlss_context.context.lock().unwrap().upscaled_resolution() + && dlss.perf_quality_mode == dlss_context.perf_quality_mode + && dlss_feature_flags == dlss_context.feature_flags => + { + let dlss_context = dlss_context.context.lock().unwrap(); + temporal_jitter.offset = + dlss_context.suggested_jitter(frame_count.0, dlss_context.render_resolution()); + } + _ => { + let dlss_context = DlssContext::new( + upscaled_resolution, + dlss.perf_quality_mode, + dlss_feature_flags, + Arc::clone(&dlss_sdk.0), + render_device.wgpu_device(), + &render_queue, + ) + .expect("Failed to create DlssContext"); + + let render_resolution = dlss_context.render_resolution(); + temporal_jitter.offset = + dlss_context.suggested_jitter(frame_count.0, render_resolution); + mip_bias.0 = dlss_context.suggested_mip_bias(render_resolution); + + commands.entity(entity).insert(( + ViewDlssContext { + 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..6fb40d5bc3172 100644 --- a/crates/bevy_anti_aliasing/src/lib.rs +++ b/crates/bevy_anti_aliasing/src/lib.rs @@ -13,6 +13,8 @@ use smaa::SmaaPlugin; use taa::TemporalAntiAliasPlugin; pub mod contrast_adaptive_sharpening; +#[cfg(feature = "dlss")] +pub mod dlss; pub mod fxaa; pub mod smaa; pub mod taa; @@ -21,6 +23,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(feature = "dlss")] + dlss::DlssPlugin, + )); } } diff --git a/crates/bevy_camera/src/camera.rs b/crates/bevy_camera/src/camera.rs index 1eea96307732e..421ed305e7311 100644 --- a/crates/bevy_camera/src/camera.rs +++ b/crates/bevy_camera/src/camera.rs @@ -80,14 +80,20 @@ impl Viewport { } } - pub fn with_override( - &self, + pub fn from_viewport_and_override( + viewport: Option<&Self>, main_pass_resolution_override: Option<&MainPassResolutionOverride>, - ) -> Self { - let mut viewport = self.clone(); + ) -> Option { + let mut viewport = viewport.cloned(); + if let Some(override_size) = main_pass_resolution_override { - viewport.physical_size = **override_size; + if viewport.is_none() { + viewport = Some(Viewport::default()); + } + + viewport.as_mut().unwrap().physical_size = **override_size; } + viewport } } @@ -101,7 +107,7 @@ impl Viewport { /// * Insert this component on a 3d camera entity in the render world. /// * The resolution override must be smaller than the camera's viewport size. /// * The resolution override is specified in physical pixels. -#[derive(Component, Reflect, Deref)] +#[derive(Component, Reflect, Deref, Debug)] #[reflect(Component)] pub struct MainPassResolutionOverride(pub UVec2); diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index c5ee7a798db2e..0ee9144a954d1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -2,6 +2,7 @@ use crate::{ core_3d::Opaque3d, skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; +use bevy_camera::Viewport; use bevy_ecs::{prelude::World, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -91,8 +92,10 @@ impl ViewNode for MainOpaquePass3dNode { let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 393167227f8db..ec108b8753924 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -1,5 +1,6 @@ use super::{Camera3d, ViewTransmissionTexture}; use crate::core_3d::Transmissive3d; +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::ToExtents; use bevy_render::{ @@ -110,8 +111,11 @@ impl ViewNode for MainTransmissivePass3dNode { let mut render_pass = render_context.begin_tracked_render_pass(render_pass_descriptor); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = Viewport::from_viewport_and_override( + camera.viewport.as_ref(), + resolution_override, + ) { + render_pass.set_camera_viewport(&viewport); } if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) { diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index 0c70ec23a0863..bbe14578b0903 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -1,4 +1,5 @@ use crate::core_3d::Transparent3d; +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -69,8 +70,10 @@ impl ViewNode for MainTransparentPass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 9fd7880869546..b412cb4c55d03 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -29,8 +29,9 @@ pub mod graph { EndMainPass, Wireframe, LateDownsampleDepth, - Taa, MotionBlur, + Taa, + Dlss, Bloom, AutoExposure, DepthOfField, diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index ab87fccee6e89..dc65fa811df6a 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,3 +1,4 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::camera::MainPassResolutionOverride; use bevy_render::experimental::occlusion_culling::OcclusionCulling; @@ -221,8 +222,10 @@ fn run_deferred_prepass<'w>( occlusion_query_set: None, }); let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws diff --git a/crates/bevy_core_pipeline/src/oit/resolve/node.rs b/crates/bevy_core_pipeline/src/oit/resolve/node.rs index 77352e5ecb42b..83b8c604f442a 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/node.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/node.rs @@ -1,3 +1,4 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -63,8 +64,10 @@ impl ViewNode for OitResolveNode { occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } render_pass.set_render_pipeline(pipeline); diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index c4cc7b1d55da5..1b54ee8c5d393 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -1,3 +1,4 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -186,8 +187,10 @@ fn run_prepass<'w>( let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, label); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index e591803751f7d..ae4ae76b772f8 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -73,6 +73,9 @@ tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"] # Include SMAA LUT KTX2 Files smaa_luts = ["bevy_anti_aliasing/smaa_luts"] +# NVIDIA Deep Learning Super Sampling +dlss = ["bevy_anti_aliasing/dlss", "bevy_render/dlss"] + # Audio format support (vorbis is enabled by default) flac = ["bevy_audio/flac"] mp3 = ["bevy_audio/mp3"] diff --git a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs index 39dcb0c1690fa..b0e34d5e1a79a 100644 --- a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs @@ -10,6 +10,7 @@ use crate::{ MeshViewBindGroup, PrepassViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; +use bevy_camera::Viewport; use bevy_core_pipeline::prepass::{ MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, }; @@ -102,8 +103,10 @@ impl ViewNode for MeshletMainOpaquePass3dNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } render_pass.set_bind_group( @@ -223,8 +226,10 @@ impl ViewNode for MeshletPrepassNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if view_has_motion_vector_prepass { @@ -354,8 +359,10 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if view_has_motion_vector_prepass { diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 4183ca8085f78..f1d2e100ace7b 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -51,6 +51,7 @@ webgpu = ["wgpu/webgpu"] detailed_trace = [] ## Adds serialization support through `serde`. serialize = ["bevy_mesh/serialize"] +dlss = ["dep:dlss_wgpu"] [dependencies] # bevy @@ -102,6 +103,7 @@ wgpu = { version = "25", default-features = false, features = [ "fragile-send-sync-non-atomic-wasm", ] } naga = { version = "25", features = ["wgsl-in"] } +dlss_wgpu = { git = "https://github.com/bevyengine/dlss_wgpu", optional = true } serde = { version = "1", features = ["derive"] } bytemuck = { version = "1.5", features = ["derive", "must_cast"] } downcast-rs = { version = "2", default-features = false, features = ["std"] } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7a2ad060878b0..9d6c07432edf9 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -109,7 +109,7 @@ use crate::{ mesh::{MeshPlugin, MorphPlugin, RenderMesh}, render_asset::prepare_assets, render_resource::{PipelineCache, Shader, ShaderLoader}, - renderer::{render_system, RenderInstance}, + renderer::render_system, settings::RenderCreation, storage::StoragePlugin, view::{ViewPlugin, WindowRenderPlugin}, @@ -118,11 +118,9 @@ use alloc::sync::Arc; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_asset::{AssetApp, AssetServer}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; -use bevy_utils::WgpuWrapper; use bitflags::bitflags; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; -use tracing::debug; /// Inline shader as an `embedded_asset` and load it permanently. /// @@ -355,6 +353,14 @@ impl Plugin for RenderPlugin { .ok() .cloned(); let settings = render_creation.clone(); + + #[cfg(feature = "dlss")] + let dlss_project_id = app + .world() + .get_resource::() + .expect("The `dlss` feature is enabled, but DlssProjectId was not added to the App before DefaultPlugins.") + .0; + let async_renderer = async move { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends, @@ -403,25 +409,17 @@ impl Plugin for RenderPlugin { force_fallback_adapter, }; - let (device, queue, adapter_info, render_adapter) = - renderer::initialize_renderer( - &instance, - &settings, - &request_adapter_options, - desired_adapter_name, - ) - .await; - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_render_resources_inner = - future_render_resources_wrapper.lock().unwrap(); - *future_render_resources_inner = Some(RenderResources( - device, - queue, - adapter_info, - render_adapter, - RenderInstance(Arc::new(WgpuWrapper::new(instance))), - )); + let render_resources = renderer::initialize_renderer( + instance, + &settings, + &request_adapter_options, + desired_adapter_name, + #[cfg(feature = "dlss")] + dlss_project_id, + ) + .await; + + *future_render_resources_wrapper.lock().unwrap() = Some(render_resources); }; // In wasm, spawn a task and detach it for execution #[cfg(target_arch = "wasm32")] @@ -490,6 +488,16 @@ impl Plugin for RenderPlugin { if let Some(future_render_resources) = app.world_mut().remove_resource::() { + #[cfg(feature = "dlss")] + let RenderResources( + device, + queue, + adapter_info, + render_adapter, + instance, + dlss_available, + ) = future_render_resources.0.lock().unwrap().take().unwrap(); + #[cfg(not(feature = "dlss"))] let RenderResources(device, queue, adapter_info, render_adapter, instance) = future_render_resources.0.lock().unwrap().take().unwrap(); @@ -503,6 +511,11 @@ impl Plugin for RenderPlugin { .insert_resource(render_adapter.clone()) .insert_resource(compressed_image_format_support); + #[cfg(feature = "dlss")] + if let Some(dlss_available) = dlss_available { + app.insert_resource(dlss_available); + } + let render_app = app.sub_app_mut(RenderApp); render_app @@ -659,3 +672,16 @@ pub fn get_mali_driver_version(adapter: &RenderAdapter) -> Option { None } + +/// Application-specific ID for DLSS. +/// +/// See the DLSS programming guide for more info. +#[cfg(feature = "dlss")] +#[derive(Resource)] +pub struct DlssProjectId(pub bevy_asset::uuid::Uuid); + +/// When DLSS is supported by the current system, this resource will exist in the main world. +/// Otherwise this resource will be absent. +#[cfg(feature = "dlss")] +#[derive(Resource, Clone, Copy)] +pub struct DlssSupported; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 52679002faa32..ab46b6f2691b0 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -14,7 +14,7 @@ use crate::{ render_graph::RenderGraph, render_phase::TrackedRenderPass, render_resource::RenderPassDescriptor, - settings::{WgpuSettings, WgpuSettingsPriority}, + settings::{RenderResources, WgpuSettings, WgpuSettingsPriority}, view::{ExtractedWindows, ViewTarget}, }; use alloc::sync::Arc; @@ -175,15 +175,16 @@ fn find_adapter_by_name( /// Initializes the renderer by retrieving and preparing the GPU instance, device and queue /// for the specified backend. pub async fn initialize_renderer( - instance: &Instance, + instance: Instance, options: &WgpuSettings, request_adapter_options: &RequestAdapterOptions<'_, '_>, desired_adapter_name: Option, -) -> (RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter) { + #[cfg(feature = "dlss")] dlss_project_id: bevy_asset::uuid::Uuid, +) -> RenderResources { #[cfg(not(target_family = "wasm"))] let mut selected_adapter = desired_adapter_name.and_then(|adapter_name| { find_adapter_by_name( - instance, + &instance, options, request_adapter_options.compatible_surface, &adapter_name, @@ -358,24 +359,47 @@ pub async fn initialize_renderer( }; } - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: options.device_label.as_ref().map(AsRef::as_ref), - required_features: features, - required_limits: limits, - memory_hints: options.memory_hints.clone(), - // See https://github.com/gfx-rs/wgpu/issues/5974 - trace: Trace::Off, - }) - .await - .unwrap(); - let queue = Arc::new(WgpuWrapper::new(queue)); - let adapter = Arc::new(WgpuWrapper::new(adapter)); - ( + let device_descriptor = wgpu::DeviceDescriptor { + label: options.device_label.as_ref().map(AsRef::as_ref), + required_features: features, + required_limits: limits, + memory_hints: options.memory_hints.clone(), + // See https://github.com/gfx-rs/wgpu/issues/5974 + trace: Trace::Off, + }; + + #[cfg(not(feature = "dlss"))] + let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap(); + + #[cfg(feature = "dlss")] + let mut dlss_supported = false; + #[cfg(feature = "dlss")] + let (device, queue) = { + match dlss_wgpu::request_device(dlss_project_id, &adapter, &device_descriptor) { + Ok(x) => { + dlss_supported = true; + x + } + // Fallback to standard device request + Err(_) => adapter.request_device(&device_descriptor).await.unwrap(), + } + }; + + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + + RenderResources( RenderDevice::from(device), - RenderQueue(queue), + RenderQueue(Arc::new(WgpuWrapper::new(queue))), RenderAdapterInfo(WgpuWrapper::new(adapter_info)), - RenderAdapter(adapter), + RenderAdapter(Arc::new(WgpuWrapper::new(adapter))), + RenderInstance(Arc::new(WgpuWrapper::new(instance))), + #[cfg(feature = "dlss")] + if dlss_supported { + Some(crate::DlssSupported) + } else { + None + }, ) } diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index d4456953af6ce..431803d5dce5c 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -148,11 +148,12 @@ impl Default for WgpuSettings { #[derive(Clone)] pub struct RenderResources( - pub RenderDevice, - pub RenderQueue, - pub RenderAdapterInfo, - pub RenderAdapter, - pub RenderInstance, + pub(crate) RenderDevice, + pub(crate) RenderQueue, + pub(crate) RenderAdapterInfo, + pub(crate) RenderAdapter, + pub(crate) RenderInstance, + #[cfg(feature = "dlss")] pub(crate) Option, ); /// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin). @@ -176,7 +177,16 @@ impl RenderCreation { adapter: RenderAdapter, instance: RenderInstance, ) -> Self { - RenderResources(device, queue, adapter_info, adapter, instance).into() + RenderResources( + device, + queue, + adapter_info, + adapter, + instance, + #[cfg(feature = "dlss")] + None, + ) + .into() } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index cdda04cf2de10..839231e083bac 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -3,6 +3,7 @@ pub mod window; use bevy_camera::{ primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, + MainPassResolutionOverride, }; use bevy_diagnostic::FrameCount; pub use visibility::*; @@ -901,6 +902,7 @@ pub fn prepare_view_uniforms( Option<&Frustum>, Option<&TemporalJitter>, Option<&MipBias>, + Option<&MainPassResolutionOverride>, )>, frame_count: Res, ) { @@ -913,13 +915,23 @@ pub fn prepare_view_uniforms( else { return; }; - for (entity, extracted_camera, extracted_view, frustum, temporal_jitter, mip_bias) in &views { + for ( + entity, + extracted_camera, + extracted_view, + frustum, + temporal_jitter, + mip_bias, + resolution_override, + ) in &views + { let viewport = extracted_view.viewport.as_vec4(); let unjittered_projection = extracted_view.clip_from_view; let mut clip_from_view = unjittered_projection; if let Some(temporal_jitter) = temporal_jitter { - temporal_jitter.jitter_projection(&mut clip_from_view, viewport.zw()); + let jitter_view_size = resolution_override.map_or(viewport.zw(), |v| v.0.as_vec2()); + temporal_jitter.jitter_projection(&mut clip_from_view, jitter_view_size); } let view_from_clip = clip_from_view.inverse(); diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index eaa432d8cbace..20bf7bb9a3e43 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -141,6 +141,9 @@ impl ViewNode for SolariLightingNode { }); let pass_span = diagnostics.pass_span(&mut pass, "solari_lighting"); + let dx = solari_lighting_resources.view_size.x.div_ceil(8); + let dy = solari_lighting_resources.view_size.y.div_ceil(8); + pass.set_bind_group(0, scene_bindings, &[]); pass.set_bind_group( 1, @@ -156,16 +159,16 @@ impl ViewNode for SolariLightingNode { 0, bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), ); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(di_spatial_and_shade_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(gi_initial_and_temporal_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(gi_spatial_and_shade_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass_span.end(&mut pass); drop(pass); diff --git a/crates/bevy_solari/src/realtime/prepare.rs b/crates/bevy_solari/src/realtime/prepare.rs index 46a94a3ca2477..0359a2ddc58ec 100644 --- a/crates/bevy_solari/src/realtime/prepare.rs +++ b/crates/bevy_solari/src/realtime/prepare.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ use bevy_image::ToExtents; use bevy_math::UVec2; use bevy_render::{ - camera::ExtractedCamera, + camera::{ExtractedCamera, MainPassResolutionOverride}, render_resource::{ Buffer, BufferDescriptor, BufferUsages, Texture, TextureDescriptor, TextureDimension, TextureUsages, TextureView, TextureViewDescriptor, @@ -37,16 +37,24 @@ pub struct SolariLightingResources { pub fn prepare_solari_lighting_resources( query: Query< - (Entity, &ExtractedCamera, Option<&SolariLightingResources>), + ( + Entity, + &ExtractedCamera, + Option<&SolariLightingResources>, + Option<&MainPassResolutionOverride>, + ), With, >, render_device: Res, mut commands: Commands, ) { - for (entity, camera, solari_lighting_resources) in &query { - let Some(view_size) = camera.physical_viewport_size else { + for (entity, camera, solari_lighting_resources, resolution_override) in &query { + let Some(mut view_size) = camera.physical_viewport_size else { continue; }; + if let Some(MainPassResolutionOverride(resolution_override)) = resolution_override { + view_size = *resolution_override; + } if solari_lighting_resources.map(|r| r.view_size) == Some(view_size) { continue; diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 120c461efe378..7213fe163cb83 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -80,6 +80,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.| diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index fd93625c0e634..44632fd07ac1c 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,22 @@ use bevy::{ }, }; +#[cfg(feature = "dlss")] +use bevy::anti_aliasing::dlss::{Dlss, DlssPerfQualityMode, DlssProjectId, DlssSupported}; + fn main() { - App::new() - .add_plugins(DefaultPlugins) + let mut app = App::new(); + + #[cfg(feature = "dlss")] + app.insert_resource(DlssProjectId(bevy_asset::uuid::uuid!( + "5417916c-0291-4e3f-8f65-326c1858ab96" + ))); + + 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,20 +47,46 @@ type TaaComponents = ( MotionVectorPrepass, ); +#[cfg(feature = "dlss")] +type DlssComponents = ( + Dlss, + TemporalJitter, + MipBias, + DepthPrepass, + MotionVectorPrepass, +); +#[cfg(not(feature = "dlss"))] +type DlssComponents = (); + fn modify_aa( keys: Res>, - camera: Single< + #[cfg(feature = "dlss")] camera: Single< ( Entity, Option<&mut Fxaa>, Option<&mut Smaa>, Option<&TemporalAntiAliasing>, &mut Msaa, + Option<&mut Dlss>, ), With, >, + #[cfg(not(feature = "dlss"))] camera: Single< + ( + Entity, + Option<&mut Fxaa>, + Option<&mut Smaa>, + Option<&TemporalAntiAliasing>, + &mut Msaa, + ), + With, + >, + #[cfg(feature = "dlss")] dlss_supported: Option>, mut commands: Commands, ) { + #[cfg(feature = "dlss")] + let (camera_entity, fxaa, smaa, taa, mut msaa, dlss) = camera.into_inner(); + #[cfg(not(feature = "dlss"))] let (camera_entity, fxaa, smaa, taa, mut msaa) = camera.into_inner(); let mut camera = commands.entity(camera_entity); @@ -60,7 +96,8 @@ fn modify_aa( camera .remove::() .remove::() - .remove::(); + .remove::() + .remove::(); } // MSAA @@ -68,7 +105,8 @@ fn modify_aa( camera .remove::() .remove::() - .remove::(); + .remove::() + .remove::(); *msaa = Msaa::Sample4; } @@ -92,6 +130,7 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(Fxaa::default()); } @@ -125,6 +164,7 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(Smaa::default()); } @@ -150,8 +190,43 @@ fn modify_aa( camera .remove::() .remove::() + .remove::() .insert(TemporalAntiAliasing::default()); } + + // DLSS + #[cfg(feature = "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(feature = "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 +254,18 @@ fn modify_sharpening( } fn update_ui( - camera: Single< + #[cfg(feature = "dlss")] camera: Single< + ( + Option<&Fxaa>, + Option<&Smaa>, + Option<&TemporalAntiAliasing>, + &ContrastAdaptiveSharpening, + &Msaa, + Option<&Dlss>, + ), + With, + >, + #[cfg(not(feature = "dlss"))] camera: Single< ( Option<&Fxaa>, Option<&Smaa>, @@ -190,22 +276,35 @@ fn update_ui( With, >, mut ui: Single<&mut Text>, + #[cfg(feature = "dlss")] dlss_supported: Option>, ) { + #[cfg(feature = "dlss")] + let (fxaa, smaa, taa, cas, msaa, dlss) = *camera; + #[cfg(not(feature = "dlss"))] let (fxaa, smaa, taa, cas, msaa) = *camera; let ui = &mut ui.0; *ui = "Antialias Method\n".to_string(); + #[cfg(feature = "dlss")] + let dlss_none = dlss.is_none(); + #[cfg(not(feature = "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(feature = "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 +340,28 @@ fn update_ui( draw_selectable_menu_item(ui, "Ultra", 'R', smaa.preset == SmaaPreset::Ultra); } + #[cfg(feature = "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 +381,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 993299dfc6699..d846950c41e8f 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/examples/shader/custom_render_phase.rs b/examples/shader/custom_render_phase.rs index 9993de341cdde..ca0171c1b5859 100644 --- a/examples/shader/custom_render_phase.rs +++ b/examples/shader/custom_render_phase.rs @@ -12,6 +12,7 @@ use std::ops::Range; +use bevy::camera::Viewport; use bevy::pbr::SetMeshViewEmptyBindGroup; use bevy::{ core_pipeline::core_3d::graph::{Core3d, Node3d}, @@ -611,8 +612,10 @@ impl ViewNode for CustomDrawNode { occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Render the phase diff --git a/release-content/release-notes/dlss.md b/release-content/release-notes/dlss.md new file mode 100644 index 0000000000000..951e42a9492fd --- /dev/null +++ b/release-content/release-notes/dlss.md @@ -0,0 +1,34 @@ +--- +title: Deep Learning Super Sampling (DLSS) +authors: ["@JMS55"] +pull_requests: [19817, 19864] +--- + +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 https://github.com/bevyengine/dlss_wgpu). + +Additionally, we've open sourced https://github.com/bevyengine/dlss_wgpu 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 https://github.com/bevyengine/dlss_wgpu 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.