render: add MSAA support

This commit is contained in:
Carter Anderson 2020-07-29 18:15:15 -07:00
parent a2c1a90695
commit ca87359c6e
10 changed files with 212 additions and 25 deletions

View File

@ -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"

View File

@ -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::<Msaa>().is_none() {
app.init_resource::<Msaa>();
}
if let Some(ref config) = self.base_render_graph_config {
let resources = app.resources();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
render_graph.add_base_graph(config);
let msaa = resources.get::<Msaa>().unwrap();
render_graph.add_base_graph(config, &msaa);
let mut active_cameras = resources.get_mut::<ActiveCameras>().unwrap();
if config.add_3d_camera {
active_cameras.add(base::camera::CAMERA3D);

View File

@ -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<DynamicBinding>,
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);

View File

@ -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<RenderResourceBindings>,
msaa: Res<Msaa>,
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,

View File

@ -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();
}

View File

@ -24,6 +24,7 @@ pub struct PassNode<Q: HecsQuery> {
inputs: Vec<ResourceSlotInfo>,
cameras: Vec<CameraInfo>,
color_attachment_input_indices: Vec<Option<usize>>,
color_resolve_target_indices: Vec<Option<usize>>,
depth_stencil_attachment_input_index: Option<usize>,
default_clear_color_inputs: Vec<usize>,
camera_bind_group_descriptor: BindGroupDescriptor,
@ -34,26 +35,37 @@ impl<Q: HecsQuery> PassNode<Q> {
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<Q: HecsQuery> PassNode<Q> {
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<Q: HecsQuery + Send + Sync + 'static> Node for PassNode<Q> {
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 {

View File

@ -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;

View File

@ -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::<Assets<PipelineDescriptor>>().unwrap();
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
let msaa = resources.get::<Msaa>().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();

View File

@ -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<Assets<Font>>,
msaa: Res<Msaa>,
font_atlas_sets: Res<Assets<FontAtlasSet>>,
texture_atlases: Res<Assets<TextureAtlas>>,
mut render_resource_bindings: ResMut<RenderResourceBindings>,
@ -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,

45
examples/3d/msaa.rs Normal file
View File

@ -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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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()
});
}