pipeline specialization (support different primitive topologies within the same pipeline)

This commit is contained in:
Carter Anderson 2020-04-19 19:06:41 -07:00
parent 86d0ae6470
commit 7fe2288814
13 changed files with 172 additions and 118 deletions

View File

@ -118,7 +118,7 @@ impl<T> AssetStorage<T> {
AssetStorage {
assets: HashMap::new(),
names: HashMap::new(),
current_index: 1,
current_index: DEFAULT_HANDLE_ID + 1, // ensure we don't start on the default handle id
}
}

View File

@ -39,7 +39,7 @@ use self::{
},
render_graph::RenderGraph,
render_resource::{
build_entity_render_resource_assignments_system,
entity_render_resource_assignments_system,
resource_providers::{
Camera2dResourceProvider, CameraResourceProvider, LightResourceProvider,
UniformResourceProvider,
@ -111,8 +111,12 @@ impl AppPlugin for RenderPlugin {
.add_resource(EntityRenderResourceAssignments::default())
.add_resource(asset_batchers)
// core systems
.add_system(build_entity_render_resource_assignments_system())
.add_system(entity_render_resource_assignments_system())
.add_system_to_stage_init(stage::POST_UPDATE, camera::camera_update_system)
.add_system_to_stage(
stage::POST_UPDATE,
mesh::mesh_specializer_system(),
)
.add_system_to_stage(stage::POST_UPDATE, mesh::mesh_batcher_system())
.add_system_to_stage(
stage::POST_UPDATE,

View File

@ -6,7 +6,7 @@ use crate::{
render_resource::AssetBatchers,
Renderable,
};
use bevy_asset::Handle;
use bevy_asset::{AssetStorage, Handle};
use glam::*;
use legion::prelude::*;
use std::borrow::Cow;
@ -224,7 +224,7 @@ pub mod shape {
];
Mesh {
primitive_topology: PrimitiveTopology::TriangleStrip,
primitive_topology: PrimitiveTopology::TriangleList,
attributes: vec![
VertexAttribute::position(positions),
VertexAttribute::normal(normals),
@ -283,7 +283,7 @@ pub mod shape {
}
Mesh {
primitive_topology: PrimitiveTopology::TriangleStrip,
primitive_topology: PrimitiveTopology::TriangleList,
attributes: vec![
VertexAttribute::position(positions),
VertexAttribute::normal(normals),
@ -321,6 +321,20 @@ pub fn mesh_batcher_system() -> Box<dyn Schedulable> {
})
}
pub fn mesh_specializer_system() -> Box<dyn Schedulable> {
SystemBuilder::new("mesh_batcher")
.read_resource::<AssetStorage<Mesh>>()
.with_query(
<(Read<Handle<Mesh>>, Write<Renderable>)>::query().filter(changed::<Handle<Mesh>>() | changed::<Renderable>()),
)
.build(|_, world, meshes, query| {
for (mesh_handle, mut renderable) in query.iter_mut(world) {
let mesh = meshes.get(&mesh_handle).unwrap();
renderable.render_resource_assignments.pipeline_specialization.primitive_topology = mesh.primitive_topology;
}
})
}
#[cfg(test)]
mod tests {
use crate::{Vertex, pipeline::state_descriptors::PrimitiveTopology, shader::AsUniforms};

View File

@ -1,5 +1,6 @@
use super::{
BindType, PipelineDescriptor, PipelineLayout, PipelineLayoutType, VertexBufferDescriptors,
state_descriptors::PrimitiveTopology, BindType, PipelineDescriptor, PipelineLayout,
PipelineLayoutType, VertexBufferDescriptors,
};
use crate::{
render_resource::{
@ -14,11 +15,25 @@ use std::collections::{HashMap, HashSet};
use legion::prelude::*;
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct PipelineSpecialization {
pub shader_specialization: ShaderSpecialization,
pub primitive_topology: PrimitiveTopology,
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct ShaderSpecialization {
pub shader_defs: HashSet<String>,
}
// TODO: consider using (Typeid, fieldinfo.index) in place of string for hashes
pub struct PipelineCompiler {
pub shader_source_to_compiled: HashMap<Handle<Shader>, Vec<(HashSet<String>, Handle<Shader>)>>,
pub pipeline_source_to_compiled:
HashMap<Handle<PipelineDescriptor>, Vec<(HashSet<String>, Handle<PipelineDescriptor>)>>,
pub shader_source_to_compiled:
HashMap<Handle<Shader>, Vec<(ShaderSpecialization, Handle<Shader>)>>,
pub pipeline_source_to_compiled: HashMap<
Handle<PipelineDescriptor>,
Vec<(PipelineSpecialization, Handle<PipelineDescriptor>)>,
>,
}
impl PipelineCompiler {
@ -86,7 +101,7 @@ impl PipelineCompiler {
&mut self,
shader_storage: &mut AssetStorage<Shader>,
shader_handle: &Handle<Shader>,
shader_defs: &HashSet<String>,
shader_specialization: &ShaderSpecialization,
) -> Handle<Shader> {
let compiled_shaders = self
.shader_source_to_compiled
@ -100,18 +115,26 @@ impl PipelineCompiler {
return *shader_handle;
}
if let Some((_compiled_shader_defs, compiled_shader)) = compiled_shaders
.iter()
.find(|(compiled_shader_defs, _compiled_shader)| *compiled_shader_defs == *shader_defs)
if let Some((_shader_specialization, compiled_shader)) =
compiled_shaders
.iter()
.find(|(current_shader_specialization, _compiled_shader)| {
*current_shader_specialization == *shader_specialization
})
{
// if shader has already been compiled with current configuration, use existing shader
*compiled_shader
} else {
// if no shader exists with the current configuration, create new shader and compile
let shader_def_vec = shader_defs.iter().cloned().collect::<Vec<String>>();
let shader_def_vec = shader_specialization
.shader_defs
.iter()
.cloned()
.collect::<Vec<String>>();
let compiled_shader = shader.get_spirv_shader(Some(&shader_def_vec));
compiled_shaders.push((shader_defs.clone(), *shader_handle));
shader_storage.add(compiled_shader)
let compiled_handle = shader_storage.add(compiled_shader);
compiled_shaders.push((shader_specialization.clone(), compiled_handle));
compiled_handle
}
}
@ -128,7 +151,9 @@ impl PipelineCompiler {
compiled_pipeline_descriptor.shader_stages.vertex = self.compile_shader(
shader_storage,
&pipeline_descriptor.shader_stages.vertex,
&render_resource_assignments.shader_defs,
&render_resource_assignments
.pipeline_specialization
.shader_specialization,
);
compiled_pipeline_descriptor.shader_stages.fragment = pipeline_descriptor
.shader_stages
@ -138,7 +163,9 @@ impl PipelineCompiler {
self.compile_shader(
shader_storage,
fragment,
&render_resource_assignments.shader_defs,
&render_resource_assignments
.pipeline_specialization
.shader_specialization,
)
});
@ -150,6 +177,9 @@ impl PipelineCompiler {
render_resource_assignments,
);
compiled_pipeline_descriptor.primitive_topology = render_resource_assignments
.pipeline_specialization
.primitive_topology;
compiled_pipeline_descriptor
}
@ -174,8 +204,8 @@ impl PipelineCompiler {
.get_mut(pipeline_handle)
.unwrap()
.iter()
.find(|(shader_defs, _macroed_pipeline_handle)| {
*shader_defs == render_resource_assignments.shader_defs
.find(|(pipeline_specialization, _macroed_pipeline_handle)| {
*pipeline_specialization == render_resource_assignments.pipeline_specialization
}) {
*macroed_pipeline_handle
} else {
@ -194,7 +224,7 @@ impl PipelineCompiler {
.get_mut(pipeline_handle)
.unwrap();
macro_pipelines.push((
render_resource_assignments.shader_defs.clone(),
render_resource_assignments.pipeline_specialization.clone(),
compiled_pipeline_handle,
));
compiled_pipeline_handle
@ -277,7 +307,12 @@ pub fn update_shader_assignments(
);
// reset shader_defs so they can be changed next frame
renderable.render_resource_assignments.shader_defs.clear();
renderable
.render_resource_assignments
.pipeline_specialization
.shader_specialization
.shader_defs
.clear();
}
}
}

View File

@ -55,7 +55,7 @@ impl<'a, 'b, 'c> ForwardPipelineBuilder for RenderGraphBuilder<'a, 'b, 'c> {
},
write_mask: ColorWrite::ALL,
})
.add_draw_target(resource_name::draw_target::ASSIGNED_BATCHES);
.add_draw_target(resource_name::draw_target::ASSIGNED_MESHES);
})
}
}

View File

@ -60,6 +60,12 @@ pub enum PrimitiveTopology {
TriangleStrip = 4,
}
impl Default for PrimitiveTopology {
fn default() -> Self {
PrimitiveTopology::TriangleList
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum FrontFace {
Ccw = 0,
@ -172,3 +178,9 @@ pub enum IndexFormat {
Uint16 = 0,
Uint32 = 1,
}
impl Default for IndexFormat {
fn default() -> Self {
IndexFormat::Uint16
}
}

View File

@ -19,7 +19,7 @@ impl EntityRenderResourceAssignments {
}
// TODO: make sure this runs right before rendering
pub fn build_entity_render_resource_assignments_system() -> Box<dyn Schedulable> {
pub fn entity_render_resource_assignments_system() -> Box<dyn Schedulable> {
SystemBuilder::new("entity_render_resource_assignments")
.write_resource::<EntityRenderResourceAssignments>()
.with_query(<Write<Renderable>>::query().filter(changed::<Renderable>()))

View File

@ -1,5 +1,5 @@
use super::RenderResource;
use crate::pipeline::{BindGroupDescriptor, BindGroupDescriptorId};
use crate::pipeline::{BindGroupDescriptor, BindGroupDescriptorId, PipelineSpecialization};
use std::{
collections::{hash_map::DefaultHasher, HashMap, HashSet},
hash::{Hash, Hasher},
@ -15,7 +15,7 @@ pub struct RenderResourceAssignments {
bind_group_resource_sets:
HashMap<BindGroupDescriptorId, (RenderResourceSetId, Option<Vec<u32>>)>,
dirty_bind_groups: HashSet<BindGroupDescriptorId>,
pub(crate) shader_defs: HashSet<String>,
pub pipeline_specialization: PipelineSpecialization,
}
impl RenderResourceAssignments {

View File

@ -1,80 +1,96 @@
use crate::{
mesh::{self, Mesh},
pipeline::{state_descriptors::IndexFormat, VertexBufferDescriptors},
render_resource::{AssetBatchers, BufferInfo, BufferUsage},
renderer_2::GlobalRenderResourceContext,
pipeline::{state_descriptors::IndexFormat, VertexBufferDescriptor, VertexBufferDescriptors},
render_resource::{AssetBatchers, BufferInfo, BufferUsage, RenderResourceAssignments},
renderer_2::{GlobalRenderResourceContext, RenderResourceContext},
shader::AsUniforms,
Vertex,
Renderable, Vertex,
};
use bevy_asset::AssetStorage;
use bevy_asset::{AssetStorage, Handle};
use legion::prelude::*;
fn setup_mesh_resource(
render_resources: &dyn RenderResourceContext,
render_resource_assignments: &mut RenderResourceAssignments,
vertex_buffer_descriptor: &VertexBufferDescriptor,
handle: Handle<Mesh>,
meshes: &AssetStorage<Mesh>,
) {
log::trace!("setup mesh for {:?}", render_resource_assignments.id);
let index_format = IndexFormat::Uint16;
let (vertex_buffer, index_buffer) = if let Some(vertex_buffer) =
render_resources.get_asset_resource(handle, mesh::VERTEX_BUFFER_ASSET_INDEX)
{
(
vertex_buffer,
render_resources.get_asset_resource(handle, mesh::INDEX_BUFFER_ASSET_INDEX),
)
} else {
let mesh_asset = meshes.get(&handle).unwrap();
let vertex_bytes = mesh_asset
.get_vertex_buffer_bytes(&vertex_buffer_descriptor)
.unwrap();
// TODO: use a staging buffer here
let vertex_buffer = render_resources.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::VERTEX,
..Default::default()
},
&vertex_bytes,
);
let index_bytes = mesh_asset.get_index_buffer_bytes(index_format).unwrap();
let index_buffer = render_resources.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::INDEX,
..Default::default()
},
&index_bytes,
);
render_resources.set_asset_resource(handle, vertex_buffer, mesh::VERTEX_BUFFER_ASSET_INDEX);
render_resources.set_asset_resource(handle, index_buffer, mesh::INDEX_BUFFER_ASSET_INDEX);
(vertex_buffer, Some(index_buffer))
};
render_resource_assignments.set_vertex_buffer("Vertex", vertex_buffer, index_buffer);
}
pub fn mesh_resource_provider_system(resources: &mut Resources) -> Box<dyn Schedulable> {
let mut vertex_buffer_descriptors = resources.get_mut::<VertexBufferDescriptors>().unwrap();
// TODO: allow pipelines to specialize on vertex_buffer_descriptor and index_format
let vertex_buffer_descriptor = Vertex::get_vertex_buffer_descriptor().unwrap();
let index_format = IndexFormat::Uint16;
vertex_buffer_descriptors.set(vertex_buffer_descriptor.clone());
SystemBuilder::new("mesh_resource_provider")
.read_resource::<GlobalRenderResourceContext>()
.read_resource::<AssetStorage<Mesh>>()
.write_resource::<AssetBatchers>()
.with_query(<(Read<Handle<Mesh>>, Write<Renderable>)>::query())
.build(
move |_, _, (render_resource_context, meshes, asset_batchers), _| {
let render_resources = &render_resource_context.context;
move |_, world, (render_resource_context, meshes, asset_batchers), query| {
let render_resources = &*render_resource_context.context;
if let Some(batches) = asset_batchers.get_handle_batches_mut::<Mesh>() {
for batch in batches {
let handle = batch.get_handle::<Mesh>().unwrap();
log::trace!("setup mesh for {:?}", batch.render_resource_assignments.id);
let (vertex_buffer, index_buffer) = if let Some(vertex_buffer) =
render_resources
.get_asset_resource(handle, mesh::VERTEX_BUFFER_ASSET_INDEX)
{
(
vertex_buffer,
render_resources
.get_asset_resource(handle, mesh::INDEX_BUFFER_ASSET_INDEX),
)
} else {
let mesh_asset = meshes.get(&handle).unwrap();
let vertex_bytes = mesh_asset.get_vertex_buffer_bytes(&vertex_buffer_descriptor).unwrap();
// TODO: use a staging buffer here
let vertex_buffer = render_resources.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::VERTEX,
..Default::default()
},
&vertex_bytes,
);
let index_bytes = mesh_asset.get_index_buffer_bytes(index_format).unwrap();
let index_buffer = render_resources.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::INDEX,
..Default::default()
},
&index_bytes,
);
render_resources.set_asset_resource(
handle,
vertex_buffer,
mesh::VERTEX_BUFFER_ASSET_INDEX,
);
render_resources.set_asset_resource(
handle,
index_buffer,
mesh::INDEX_BUFFER_ASSET_INDEX,
);
(vertex_buffer, Some(index_buffer))
};
batch.render_resource_assignments.set_vertex_buffer(
"Vertex",
vertex_buffer,
index_buffer,
setup_mesh_resource(
render_resources,
&mut batch.render_resource_assignments,
&vertex_buffer_descriptor,
handle,
&meshes,
);
}
}
// TODO: remove this once batches are pipeline specific and deprecate assigned_meshes draw target
for (handle, mut renderable) in query.iter_mut(world) {
setup_mesh_resource(
render_resources,
&mut renderable.render_resource_assignments,
&vertex_buffer_descriptor,
*handle,
&meshes,
);
}
},
)
}

View File

@ -31,6 +31,8 @@ where
if let Some(shader_defs) = uniforms.get_shader_defs() {
renderable
.render_resource_assignments
.pipeline_specialization
.shader_specialization
.shader_defs
.extend(shader_defs)
}
@ -58,6 +60,8 @@ where
if let Some(shader_defs) = uniforms.get_shader_defs() {
renderable
.render_resource_assignments
.pipeline_specialization
.shader_specialization
.shader_defs
.extend(shader_defs)
}

View File

@ -10,7 +10,6 @@ fn main() {
/// set up a simple scene
fn setup(world: &mut World, resources: &mut Resources) {
env_logger::init();
// create a cube and a plane mesh
let mut mesh_storage = resources.get_mut::<AssetStorage<Mesh>>().unwrap();
let cube_handle = mesh_storage.add(Mesh::from(shape::Cube));
@ -45,13 +44,6 @@ fn setup(world: &mut World, resources: &mut Resources) {
translation: Translation::new(0.0, 0.0, 1.0),
..Default::default()
})
// cube
.add_entity(MeshEntity {
mesh: cube_handle,
material: cube_material_handle,
translation: Translation::new(2.0, 0.0, 1.0),
..Default::default()
})
// light
.add_entity(LightEntity {
translation: Translation::new(4.0, -4.0, 5.0),

View File

@ -1,13 +1,9 @@
use bevy::{prelude::*, render::shader};
use bevy::prelude::*;
fn main() {
App::build()
.add_default_plugins()
.add_startup_system(setup)
.add_system_to_stage(
stage::POST_UPDATE,
shader::asset_handle_batcher_system::<MyMaterial>(),
)
.run();
}
@ -32,8 +28,7 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
ShaderStage::Vertex,
r#"
#version 450
layout(location = 0) in vec4 Vertex_Position;
layout(location = 0) out vec4 v_Position;
layout(location = 0) in vec3 Vertex_Position;
layout(set = 0, binding = 0) uniform Camera {
mat4 ViewProj;
};
@ -41,8 +36,7 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
mat4 Model;
};
void main() {
v_Position = Model * Vertex_Position;
gl_Position = ViewProj * v_Position;
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
}
"#,
))
@ -50,7 +44,6 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
ShaderStage::Fragment,
r#"
#version 450
layout(location = 0) in vec4 v_Position;
layout(location = 0) out vec4 o_Target;
layout(set = 1, binding = 1) uniform MyMaterial_color {
vec4 color;
@ -73,13 +66,8 @@ fn setup(world: &mut World, resources: &mut Resources) {
let material = material_storage.add(MyMaterial {
color: Color::rgb(0.0, 0.8, 0.0),
});
resources.insert(material_storage);
// batch materials to improve performance
let mut asset_batchers = resources.get_mut::<AssetBatchers>().unwrap();
asset_batchers.batch_types2::<Mesh, MyMaterial>();
// get a handle to our newly created shader pipeline
let mut pipeline_storage = resources
.get_mut::<AssetStorage<PipelineDescriptor>>()

View File

@ -4,10 +4,6 @@ fn main() {
App::build()
.add_default_plugins()
.add_startup_system(setup)
.add_system_to_stage(
stage::POST_UPDATE,
shader::asset_handle_batcher_system::<MyMaterial>(),
)
.add_system_to_stage(
stage::POST_UPDATE,
shader::asset_handle_shader_def_system::<MyMaterial>(),
@ -38,8 +34,7 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
ShaderStage::Vertex,
r#"
#version 450
layout(location = 0) in vec4 Vertex_Position;
layout(location = 0) out vec4 v_Position;
layout(location = 0) in vec3 Vertex_Position;
layout(set = 0, binding = 0) uniform Camera {
mat4 ViewProj;
};
@ -47,8 +42,7 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
mat4 Model;
};
void main() {
v_Position = Model * Vertex_Position;
gl_Position = ViewProj * v_Position;
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
}
"#,
))
@ -56,7 +50,6 @@ fn add_shader_to_render_graph(resources: &mut Resources) {
ShaderStage::Fragment,
r#"
#version 450
layout(location = 0) in vec4 v_Position;
layout(location = 0) out vec4 o_Target;
layout(set = 1, binding = 1) uniform MyMaterial_color {
vec4 color;
@ -92,10 +85,6 @@ fn setup(world: &mut World, resources: &mut Resources) {
resources.insert(material_storage);
// batch materials to improve performance
let mut asset_batchers = resources.get_mut::<AssetBatchers>().unwrap();
asset_batchers.batch_types2::<Mesh, MyMaterial>();
// get a handle to our newly created shader pipeline
let mut pipeline_storage = resources
.get_mut::<AssetStorage<PipelineDescriptor>>()