From ca87359c6ed4e765fd22c4aa43156e4008001589 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 29 Jul 2020 18:15:15 -0700 Subject: [PATCH] render: add MSAA support --- Cargo.toml | 4 + crates/bevy_render/src/lib.rs | 9 ++- .../src/pipeline/pipeline_compiler.rs | 16 +++- .../src/pipeline/render_pipelines.rs | 9 ++- crates/bevy_render/src/render_graph/base.rs | 78 ++++++++++++++++--- .../src/render_graph/nodes/pass_node.rs | 21 ++++- crates/bevy_text/src/draw.rs | 9 ++- crates/bevy_ui/src/render/mod.rs | 42 ++++++++-- crates/bevy_ui/src/widget/text.rs | 4 +- examples/3d/msaa.rs | 45 +++++++++++ 10 files changed, 212 insertions(+), 25 deletions(-) create mode 100644 examples/3d/msaa.rs diff --git a/Cargo.toml b/Cargo.toml index e5402eeb56..54afcc112a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,10 @@ path = "examples/2d/texture_atlas.rs" name = "load_model" path = "examples/3d/load_model.rs" +[[example]] +name = "msaa" +path = "examples/3d/msaa.rs" + [[example]] name = "parenting" path = "examples/3d/parenting.rs" diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 053bfe05eb..8f8b82f649 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -15,6 +15,7 @@ pub use once_cell; pub mod prelude { pub use crate::{ + base::Msaa, color::Color, draw::Draw, entity::*, @@ -26,6 +27,7 @@ pub mod prelude { } use crate::prelude::*; +use base::Msaa; use bevy_app::prelude::*; use bevy_asset::AddAsset; use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem}; @@ -131,10 +133,15 @@ impl AppPlugin for RenderPlugin { shader::clear_shader_defs_system.system(), ); + if app.resources().get::().is_none() { + app.init_resource::(); + } + if let Some(ref config) = self.base_render_graph_config { let resources = app.resources(); let mut render_graph = resources.get_mut::().unwrap(); - render_graph.add_base_graph(config); + let msaa = resources.get::().unwrap(); + render_graph.add_base_graph(config, &msaa); let mut active_cameras = resources.get_mut::().unwrap(); if config.add_3d_camera { active_cameras.add(base::camera::CAMERA3D); diff --git a/crates/bevy_render/src/pipeline/pipeline_compiler.rs b/crates/bevy_render/src/pipeline/pipeline_compiler.rs index efa9dfa0a6..151bbb8534 100644 --- a/crates/bevy_render/src/pipeline/pipeline_compiler.rs +++ b/crates/bevy_render/src/pipeline/pipeline_compiler.rs @@ -7,11 +7,23 @@ use bevy_asset::{Assets, Handle}; use once_cell::sync::Lazy; use std::collections::{HashMap, HashSet}; -#[derive(Clone, Eq, PartialEq, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Debug)] pub struct PipelineSpecialization { pub shader_specialization: ShaderSpecialization, pub primitive_topology: PrimitiveTopology, pub dynamic_bindings: Vec, + pub sample_count: u32, +} + +impl Default for PipelineSpecialization { + fn default() -> Self { + Self { + sample_count: 1, + shader_specialization: Default::default(), + primitive_topology: Default::default(), + dynamic_bindings: Default::default(), + } + } } impl PipelineSpecialization { @@ -145,6 +157,8 @@ impl PipelineCompiler { Some(vertex_buffer_descriptors), &pipeline_specialization.dynamic_bindings, ); + + specialized_descriptor.sample_count = pipeline_specialization.sample_count; specialized_descriptor.primitive_topology = pipeline_specialization.primitive_topology; let specialized_pipeline_handle = pipelines.add(specialized_descriptor); diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs index 1d7f1ba89f..a562861a67 100644 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ b/crates/bevy_render/src/pipeline/render_pipelines.rs @@ -1,10 +1,10 @@ use super::{PipelineDescriptor, PipelineSpecialization}; use crate::{ draw::{Draw, DrawContext, DrawError, Drawable}, - renderer::RenderResourceBindings, + renderer::RenderResourceBindings, prelude::Msaa, }; use bevy_asset::Handle; -use bevy_ecs::{Query, ResMut}; +use bevy_ecs::{Query, ResMut, Res}; use bevy_property::Properties; #[derive(Properties, Default, Clone)] pub struct RenderPipeline { @@ -104,9 +104,14 @@ impl<'a> Drawable for DrawableRenderPipelines<'a> { pub fn draw_render_pipelines_system( mut draw_context: DrawContext, mut render_resource_bindings: ResMut, + msaa: Res, mut query: Query<(&mut Draw, &mut RenderPipelines)>, ) { for (mut draw, mut render_pipelines) in &mut query.iter() { + for pipeline in render_pipelines.pipelines.iter_mut() { + pipeline.specialization.sample_count = msaa.samples; + } + let mut drawable = DrawableRenderPipelines { render_pipelines: &mut render_pipelines, render_resource_bindings: &mut render_resource_bindings, diff --git a/crates/bevy_render/src/render_graph/base.rs b/crates/bevy_render/src/render_graph/base.rs index 50e5ddb456..870bfa6c43 100644 --- a/crates/bevy_render/src/render_graph/base.rs +++ b/crates/bevy_render/src/render_graph/base.rs @@ -13,6 +13,18 @@ use crate::{ }; use bevy_window::WindowId; +pub struct Msaa { + pub samples: u32, +} + +impl Default for Msaa { + fn default() -> Self { + Self { + samples: 4, + } + } +} + pub struct BaseRenderGraphConfig { pub add_2d_camera: bool, pub add_3d_camera: bool, @@ -28,6 +40,7 @@ pub mod node { pub const CAMERA2D: &str = "camera2d"; pub const TEXTURE_COPY: &str = "texture_copy"; pub const MAIN_DEPTH_TEXTURE: &str = "main_pass_depth_texture"; + pub const MAIN_SAMPLED_COLOR_ATTACHMENT: &str = "main_pass_sampled_color_attachment"; pub const MAIN_PASS: &str = "main_pass"; pub const SHARED_BUFFERS: &str = "shared_buffers"; } @@ -53,11 +66,11 @@ impl Default for BaseRenderGraphConfig { /// By itself this graph doesn't do much, but it allows Render plugins to interop with each other by having a common /// set of nodes. It can be customized using `BaseRenderGraphConfig`. pub trait BaseRenderGraphBuilder { - fn add_base_graph(&mut self, config: &BaseRenderGraphConfig) -> &mut Self; + fn add_base_graph(&mut self, config: &BaseRenderGraphConfig, msaa: &Msaa) -> &mut Self; } impl BaseRenderGraphBuilder for RenderGraph { - fn add_base_graph(&mut self, config: &BaseRenderGraphConfig) -> &mut Self { + fn add_base_graph(&mut self, config: &BaseRenderGraphConfig, msaa: &Msaa) -> &mut Self { self.add_node(node::TEXTURE_COPY, TextureCopyNode::default()); if config.add_3d_camera { self.add_system_node(node::CAMERA3D, CameraNode::new(camera::CAMERA3D)); @@ -80,7 +93,7 @@ impl BaseRenderGraphBuilder for RenderGraph { height: 1, }, mip_level_count: 1, - sample_count: 1, + sample_count: msaa.samples, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, // PERF: vulkan docs recommend using 24 bit depth for better performance usage: TextureUsage::OUTPUT_ATTACHMENT, @@ -90,15 +103,29 @@ impl BaseRenderGraphBuilder for RenderGraph { } if config.add_main_pass { - let mut main_pass_node = PassNode::<&MainPass>::new(PassDescriptor { - color_attachments: vec![RenderPassColorAttachmentDescriptor { - attachment: TextureAttachment::Input("color".to_string()), + let color_attachment = if msaa.samples > 1 { + RenderPassColorAttachmentDescriptor { + attachment: TextureAttachment::Input("color_attachment".to_string()), + resolve_target: Some(TextureAttachment::Input( + "color_resolve_target".to_string(), + )), + ops: Operations { + load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)), + store: true, + }, + } + } else { + RenderPassColorAttachmentDescriptor { + attachment: TextureAttachment::Input("color_attachment".to_string()), resolve_target: None, ops: Operations { load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)), store: true, }, - }], + } + }; + let mut main_pass_node = PassNode::<&MainPass>::new(PassDescriptor { + color_attachments: vec![color_attachment], depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor { attachment: TextureAttachment::Input("depth".to_string()), depth_ops: Some(Operations { @@ -107,7 +134,7 @@ impl BaseRenderGraphBuilder for RenderGraph { }), stencil_ops: None, }), - sample_count: 1, + sample_count: msaa.samples, }); main_pass_node.use_default_clear_color(0); @@ -146,7 +173,40 @@ impl BaseRenderGraphBuilder for RenderGraph { node::PRIMARY_SWAP_CHAIN, WindowSwapChainNode::OUT_TEXTURE, node::MAIN_PASS, - "color", + if msaa.samples > 1 { + "color_resolve_target" + } else { + "color_attachment" + }, + ) + .unwrap(); + } + + if msaa.samples > 1 { + self.add_node( + node::MAIN_SAMPLED_COLOR_ATTACHMENT, + WindowTextureNode::new( + WindowId::primary(), + TextureDescriptor { + size: Extent3d { + depth: 1, + width: 1, + height: 1, + }, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + usage: TextureUsage::OUTPUT_ATTACHMENT, + }, + ), + ); + + self.add_slot_edge( + node::MAIN_SAMPLED_COLOR_ATTACHMENT, + WindowSwapChainNode::OUT_TEXTURE, + node::MAIN_PASS, + "color_attachment", ) .unwrap(); } diff --git a/crates/bevy_render/src/render_graph/nodes/pass_node.rs b/crates/bevy_render/src/render_graph/nodes/pass_node.rs index 4e0c40a0a6..f6d8cbbdc4 100644 --- a/crates/bevy_render/src/render_graph/nodes/pass_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/pass_node.rs @@ -24,6 +24,7 @@ pub struct PassNode { inputs: Vec, cameras: Vec, color_attachment_input_indices: Vec>, + color_resolve_target_indices: Vec>, depth_stencil_attachment_input_index: Option, default_clear_color_inputs: Vec, camera_bind_group_descriptor: BindGroupDescriptor, @@ -34,26 +35,37 @@ impl PassNode { pub fn new(descriptor: PassDescriptor) -> Self { let mut inputs = Vec::new(); let mut color_attachment_input_indices = Vec::new(); + let mut color_resolve_target_indices = Vec::new(); for color_attachment in descriptor.color_attachments.iter() { if let TextureAttachment::Input(ref name) = color_attachment.attachment { + color_attachment_input_indices.push(Some(inputs.len())); inputs.push(ResourceSlotInfo::new( name.to_string(), RenderResourceType::Texture, )); - color_attachment_input_indices.push(Some(inputs.len() - 1)); } else { color_attachment_input_indices.push(None); } + + if let Some(TextureAttachment::Input(ref name)) = color_attachment.resolve_target { + color_resolve_target_indices.push(Some(inputs.len())); + inputs.push(ResourceSlotInfo::new( + name.to_string(), + RenderResourceType::Texture, + )); + } else { + color_resolve_target_indices.push(None); + } } let mut depth_stencil_attachment_input_index = None; if let Some(ref depth_stencil_attachment) = descriptor.depth_stencil_attachment { if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment { + depth_stencil_attachment_input_index = Some(inputs.len()); inputs.push(ResourceSlotInfo::new( name.to_string(), RenderResourceType::Texture, )); - depth_stencil_attachment_input_index = Some(inputs.len() - 1); } } @@ -74,6 +86,7 @@ impl PassNode { inputs, cameras: Vec::new(), color_attachment_input_indices, + color_resolve_target_indices, depth_stencil_attachment_input_index, default_clear_color_inputs: Vec::new(), camera_bind_group_descriptor, @@ -120,6 +133,10 @@ impl Node for PassNode { color_attachment.attachment = TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap()); } + if let Some(input_index) = self.color_resolve_target_indices[i] { + color_attachment.resolve_target = + Some(TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap())); + } } if let Some(input_index) = self.depth_stencil_attachment_input_index { diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs index 7a3ca538e7..52fc7127ef 100644 --- a/crates/bevy_text/src/draw.rs +++ b/crates/bevy_text/src/draw.rs @@ -10,7 +10,7 @@ use bevy_render::{ renderer::{ AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings, RenderResourceId, - }, + }, prelude::Msaa, }; use bevy_sprite::{TextureAtlas, TextureAtlasSprite}; @@ -38,6 +38,7 @@ pub struct DrawableText<'a> { pub container_size: Vec2, pub style: &'a TextStyle, pub text: &'a str, + pub msaa: &'a Msaa, } impl<'a> Drawable for DrawableText<'a> { @@ -45,8 +46,10 @@ impl<'a> Drawable for DrawableText<'a> { context.set_pipeline( draw, bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, - // TODO: remove this shader def specialization when its easier to manually bind global render resources to specific bind groups - &PipelineSpecialization::default(), + &PipelineSpecialization { + sample_count: self.msaa.samples, + ..Default::default() + }, )?; let render_resource_context = &**context.render_resource_context; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index e1a94ce560..e742b105af 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -8,6 +8,7 @@ use bevy_render::{ RenderPassDepthStencilAttachmentDescriptor, TextureAttachment, }, pipeline::*, + prelude::Msaa, render_graph::{ base, CameraNode, PassNode, RenderGraph, RenderResourcesNode, WindowSwapChainNode, WindowTextureNode, @@ -82,17 +83,31 @@ impl UiRenderGraphBuilder for RenderGraph { fn add_ui_graph(&mut self, resources: &Resources) -> &mut Self { let mut pipelines = resources.get_mut::>().unwrap(); let mut shaders = resources.get_mut::>().unwrap(); + let msaa = resources.get::().unwrap(); pipelines.set(UI_PIPELINE_HANDLE, build_ui_pipeline(&mut shaders)); - let mut ui_pass_node = PassNode::<&Node>::new(PassDescriptor { - color_attachments: vec![RenderPassColorAttachmentDescriptor { - attachment: TextureAttachment::Input("color".to_string()), + let color_attachment = if msaa.samples > 1 { + RenderPassColorAttachmentDescriptor { + attachment: TextureAttachment::Input("color_attachment".to_string()), + resolve_target: Some(TextureAttachment::Input("color_resolve_target".to_string())), + ops: Operations { + load: LoadOp::Load, + store: true, + }, + } + } else { + RenderPassColorAttachmentDescriptor { + attachment: TextureAttachment::Input("color_attachment".to_string()), resolve_target: None, ops: Operations { load: LoadOp::Load, store: true, }, - }], + } + }; + + let mut ui_pass_node = PassNode::<&Node>::new(PassDescriptor { + color_attachments: vec![color_attachment], depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor { attachment: TextureAttachment::Input("depth".to_string()), depth_ops: Some(Operations { @@ -101,7 +116,7 @@ impl UiRenderGraphBuilder for RenderGraph { }), stencil_ops: None, }), - sample_count: 1, + sample_count: msaa.samples, }); ui_pass_node.add_camera(camera::UI_CAMERA); self.add_node(node::UI_PASS, ui_pass_node); @@ -110,7 +125,11 @@ impl UiRenderGraphBuilder for RenderGraph { base::node::PRIMARY_SWAP_CHAIN, WindowSwapChainNode::OUT_TEXTURE, node::UI_PASS, - "color", + if msaa.samples > 1 { + "color_resolve_target" + } else { + "color_attachment" + }, ) .unwrap(); @@ -122,6 +141,17 @@ impl UiRenderGraphBuilder for RenderGraph { ) .unwrap(); + if msaa.samples > 1 { + self.add_slot_edge( + base::node::MAIN_SAMPLED_COLOR_ATTACHMENT, + WindowSwapChainNode::OUT_TEXTURE, + node::UI_PASS, + "color_attachment", + ) + .unwrap(); + } + + // ensure ui pass runs after main pass self.add_node_edge(base::node::MAIN_PASS, node::UI_PASS) .unwrap(); diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 58ba23d3dd..812e48527e 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -5,7 +5,7 @@ use bevy_math::Size; use bevy_render::{ draw::{Draw, DrawContext, Drawable}, renderer::{AssetRenderResourceBindings, RenderResourceBindings}, - texture::Texture, + texture::Texture, prelude::Msaa, }; use bevy_sprite::TextureAtlas; use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle}; @@ -51,6 +51,7 @@ pub fn text_system( pub fn draw_text_system( mut draw_context: DrawContext, fonts: Res>, + msaa: Res, font_atlas_sets: Res>, texture_atlases: Res>, mut render_resource_bindings: ResMut, @@ -69,6 +70,7 @@ pub fn draw_text_system( render_resource_bindings: &mut render_resource_bindings, asset_render_resource_bindings: &mut asset_render_resource_bindings, position, + msaa: &msaa, style: &text.style, text: &text.value, container_size: node.size, diff --git a/examples/3d/msaa.rs b/examples/3d/msaa.rs new file mode 100644 index 0000000000..63336ae07c --- /dev/null +++ b/examples/3d/msaa.rs @@ -0,0 +1,45 @@ +use bevy::prelude::*; + +/// This example shows how to configure Multi-Sample Anti-Aliasing. Setting the sample count higher will result in smoother edges, +/// but it will also increase the cost to render those edges. The range should generally be somewhere between 1 (no multi sampling, +/// but cheap) to 8 (crisp but expensive) +fn main() { + App::build() + .add_resource(Msaa { samples: 4 }) + .add_default_plugins() + .add_startup_system(setup.system()) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // add entities to the world + commands + // cube + .spawn(PbrComponents { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(StandardMaterial { + albedo: Color::rgb(0.5, 0.4, 0.3), + ..Default::default() + }), + ..Default::default() + }) + // light + .spawn(LightComponents { + translation: Translation::new(4.0, 8.0, 4.0), + ..Default::default() + }) + // camera + .spawn(Camera3dComponents { + transform: Transform::new_sync_disabled(Mat4::face_toward( + Vec3::new(-3.0, 3.0, 5.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 1.0, 0.0), + )), + ..Default::default() + }); +}