Mesh vertex buffer layouts (#3959)

This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
  * `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
  *  `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"  
  * Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently. 
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).    
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key.  For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!

To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:

```rust
impl SpecializedMeshPipeline for MeshPipeline {
    type Key = MeshPipelineKey;

    fn specialize(
        &self,
        key: Self::Key,
        layout: &MeshVertexBufferLayout,
    ) -> RenderPipelineDescriptor {
        let mut vertex_attributes = vec![
            Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
            Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
            Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
        ];

        let mut shader_defs = Vec::new();
        if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
            shader_defs.push(String::from("VERTEX_TANGENTS"));
            vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
        }

        let vertex_buffer_layout = layout
            .get_layout(&vertex_attributes)
            .expect("Mesh is missing a vertex attribute");
```

Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.

This is still a draft because I still need to:

* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly

Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.

Alternative to #3120
Fixes #3030


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Carter Anderson 2022-02-23 23:21:13 +00:00
parent b3a2cbbc98
commit e369a8ad51
30 changed files with 1024 additions and 543 deletions

View File

@ -447,6 +447,10 @@ name = "scene"
path = "examples/scene/scene.rs" path = "examples/scene/scene.rs"
# Shaders # Shaders
[[example]]
name = "custom_vertex_attribute"
path = "examples/shader/custom_vertex_attribute.rs"
[[example]] [[example]]
name = "shader_defs" name = "shader_defs"
path = "examples/shader/shader_defs.rs" path = "examples/shader/shader_defs.rs"

View File

@ -0,0 +1,40 @@
#import bevy_pbr::mesh_view_bind_group
#import bevy_pbr::mesh_struct
struct Vertex {
[[location(0)]] position: vec3<f32>;
[[location(1)]] blend_color: vec4<f32>;
};
struct CustomMaterial {
color: vec4<f32>;
};
[[group(1), binding(0)]]
var<uniform> material: CustomMaterial;
[[group(2), binding(0)]]
var<uniform> mesh: Mesh;
struct VertexOutput {
[[builtin(position)]] clip_position: vec4<f32>;
[[location(0)]] blend_color: vec4<f32>;
};
[[stage(vertex)]]
fn vertex(vertex: Vertex) -> VertexOutput {
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
var out: VertexOutput;
out.clip_position = view.view_proj * world_position;
out.blend_color = vertex.blend_color;
return out;
}
struct FragmentInput {
[[location(0)]] blend_color: vec4<f32>;
};
[[stage(fragment)]]
fn fragment(input: FragmentInput) -> [[location(0)]] vec4<f32> {
return material.color * input.blend_color;
}

View File

@ -125,40 +125,40 @@ async fn load_gltf<'a, 'b>(
.read_positions() .read_positions()
.map(|v| VertexAttributeValues::Float32x3(v.collect())) .map(|v| VertexAttributeValues::Float32x3(v.collect()))
{ {
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute);
} }
if let Some(vertex_attribute) = reader if let Some(vertex_attribute) = reader
.read_normals() .read_normals()
.map(|v| VertexAttributeValues::Float32x3(v.collect())) .map(|v| VertexAttributeValues::Float32x3(v.collect()))
{ {
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute);
} }
if let Some(vertex_attribute) = reader if let Some(vertex_attribute) = reader
.read_tangents() .read_tangents()
.map(|v| VertexAttributeValues::Float32x4(v.collect())) .map(|v| VertexAttributeValues::Float32x4(v.collect()))
{ {
mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
} }
if let Some(vertex_attribute) = reader if let Some(vertex_attribute) = reader
.read_tex_coords(0) .read_tex_coords(0)
.map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect())) .map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect()))
{ {
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute);
} else { } else {
let len = mesh.count_vertices(); let len = mesh.count_vertices();
let uvs = vec![[0.0, 0.0]; len]; let uvs = vec![[0.0, 0.0]; len];
bevy_log::debug!("missing `TEXCOORD_0` vertex attribute, loading zeroed out UVs"); bevy_log::debug!("missing `TEXCOORD_0` vertex attribute, loading zeroed out UVs");
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
} }
// if let Some(vertex_attribute) = reader // if let Some(vertex_attribute) = reader
// .read_colors(0) // .read_colors(0)
// .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) // .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect()))
// { // {
// mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); // mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
// } // }
if let Some(indices) = reader.read_indices() { if let Some(indices) = reader.read_indices() {

View File

@ -40,7 +40,7 @@ use bevy_render::{
prelude::Color, prelude::Color,
render_graph::RenderGraph, render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
render_resource::{Shader, SpecializedPipelines}, render_resource::{Shader, SpecializedMeshPipelines},
view::VisibilitySystems, view::VisibilitySystems,
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
@ -176,7 +176,7 @@ impl Plugin for PbrPlugin {
.init_resource::<DrawFunctions<Shadow>>() .init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>() .init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>() .init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedPipelines<ShadowPipeline>>(); .init_resource::<SpecializedMeshPipelines<ShadowPipeline>>();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Shadow, DrawShadowMesh>(); render_app.add_render_command::<Shadow, DrawShadowMesh>();

View File

@ -15,7 +15,7 @@ use bevy_ecs::{
world::FromWorld, world::FromWorld,
}; };
use bevy_render::{ use bevy_render::{
mesh::Mesh, mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin, render_component::ExtractComponentPlugin,
render_phase::{ render_phase::{
@ -24,12 +24,13 @@ use bevy_render::{
}, },
render_resource::{ render_resource::{
BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader,
SpecializedPipeline, SpecializedPipelines, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
}, },
renderer::RenderDevice, renderer::RenderDevice,
view::{ExtractedView, Msaa, VisibleEntities}, view::{ExtractedView, Msaa, VisibleEntities},
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
use bevy_utils::tracing::error;
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -72,6 +73,16 @@ pub trait Material: Asset + RenderAsset {
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] { fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
&[] &[]
} }
/// Customizes the default [`RenderPipelineDescriptor`].
#[allow(unused_variables)]
#[inline]
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
} }
impl<M: Material> SpecializedMaterial for M { impl<M: Material> SpecializedMaterial for M {
@ -81,7 +92,13 @@ impl<M: Material> SpecializedMaterial for M {
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {} fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
#[inline] #[inline]
fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {} fn specialize(
descriptor: &mut RenderPipelineDescriptor,
_key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
<M as Material>::specialize(descriptor, layout)
}
#[inline] #[inline]
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup { fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
@ -130,7 +147,11 @@ pub trait SpecializedMaterial: Asset + RenderAsset {
fn key(material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key; fn key(material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key;
/// Specializes the given `descriptor` according to the given `key`. /// Specializes the given `descriptor` according to the given `key`.
fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor); fn specialize(
descriptor: &mut RenderPipelineDescriptor,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError>;
/// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`].
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup; fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
@ -188,12 +209,18 @@ impl<M: SpecializedMaterial> Plugin for MaterialPlugin<M> {
.add_render_command::<Opaque3d, DrawMaterial<M>>() .add_render_command::<Opaque3d, DrawMaterial<M>>()
.add_render_command::<AlphaMask3d, DrawMaterial<M>>() .add_render_command::<AlphaMask3d, DrawMaterial<M>>()
.init_resource::<MaterialPipeline<M>>() .init_resource::<MaterialPipeline<M>>()
.init_resource::<SpecializedPipelines<MaterialPipeline<M>>>() .init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
.add_system_to_stage(RenderStage::Queue, queue_material_meshes::<M>); .add_system_to_stage(RenderStage::Queue, queue_material_meshes::<M>);
} }
} }
} }
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct MaterialPipelineKey<T> {
mesh_key: MeshPipelineKey,
material_key: T,
}
pub struct MaterialPipeline<M: SpecializedMaterial> { pub struct MaterialPipeline<M: SpecializedMaterial> {
pub mesh_pipeline: MeshPipeline, pub mesh_pipeline: MeshPipeline,
pub material_layout: BindGroupLayout, pub material_layout: BindGroupLayout,
@ -202,11 +229,15 @@ pub struct MaterialPipeline<M: SpecializedMaterial> {
marker: PhantomData<M>, marker: PhantomData<M>,
} }
impl<M: SpecializedMaterial> SpecializedPipeline for MaterialPipeline<M> { impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
type Key = (MeshPipelineKey, M::Key); type Key = MaterialPipelineKey<M::Key>;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let mut descriptor = self.mesh_pipeline.specialize(key.0); &self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
if let Some(vertex_shader) = &self.vertex_shader { if let Some(vertex_shader) = &self.vertex_shader {
descriptor.vertex.shader = vertex_shader.clone(); descriptor.vertex.shader = vertex_shader.clone();
} }
@ -220,8 +251,8 @@ impl<M: SpecializedMaterial> SpecializedPipeline for MaterialPipeline<M> {
self.mesh_pipeline.mesh_layout.clone(), self.mesh_pipeline.mesh_layout.clone(),
]); ]);
M::specialize(key.1, &mut descriptor); M::specialize(&mut descriptor, key.material_key, layout)?;
descriptor Ok(descriptor)
} }
} }
@ -275,7 +306,7 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>, alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>, transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
material_pipeline: Res<MaterialPipeline<M>>, material_pipeline: Res<MaterialPipeline<M>>,
mut pipelines: ResMut<SpecializedPipelines<MaterialPipeline<M>>>, mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
@ -307,72 +338,81 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
let inverse_view_matrix = view.transform.compute_matrix().inverse(); let inverse_view_matrix = view.transform.compute_matrix().inverse();
let inverse_view_row_2 = inverse_view_matrix.row(2); let inverse_view_row_2 = inverse_view_matrix.row(2);
let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
for visible_entity in &visible_entities.entities { for visible_entity in &visible_entities.entities {
if let Ok((material_handle, mesh_handle, mesh_uniform)) = if let Ok((material_handle, mesh_handle, mesh_uniform)) =
material_meshes.get(*visible_entity) material_meshes.get(*visible_entity)
{ {
if let Some(material) = render_materials.get(material_handle) { if let Some(material) = render_materials.get(material_handle) {
let mut mesh_key = mesh_key;
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
if mesh.has_tangents { let mut mesh_key =
mesh_key |= MeshPipelineKey::VERTEX_TANGENTS; MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
| msaa_key;
let alpha_mode = M::alpha_mode(material);
if let AlphaMode::Blend = alpha_mode {
mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS;
} }
mesh_key |=
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
}
let alpha_mode = M::alpha_mode(material);
if let AlphaMode::Blend = alpha_mode {
mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS;
}
let specialized_key = M::key(material); let material_key = M::key(material);
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material_pipeline,
(mesh_key, specialized_key),
);
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix let pipeline_id = pipelines.specialize(
// gives the z component of translation of the mesh in view space &mut pipeline_cache,
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); &material_pipeline,
match alpha_mode { MaterialPipelineKey {
AlphaMode::Opaque => { mesh_key,
opaque_phase.add(Opaque3d { material_key,
entity: *visible_entity, },
draw_function: draw_opaque_pbr, &mesh.layout,
pipeline: pipeline_id, );
// NOTE: Front-to-back ordering for opaque with ascending sort means near should have the let pipeline_id = match pipeline_id {
// lowest sort key and getting further away should increase. As we have Ok(id) => id,
// -z in front of the camera, values in view space decrease away from the Err(err) => {
// camera. Flipping the sign of mesh_z results in the correct front-to-back ordering error!("{}", err);
distance: -mesh_z, continue;
}); }
} };
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d { // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
entity: *visible_entity, // gives the z component of translation of the mesh in view space
draw_function: draw_alpha_mask_pbr, let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3));
pipeline: pipeline_id, match alpha_mode {
// NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the AlphaMode::Opaque => {
// lowest sort key and getting further away should increase. As we have opaque_phase.add(Opaque3d {
// -z in front of the camera, values in view space decrease away from the entity: *visible_entity,
// camera. Flipping the sign of mesh_z results in the correct front-to-back ordering draw_function: draw_opaque_pbr,
distance: -mesh_z, pipeline: pipeline_id,
}); // NOTE: Front-to-back ordering for opaque with ascending sort means near should have the
} // lowest sort key and getting further away should increase. As we have
AlphaMode::Blend => { // -z in front of the camera, values in view space decrease away from the
transparent_phase.add(Transparent3d { // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering
entity: *visible_entity, distance: -mesh_z,
draw_function: draw_transparent_pbr, });
pipeline: pipeline_id, }
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the AlphaMode::Mask(_) => {
// lowest sort key and getting closer should increase. As we have alpha_mask_phase.add(AlphaMask3d {
// -z in front of the camera, the largest distance is -far with values increasing toward the entity: *visible_entity,
// camera. As such we can just use mesh_z as the distance draw_function: draw_alpha_mask_pbr,
distance: mesh_z, pipeline: pipeline_id,
}); // NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the
// lowest sort key and getting further away should increase. As we have
// -z in front of the camera, values in view space decrease away from the
// camera. Flipping the sign of mesh_z results in the correct front-to-back ordering
distance: -mesh_z,
});
}
AlphaMode::Blend => {
transparent_phase.add(Transparent3d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
pipeline: pipeline_id,
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
// lowest sort key and getting closer should increase. As we have
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
distance: mesh_z,
});
}
} }
} }
} }

View File

@ -5,6 +5,7 @@ use bevy_math::Vec4;
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_render::{ use bevy_render::{
color::Color, color::Color,
mesh::MeshVertexBufferLayout,
prelude::Shader, prelude::Shader,
render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::{ render_resource::{
@ -338,7 +339,11 @@ impl SpecializedMaterial for StandardMaterial {
} }
} }
fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor) { fn specialize(
descriptor: &mut RenderPipelineDescriptor,
key: Self::Key,
_layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
if key.normal_map { if key.normal_map {
descriptor descriptor
.fragment .fragment
@ -350,6 +355,7 @@ impl SpecializedMaterial for StandardMaterial {
if let Some(label) = &mut descriptor.label { if let Some(label) = &mut descriptor.label {
*label = format!("pbr_{}", *label).into(); *label = format!("pbr_{}", *label).into();
} }
Ok(())
} }
fn fragment_shader(_asset_server: &AssetServer) -> Option<Handle<Shader>> { fn fragment_shader(_asset_server: &AssetServer) -> Option<Handle<Shader>> {

View File

@ -14,7 +14,7 @@ use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
use bevy_render::{ use bevy_render::{
camera::{Camera, CameraProjection}, camera::{Camera, CameraProjection},
color::Color, color::Color,
mesh::Mesh, mesh::{Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets, render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{ render_phase::{
@ -28,7 +28,10 @@ use bevy_render::{
view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::{tracing::warn, HashMap}; use bevy_utils::{
tracing::{error, warn},
HashMap,
};
use std::num::NonZeroU32; use std::num::NonZeroU32;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
@ -214,7 +217,6 @@ bitflags::bitflags! {
#[repr(transparent)] #[repr(transparent)]
pub struct ShadowPipelineKey: u32 { pub struct ShadowPipelineKey: u32 {
const NONE = 0; const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
} }
} }
@ -244,76 +246,23 @@ impl ShadowPipelineKey {
} }
} }
impl SpecializedPipeline for ShadowPipeline { impl SpecializedMeshPipeline for ShadowPipeline {
type Key = ShadowPipelineKey; type Key = ShadowPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let (vertex_array_stride, vertex_attributes) = &self,
if key.contains(ShadowPipelineKey::VERTEX_TANGENTS) { key: Self::Key,
( layout: &MeshVertexBufferLayout,
48, ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
vec![ let vertex_buffer_layout =
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?;
VertexAttribute {
format: VertexFormat::Float32x3, Ok(RenderPipelineDescriptor {
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 40,
shader_location: 2,
},
// Tangent
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 24,
shader_location: 3,
},
],
)
} else {
(
32,
vec![
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
],
)
};
RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(), shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(), entry_point: "vertex".into(),
shader_defs: vec![], shader_defs: vec![],
buffers: vec![VertexBufferLayout { buffers: vec![vertex_buffer_layout],
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
}, },
fragment: None, fragment: None,
layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]),
@ -344,7 +293,7 @@ impl SpecializedPipeline for ShadowPipeline {
}), }),
multisample: MultisampleState::default(), multisample: MultisampleState::default(),
label: Some("shadow_pipeline".into()), label: Some("shadow_pipeline".into()),
} })
} }
} }
@ -1090,7 +1039,7 @@ pub fn queue_shadows(
shadow_pipeline: Res<ShadowPipeline>, shadow_pipeline: Res<ShadowPipeline>,
casting_meshes: Query<&Handle<Mesh>, Without<NotShadowCaster>>, casting_meshes: Query<&Handle<Mesh>, Without<NotShadowCaster>>,
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
mut pipelines: ResMut<SpecializedPipelines<ShadowPipeline>>, mut pipelines: ResMut<SpecializedMeshPipelines<ShadowPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
view_lights: Query<&ViewLightEntities>, view_lights: Query<&ViewLightEntities>,
mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>,
@ -1120,23 +1069,32 @@ pub fn queue_shadows(
// NOTE: Lights with shadow mapping disabled will have no visible entities // NOTE: Lights with shadow mapping disabled will have no visible entities
// so no meshes will be queued // so no meshes will be queued
for entity in visible_entities.iter().copied() { for entity in visible_entities.iter().copied() {
let mut key = ShadowPipelineKey::empty();
if let Ok(mesh_handle) = casting_meshes.get(entity) { if let Ok(mesh_handle) = casting_meshes.get(entity) {
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
if mesh.has_tangents { let key =
key |= ShadowPipelineKey::VERTEX_TANGENTS; ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
} let pipeline_id = pipelines.specialize(
key |= ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); &mut pipeline_cache,
} &shadow_pipeline,
let pipeline_id = key,
pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); &mesh.layout,
);
shadow_phase.add(Shadow { let pipeline_id = match pipeline_id {
draw_function: draw_shadow_mesh, Ok(id) => id,
pipeline: pipeline_id, Err(err) => {
entity, error!("{}", err);
distance: 0.0, // TODO: sort back-to-front continue;
}); }
};
shadow_phase.add(Shadow {
draw_function: draw_shadow_mesh,
pipeline: pipeline_id,
entity,
distance: 0.0, // TODO: sort back-to-front
});
}
} }
} }
} }

View File

@ -11,7 +11,7 @@ use bevy_ecs::{
use bevy_math::{Mat4, Size}; use bevy_math::{Mat4, Size};
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_render::{ use bevy_render::{
mesh::{GpuBufferInfo, Mesh}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets, render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
@ -366,8 +366,7 @@ bitflags::bitflags! {
/// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA.
pub struct MeshPipelineKey: u32 { pub struct MeshPipelineKey: u32 {
const NONE = 0; const NONE = 0;
const VERTEX_TANGENTS = (1 << 0); const TRANSPARENT_MAIN_PASS = (1 << 0);
const TRANSPARENT_MAIN_PASS = (1 << 1);
const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS; const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS;
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
} }
@ -409,71 +408,28 @@ impl MeshPipelineKey {
} }
} }
impl SpecializedPipeline for MeshPipeline { impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey; type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let (vertex_array_stride, vertex_attributes) = &self,
if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { key: Self::Key,
( layout: &MeshVertexBufferLayout,
48, ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
vec![ let mut vertex_attributes = vec![
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
VertexAttribute { Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
format: VertexFormat::Float32x3, Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
offset: 12, ];
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 40,
shader_location: 2,
},
// Tangent
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 24,
shader_location: 3,
},
],
)
} else {
(
32,
vec![
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
],
)
};
let mut shader_defs = Vec::new(); let mut shader_defs = Vec::new();
if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS")); shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
} }
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
let (label, blend, depth_write_enabled); let (label, blend, depth_write_enabled);
if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) {
label = "transparent_mesh_pipeline".into(); label = "transparent_mesh_pipeline".into();
@ -493,16 +449,12 @@ impl SpecializedPipeline for MeshPipeline {
#[cfg(feature = "webgl")] #[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
RenderPipelineDescriptor { Ok(RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(), shader: MESH_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(), entry_point: "vertex".into(),
shader_defs: shader_defs.clone(), shader_defs: shader_defs.clone(),
buffers: vec![VertexBufferLayout { buffers: vec![vertex_buffer_layout],
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(), shader: MESH_SHADER_HANDLE.typed::<Shader>(),
@ -546,7 +498,7 @@ impl SpecializedPipeline for MeshPipeline {
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
label: Some(label), label: Some(label),
} })
} }
} }

View File

@ -5,15 +5,20 @@ use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
use bevy_core_pipeline::Opaque3d; use bevy_core_pipeline::Opaque3d;
use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_reflect::{Reflect, TypeUuid}; use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::render_resource::PolygonMode; use bevy_render::mesh::MeshVertexBufferLayout;
use bevy_render::render_resource::{
PolygonMode, RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
SpecializedMeshPipelines,
};
use bevy_render::{ use bevy_render::{
mesh::Mesh, mesh::Mesh,
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines}, render_resource::{RenderPipelineCache, Shader},
view::{ExtractedView, Msaa}, view::{ExtractedView, Msaa},
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
use bevy_utils::tracing::error;
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766); HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
@ -36,7 +41,7 @@ impl Plugin for WireframePlugin {
render_app render_app
.add_render_command::<Opaque3d, DrawWireframes>() .add_render_command::<Opaque3d, DrawWireframes>()
.init_resource::<WireframePipeline>() .init_resource::<WireframePipeline>()
.init_resource::<SpecializedPipelines<WireframePipeline>>() .init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_wireframes) .add_system_to_stage(RenderStage::Extract, extract_wireframes)
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config) .add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
.add_system_to_stage(RenderStage::Queue, queue_wireframes); .add_system_to_stage(RenderStage::Queue, queue_wireframes);
@ -80,27 +85,32 @@ impl FromWorld for WireframePipeline {
} }
} }
impl SpecializedPipeline for WireframePipeline { impl SpecializedMeshPipeline for WireframePipeline {
type Key = MeshPipelineKey; type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor { fn specialize(
let mut descriptor = self.mesh_pipeline.specialize(key); &self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone_weak(); descriptor.vertex.shader = self.shader.clone_weak();
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
descriptor.primitive.polygon_mode = PolygonMode::Line; descriptor.primitive.polygon_mode = PolygonMode::Line;
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
descriptor Ok(descriptor)
} }
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
fn queue_wireframes( fn queue_wireframes(
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>, opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
wireframe_config: Res<WireframeConfig>, wireframe_config: Res<WireframeConfig>,
wireframe_pipeline: Res<WireframePipeline>, wireframe_pipeline: Res<WireframePipeline>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
mut specialized_pipelines: ResMut<SpecializedPipelines<WireframePipeline>>, mut specialized_pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
mut material_meshes: QuerySet<( mut material_meshes: QuerySet<(
QueryState<(Entity, &Handle<Mesh>, &MeshUniform)>, QueryState<(Entity, &Handle<Mesh>, &MeshUniform)>,
@ -112,7 +122,7 @@ fn queue_wireframes(
.read() .read()
.get_id::<DrawWireframes>() .get_id::<DrawWireframes>()
.unwrap(); .unwrap();
let key = MeshPipelineKey::from_msaa_samples(msaa.samples); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
for (view, mut transparent_phase) in views.iter_mut() { for (view, mut transparent_phase) in views.iter_mut() {
let view_matrix = view.transform.compute_matrix(); let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2); let view_row_2 = view_matrix.row(2);
@ -120,15 +130,24 @@ fn queue_wireframes(
let add_render_phase = let add_render_phase =
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| { |(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
let key = let key = msaa_key
key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline_id = specialized_pipelines.specialize(
&mut pipeline_cache,
&wireframe_pipeline,
key,
&mesh.layout,
);
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
error!("{}", err);
return;
}
};
transparent_phase.add(Opaque3d { transparent_phase.add(Opaque3d {
entity, entity,
pipeline: specialized_pipelines.specialize( pipeline: pipeline_id,
&mut pipeline_cache,
&wireframe_pipeline,
key,
),
draw_function: draw_custom, draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)), distance: view_row_2.dot(mesh_uniform.transform.col(3)),
}); });

View File

@ -3,17 +3,19 @@ mod conversions;
use crate::{ use crate::{
primitives::Aabb, primitives::Aabb,
render_asset::{PrepareAssetError, RenderAsset}, render_asset::{PrepareAssetError, RenderAsset},
render_resource::Buffer, render_resource::{Buffer, VertexBufferLayout},
renderer::RenderDevice, renderer::RenderDevice,
}; };
use bevy_core::cast_slice; use bevy_core::cast_slice;
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::*; use bevy_math::*;
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_utils::EnumVariantMeta; use bevy_utils::{EnumVariantMeta, Hashed};
use std::{borrow::Cow, collections::BTreeMap}; use std::{collections::BTreeMap, hash::Hash};
use thiserror::Error;
use wgpu::{ use wgpu::{
util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat, util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexAttribute,
VertexFormat, VertexStepMode,
}; };
pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
@ -25,10 +27,10 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;
pub struct Mesh { pub struct Mesh {
primitive_topology: PrimitiveTopology, primitive_topology: PrimitiveTopology,
/// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...)
/// for this mesh. Attribute name maps to attribute values. /// for this mesh. Attribute ids to attribute values.
/// Uses a BTreeMap because, unlike HashMap, it has a defined iteration order, /// Uses a BTreeMap because, unlike HashMap, it has a defined iteration order,
/// which allows easy stable VertexBuffers (i.e. same buffer order) /// which allows easy stable VertexBuffers (i.e. same buffer order)
attributes: BTreeMap<Cow<'static, str>, VertexAttributeValues>, attributes: BTreeMap<MeshVertexAttributeId, MeshAttributeData>,
indices: Option<Indices>, indices: Option<Indices>,
} }
@ -44,29 +46,39 @@ pub struct Mesh {
/// # use bevy_render::render_resource::PrimitiveTopology; /// # use bevy_render::render_resource::PrimitiveTopology;
/// fn create_triangle() -> Mesh { /// fn create_triangle() -> Mesh {
/// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); /// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
/// mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]); /// mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]);
/// mesh.set_indices(Some(Indices::U32(vec![0,1,2]))); /// mesh.set_indices(Some(Indices::U32(vec![0,1,2])));
/// mesh /// mesh
/// } /// }
/// ``` /// ```
impl Mesh { impl Mesh {
/// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`] /// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_COLOR: &'static str = "Vertex_Color"; pub const ATTRIBUTE_POSITION: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3);
/// The direction the vertex normal is facing in. /// The direction the vertex normal is facing in.
/// Use in conjunction with [`Mesh::set_attribute`] /// Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal"; pub const ATTRIBUTE_NORMAL: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3);
/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_UV_0: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2);
/// The direction of the vertex tangent. Used for normal mapping /// The direction of the vertex tangent. Used for normal mapping
pub const ATTRIBUTE_TANGENT: &'static str = "Vertex_Tangent"; pub const ATTRIBUTE_TANGENT: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Tangent", 3, VertexFormat::Float32x4);
/// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position"; pub const ATTRIBUTE_COLOR: MeshVertexAttribute =
/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`] MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Uint32);
pub const ATTRIBUTE_UV_0: &'static str = "Vertex_Uv";
/// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::set_attribute`] /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_JOINT_WEIGHT: &'static str = "Vertex_JointWeight"; pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute =
/// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`] MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4);
pub const ATTRIBUTE_JOINT_INDEX: &'static str = "Vertex_JointIndex"; /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_JointIndex", 6, VertexFormat::Uint32);
/// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the /// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the
/// renderer knows how to treat the vertex data. Most of the time this will be /// renderer knows how to treat the vertex data. Most of the time this will be
@ -86,41 +98,62 @@ impl Mesh {
/// Sets the data for a vertex attribute (position, normal etc.). The name will /// Sets the data for a vertex attribute (position, normal etc.). The name will
/// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
pub fn set_attribute( #[inline]
pub fn insert_attribute(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, attribute: MeshVertexAttribute,
values: impl Into<VertexAttributeValues>, values: impl Into<VertexAttributeValues>,
) { ) {
let values: VertexAttributeValues = values.into(); self.attributes.insert(
self.attributes.insert(name.into(), values); attribute.id,
MeshAttributeData {
attribute,
values: values.into(),
},
);
}
#[inline]
pub fn contains_attribute(&self, id: impl Into<MeshVertexAttributeId>) -> bool {
self.attributes.contains_key(&id.into())
} }
/// Retrieves the data currently set to the vertex attribute with the specified `name`. /// Retrieves the data currently set to the vertex attribute with the specified `name`.
pub fn attribute(&self, name: impl Into<Cow<'static, str>>) -> Option<&VertexAttributeValues> { #[inline]
self.attributes.get(&name.into()) pub fn attribute(
&self,
id: impl Into<MeshVertexAttributeId>,
) -> Option<&VertexAttributeValues> {
self.attributes.get(&id.into()).map(|data| &data.values)
} }
/// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
#[inline]
pub fn attribute_mut( pub fn attribute_mut(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, id: impl Into<MeshVertexAttributeId>,
) -> Option<&mut VertexAttributeValues> { ) -> Option<&mut VertexAttributeValues> {
self.attributes.get_mut(&name.into()) self.attributes
.get_mut(&id.into())
.map(|data| &mut data.values)
} }
/// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the
/// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants
/// that use triangles. /// that use triangles.
#[inline]
pub fn set_indices(&mut self, indices: Option<Indices>) { pub fn set_indices(&mut self, indices: Option<Indices>) {
self.indices = indices; self.indices = indices;
} }
/// Retrieves the vertex `indices` of the mesh. /// Retrieves the vertex `indices` of the mesh.
#[inline]
pub fn indices(&self) -> Option<&Indices> { pub fn indices(&self) -> Option<&Indices> {
self.indices.as_ref() self.indices.as_ref()
} }
/// Retrieves the vertex `indices` of the mesh mutably. /// Retrieves the vertex `indices` of the mesh mutably.
#[inline]
pub fn indices_mut(&mut self) -> Option<&mut Indices> { pub fn indices_mut(&mut self) -> Option<&mut Indices> {
self.indices.as_mut() self.indices.as_mut()
} }
@ -134,27 +167,32 @@ impl Mesh {
}) })
} }
// pub fn get_vertex_buffer_layout(&self) -> VertexBufferLayout { /// For a given `descriptor` returns a [`VertexBufferLayout`] compatible with this mesh. If this
// let mut attributes = Vec::new(); /// mesh is not compatible with the given `descriptor` (ex: it is missing vertex attributes), [`None`] will
// let mut accumulated_offset = 0; /// be returned.
// for (attribute_name, attribute_values) in self.attributes.iter() { pub fn get_mesh_vertex_buffer_layout(&self) -> MeshVertexBufferLayout {
// let vertex_format = VertexFormat::from(attribute_values); let mut attributes = Vec::with_capacity(self.attributes.len());
// attributes.push(VertexAttribute { let mut attribute_ids = Vec::with_capacity(self.attributes.len());
// name: attribute_name.clone(), let mut accumulated_offset = 0;
// offset: accumulated_offset, for (index, data) in self.attributes.values().enumerate() {
// format: vertex_format, attribute_ids.push(data.attribute.id);
// shader_location: 0, attributes.push(VertexAttribute {
// }); offset: accumulated_offset,
// accumulated_offset += vertex_format.get_size(); format: data.attribute.format,
// } shader_location: index as u32,
});
accumulated_offset += data.attribute.format.get_size();
}
// VertexBufferLayout { MeshVertexBufferLayout::new(InnerMeshVertexBufferLayout {
// name: Default::default(), layout: VertexBufferLayout {
// stride: accumulated_offset, array_stride: accumulated_offset,
// step_mode: InputStepMode::Vertex, step_mode: VertexStepMode::Vertex,
// attributes, attributes,
// } },
// } attribute_ids,
})
}
/// Counts all vertices of the mesh. /// Counts all vertices of the mesh.
/// ///
@ -162,11 +200,11 @@ impl Mesh {
/// Panics if the attributes have different vertex counts. /// Panics if the attributes have different vertex counts.
pub fn count_vertices(&self) -> usize { pub fn count_vertices(&self) -> usize {
let mut vertex_count: Option<usize> = None; let mut vertex_count: Option<usize> = None;
for (attribute_name, attribute_data) in &self.attributes { for (attribute_id, attribute_data) in self.attributes.iter() {
let attribute_len = attribute_data.len(); let attribute_len = attribute_data.values.len();
if let Some(previous_vertex_count) = vertex_count { if let Some(previous_vertex_count) = vertex_count {
assert_eq!(previous_vertex_count, attribute_len, assert_eq!(previous_vertex_count, attribute_len,
"Attribute {} has a different vertex count ({}) than other attributes ({}) in this mesh.", attribute_name, attribute_len, previous_vertex_count); "{:?} has a different vertex count ({}) than other attributes ({}) in this mesh.", attribute_id, attribute_len, previous_vertex_count);
} }
vertex_count = Some(attribute_len); vertex_count = Some(attribute_len);
} }
@ -182,8 +220,8 @@ impl Mesh {
/// Panics if the attributes have different vertex counts. /// Panics if the attributes have different vertex counts.
pub fn get_vertex_buffer_data(&self) -> Vec<u8> { pub fn get_vertex_buffer_data(&self) -> Vec<u8> {
let mut vertex_size = 0; let mut vertex_size = 0;
for attribute_values in self.attributes.values() { for attribute_data in self.attributes.values() {
let vertex_format = VertexFormat::from(attribute_values); let vertex_format = attribute_data.attribute.format;
vertex_size += vertex_format.get_size() as usize; vertex_size += vertex_format.get_size() as usize;
} }
@ -191,10 +229,9 @@ impl Mesh {
let mut attributes_interleaved_buffer = vec![0; vertex_count * vertex_size]; let mut attributes_interleaved_buffer = vec![0; vertex_count * vertex_size];
// bundle into interleaved buffers // bundle into interleaved buffers
let mut attribute_offset = 0; let mut attribute_offset = 0;
for attribute_values in self.attributes.values() { for attribute_data in self.attributes.values() {
let vertex_format = VertexFormat::from(attribute_values); let attribute_size = attribute_data.attribute.format.get_size() as usize;
let attribute_size = vertex_format.get_size() as usize; let attributes_bytes = attribute_data.values.get_bytes();
let attributes_bytes = attribute_values.get_bytes();
for (vertex_index, attribute_bytes) in for (vertex_index, attribute_bytes) in
attributes_bytes.chunks_exact(attribute_size).enumerate() attributes_bytes.chunks_exact(attribute_size).enumerate()
{ {
@ -232,7 +269,7 @@ impl Mesh {
}; };
for attributes in self.attributes.values_mut() { for attributes in self.attributes.values_mut() {
let indices = indices.iter(); let indices = indices.iter();
match attributes { match &mut attributes.values {
VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices),
VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices),
VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices),
@ -285,7 +322,7 @@ impl Mesh {
.flat_map(|normal| [normal; 3]) .flat_map(|normal| [normal; 3])
.collect(); .collect();
self.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
} }
/// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space
@ -314,6 +351,131 @@ impl Mesh {
} }
} }
#[derive(Debug, Clone)]
pub struct MeshVertexAttribute {
/// The friendly name of the vertex attribute
pub name: &'static str,
/// The _unique_ id of the vertex attribute. This will also determine sort ordering
/// when generating vertex buffers. Built-in / standard attributes will use "close to zero"
/// indices. When in doubt, use a random / very large usize to avoid conflicts.
pub id: MeshVertexAttributeId,
/// The format of the vertex attribute.
pub format: VertexFormat,
}
impl MeshVertexAttribute {
pub const fn new(name: &'static str, id: usize, format: VertexFormat) -> Self {
Self {
name,
id: MeshVertexAttributeId(id),
format,
}
}
pub const fn at_shader_location(&self, shader_location: u32) -> VertexAttributeDescriptor {
VertexAttributeDescriptor::new(shader_location, self.id, self.name)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct MeshVertexAttributeId(usize);
impl From<MeshVertexAttribute> for MeshVertexAttributeId {
fn from(attribute: MeshVertexAttribute) -> Self {
attribute.id
}
}
pub type MeshVertexBufferLayout = Hashed<InnerMeshVertexBufferLayout>;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct InnerMeshVertexBufferLayout {
attribute_ids: Vec<MeshVertexAttributeId>,
layout: VertexBufferLayout,
}
impl InnerMeshVertexBufferLayout {
#[inline]
pub fn contains(&self, attribute_id: impl Into<MeshVertexAttributeId>) -> bool {
self.attribute_ids.contains(&attribute_id.into())
}
#[inline]
pub fn attribute_ids(&self) -> &[MeshVertexAttributeId] {
&self.attribute_ids
}
#[inline]
pub fn layout(&self) -> &VertexBufferLayout {
&self.layout
}
pub fn get_layout(
&self,
attribute_descriptors: &[VertexAttributeDescriptor],
) -> Result<VertexBufferLayout, MissingVertexAttributeError> {
let mut attributes = Vec::with_capacity(attribute_descriptors.len());
for attribute_descriptor in attribute_descriptors.iter() {
if let Some(index) = self
.attribute_ids
.iter()
.position(|id| *id == attribute_descriptor.id)
{
let layout_attribute = &self.layout.attributes[index];
attributes.push(VertexAttribute {
format: layout_attribute.format,
offset: layout_attribute.offset,
shader_location: attribute_descriptor.shader_location,
})
} else {
return Err(MissingVertexAttributeError {
id: attribute_descriptor.id,
name: attribute_descriptor.name,
pipeline_type: None,
});
}
}
Ok(VertexBufferLayout {
array_stride: self.layout.array_stride,
step_mode: self.layout.step_mode,
attributes,
})
}
}
#[derive(Error, Debug)]
#[error("Mesh is missing requested attribute: {name} ({id:?}, pipeline type: {pipeline_type:?})")]
pub struct MissingVertexAttributeError {
pub(crate) pipeline_type: Option<&'static str>,
id: MeshVertexAttributeId,
name: &'static str,
}
pub struct VertexAttributeDescriptor {
pub shader_location: u32,
pub id: MeshVertexAttributeId,
name: &'static str,
}
impl VertexAttributeDescriptor {
pub const fn new(shader_location: u32, id: MeshVertexAttributeId, name: &'static str) -> Self {
Self {
shader_location,
id,
name,
}
}
}
#[derive(Debug, Clone)]
struct MeshAttributeData {
attribute: MeshVertexAttribute,
values: VertexAttributeValues,
}
const VEC3_MIN: Vec3 = const_vec3!([std::f32::MIN, std::f32::MIN, std::f32::MIN]); const VEC3_MIN: Vec3 = const_vec3!([std::f32::MIN, std::f32::MIN, std::f32::MIN]);
const VEC3_MAX: Vec3 = const_vec3!([std::f32::MAX, std::f32::MAX, std::f32::MAX]); const VEC3_MAX: Vec3 = const_vec3!([std::f32::MAX, std::f32::MAX, std::f32::MAX]);
@ -521,7 +683,6 @@ impl From<&VertexAttributeValues> for VertexFormat {
} }
} }
} }
/// An array of indices into the [`VertexAttributeValues`] for a mesh. /// An array of indices into the [`VertexAttributeValues`] for a mesh.
/// ///
/// It describes the order in which the vertex attributes should be joined into faces. /// It describes the order in which the vertex attributes should be joined into faces.
@ -590,8 +751,8 @@ pub struct GpuMesh {
/// Contains all attribute data for each vertex. /// Contains all attribute data for each vertex.
pub vertex_buffer: Buffer, pub vertex_buffer: Buffer,
pub buffer_info: GpuBufferInfo, pub buffer_info: GpuBufferInfo,
pub has_tangents: bool,
pub primitive_topology: PrimitiveTopology, pub primitive_topology: PrimitiveTopology,
pub layout: MeshVertexBufferLayout,
} }
/// The index/vertex buffer info of a [`GpuMesh`]. /// The index/vertex buffer info of a [`GpuMesh`].
@ -645,11 +806,13 @@ impl RenderAsset for Mesh {
}, },
); );
let mesh_vertex_buffer_layout = mesh.get_mesh_vertex_buffer_layout();
Ok(GpuMesh { Ok(GpuMesh {
vertex_buffer, vertex_buffer,
buffer_info, buffer_info,
has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT),
primitive_topology: mesh.primitive_topology(), primitive_topology: mesh.primitive_topology(),
layout: mesh_vertex_buffer_layout,
}) })
} }
} }

View File

@ -370,9 +370,9 @@ impl From<Capsule> for Mesh {
assert_eq!(tris.len(), fs_len); assert_eq!(tris.len(), fs_len);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vs); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vs);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vns); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vns);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vts); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vts);
mesh.set_indices(Some(Indices::U32(tris))); mesh.set_indices(Some(Indices::U32(tris)));
mesh mesh
} }

View File

@ -95,9 +95,9 @@ impl From<Icosphere> for Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices)); mesh.set_indices(Some(indices));
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, points); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh mesh
} }
} }

View File

@ -112,9 +112,9 @@ impl From<Box> for Mesh {
]); ]);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh.set_indices(Some(indices)); mesh.set_indices(Some(indices));
mesh mesh
} }
@ -171,9 +171,9 @@ impl From<Quad> for Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices)); mesh.set_indices(Some(indices));
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh mesh
} }
} }
@ -215,9 +215,9 @@ impl From<Plane> for Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices)); mesh.set_indices(Some(indices));
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh mesh
} }
} }

View File

@ -90,9 +90,9 @@ impl From<Torus> for Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(Indices::U32(indices))); mesh.set_indices(Some(Indices::U32(indices)));
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh mesh
} }
} }

View File

@ -82,9 +82,9 @@ impl From<UVSphere> for Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(Indices::U32(indices))); mesh.set_indices(Some(Indices::U32(indices)));
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertices); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh mesh
} }
} }

View File

@ -10,6 +10,12 @@ pub struct BindGroupLayout {
value: Arc<wgpu::BindGroupLayout>, value: Arc<wgpu::BindGroupLayout>,
} }
impl PartialEq for BindGroupLayout {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl BindGroupLayout { impl BindGroupLayout {
#[inline] #[inline]
pub fn id(&self) -> BindGroupLayoutId { pub fn id(&self) -> BindGroupLayoutId {

View File

@ -4,7 +4,7 @@ use bevy_reflect::Uuid;
use std::{borrow::Cow, ops::Deref, sync::Arc}; use std::{borrow::Cow, ops::Deref, sync::Arc};
use wgpu::{ use wgpu::{
BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState,
VertexAttribute, VertexStepMode, VertexAttribute, VertexFormat, VertexStepMode,
}; };
/// A [`RenderPipeline`] identifier. /// A [`RenderPipeline`] identifier.
@ -87,7 +87,7 @@ impl Deref for ComputePipeline {
} }
/// Describes a render (graphics) pipeline. /// Describes a render (graphics) pipeline.
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct RenderPipelineDescriptor { pub struct RenderPipelineDescriptor {
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification.
pub label: Option<Cow<'static, str>>, pub label: Option<Cow<'static, str>>,
@ -105,7 +105,7 @@ pub struct RenderPipelineDescriptor {
pub fragment: Option<FragmentState>, pub fragment: Option<FragmentState>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct VertexState { pub struct VertexState {
/// The compiled shader module for this stage. /// The compiled shader module for this stage.
pub shader: Handle<Shader>, pub shader: Handle<Shader>,
@ -118,7 +118,7 @@ pub struct VertexState {
} }
/// Describes how the vertex buffer is interpreted. /// Describes how the vertex buffer is interpreted.
#[derive(Clone, Debug, Hash, Eq, PartialEq)] #[derive(Default, Clone, Debug, Hash, Eq, PartialEq)]
pub struct VertexBufferLayout { pub struct VertexBufferLayout {
/// The stride, in bytes, between elements of this buffer. /// The stride, in bytes, between elements of this buffer.
pub array_stride: BufferAddress, pub array_stride: BufferAddress,
@ -128,8 +128,36 @@ pub struct VertexBufferLayout {
pub attributes: Vec<VertexAttribute>, pub attributes: Vec<VertexAttribute>,
} }
impl VertexBufferLayout {
/// Creates a new densely packed [`VertexBufferLayout`] from an iterator of vertex formats.
/// Iteration order determines the `shader_location` and `offset` of the [`VertexAttributes`](VertexAttribute).
/// The first iterated item will have a `shader_location` and `offset` of zero.
/// The `array_stride` is the sum of the size of the iterated [`VertexFormats`](VertexFormat) (in bytes).
pub fn from_vertex_formats<T: IntoIterator<Item = VertexFormat>>(
step_mode: VertexStepMode,
vertex_formats: T,
) -> Self {
let mut offset = 0;
let mut attributes = Vec::new();
for (shader_location, format) in vertex_formats.into_iter().enumerate() {
attributes.push(VertexAttribute {
format,
offset,
shader_location: shader_location as u32,
});
offset += format.size();
}
VertexBufferLayout {
array_stride: offset,
step_mode,
attributes,
}
}
}
/// Describes the fragment process in a render pipeline. /// Describes the fragment process in a render pipeline.
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct FragmentState { pub struct FragmentState {
/// The compiled shader module for this stage. /// The compiled shader module for this stage.
pub shader: Handle<Shader>, pub shader: Handle<Shader>,

View File

@ -13,7 +13,7 @@ use bevy_ecs::system::{Res, ResMut};
use bevy_utils::{tracing::error, Entry, HashMap, HashSet}; use bevy_utils::{tracing::error, Entry, HashMap, HashSet};
use std::{hash::Hash, ops::Deref, sync::Arc}; use std::{hash::Hash, ops::Deref, sync::Arc};
use thiserror::Error; use thiserror::Error;
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout};
use super::ProcessedShader; use super::ProcessedShader;
@ -244,6 +244,11 @@ impl RenderPipelineCache {
&self.pipelines[id.0].state &self.pipelines[id.0].state
} }
#[inline]
pub fn get_descriptor(&self, id: CachedPipelineId) -> &RenderPipelineDescriptor {
&self.pipelines[id.0].descriptor
}
#[inline] #[inline]
pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> {
if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state { if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state {
@ -345,7 +350,7 @@ impl RenderPipelineCache {
.vertex .vertex
.buffers .buffers
.iter() .iter()
.map(|layout| VertexBufferLayout { .map(|layout| RawVertexBufferLayout {
array_stride: layout.array_stride, array_stride: layout.array_stride,
attributes: &layout.attributes, attributes: &layout.attributes,
step_mode: layout.step_mode, step_mode: layout.step_mode,

View File

@ -1,6 +1,20 @@
use crate::render_resource::{CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor}; use crate::{
use bevy_utils::HashMap; mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError},
render_resource::{
CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor, VertexBufferLayout,
},
};
use bevy_utils::{
hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap, PreHashMap, PreHashMapExt,
};
use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use thiserror::Error;
pub trait SpecializedPipeline {
type Key: Clone + Hash + PartialEq + Eq;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;
}
pub struct SpecializedPipelines<S: SpecializedPipeline> { pub struct SpecializedPipelines<S: SpecializedPipeline> {
cache: HashMap<S::Key, CachedPipelineId>, cache: HashMap<S::Key, CachedPipelineId>,
@ -28,7 +42,89 @@ impl<S: SpecializedPipeline> SpecializedPipelines<S> {
} }
} }
pub trait SpecializedPipeline { #[derive(Error, Debug)]
type Key: Clone + Hash + PartialEq + Eq; pub enum SpecializedMeshPipelineError {
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; #[error(transparent)]
MissingVertexAttribute(#[from] MissingVertexAttributeError),
}
pub trait SpecializedMeshPipeline {
type Key: Clone + Hash + PartialEq + Eq;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError>;
}
pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {
mesh_layout_cache: PreHashMap<InnerMeshVertexBufferLayout, HashMap<S::Key, CachedPipelineId>>,
vertex_layout_cache: HashMap<VertexBufferLayout, HashMap<S::Key, CachedPipelineId>>,
}
impl<S: SpecializedMeshPipeline> Default for SpecializedMeshPipelines<S> {
fn default() -> Self {
Self {
mesh_layout_cache: Default::default(),
vertex_layout_cache: Default::default(),
}
}
}
impl<S: SpecializedMeshPipeline> SpecializedMeshPipelines<S> {
#[inline]
pub fn specialize(
&mut self,
cache: &mut RenderPipelineCache,
specialize_pipeline: &S,
key: S::Key,
layout: &MeshVertexBufferLayout,
) -> Result<CachedPipelineId, SpecializedMeshPipelineError> {
let map = self
.mesh_layout_cache
.get_or_insert_with(layout, Default::default);
match map.entry(key.clone()) {
Entry::Occupied(entry) => Ok(*entry.into_mut()),
Entry::Vacant(entry) => {
let descriptor = specialize_pipeline
.specialize(key.clone(), layout)
.map_err(|mut err| {
{
let SpecializedMeshPipelineError::MissingVertexAttribute(err) =
&mut err;
err.pipeline_type = Some(std::any::type_name::<S>());
}
err
})?;
// Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout
// We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them
let layout_map = match self
.vertex_layout_cache
.raw_entry_mut()
.from_key(&descriptor.vertex.buffers[0])
{
RawEntryMut::Occupied(entry) => entry.into_mut(),
RawEntryMut::Vacant(entry) => {
entry
.insert(descriptor.vertex.buffers[0].clone(), Default::default())
.1
}
};
Ok(*entry.insert(match layout_map.entry(key) {
Entry::Occupied(entry) => {
if cfg!(debug_assertions) {
let stored_descriptor = cache.get_descriptor(*entry.get());
if stored_descriptor != &descriptor {
error!("The cached pipeline descriptor for {} is not equal to the generated descriptor for the given key. This means the SpecializePipeline implementation uses 'unused' MeshVertexBufferLayout information to specialize the pipeline. This is not allowed because it would invalidate the pipeline cache.", std::any::type_name::<S>());
}
}
*entry.into_mut()
}
Entry::Vacant(entry) => {
*entry.insert(cache.queue(descriptor))
}
}))
}
}
}
} }

View File

@ -11,8 +11,9 @@ use bevy_ecs::{
}, },
world::FromWorld, world::FromWorld,
}; };
use bevy_log::error;
use bevy_render::{ use bevy_render::{
mesh::Mesh, mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin, render_component::ExtractComponentPlugin,
render_phase::{ render_phase::{
@ -21,7 +22,7 @@ use bevy_render::{
}, },
render_resource::{ render_resource::{
BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader,
SpecializedPipeline, SpecializedPipelines, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
}, },
renderer::RenderDevice, renderer::RenderDevice,
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, view::{ComputedVisibility, Msaa, Visibility, VisibleEntities},
@ -69,6 +70,16 @@ pub trait Material2d: Asset + RenderAsset {
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] { fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
&[] &[]
} }
/// Customizes the default [`RenderPipelineDescriptor`].
#[allow(unused_variables)]
#[inline]
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
} }
impl<M: Material2d> SpecializedMaterial2d for M { impl<M: Material2d> SpecializedMaterial2d for M {
@ -78,7 +89,13 @@ impl<M: Material2d> SpecializedMaterial2d for M {
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {} fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
#[inline] #[inline]
fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {} fn specialize(
_key: Self::Key,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
<M as Material2d>::specialize(descriptor, layout)
}
#[inline] #[inline]
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup { fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
@ -122,7 +139,11 @@ pub trait SpecializedMaterial2d: Asset + RenderAsset {
fn key(material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key; fn key(material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key;
/// Specializes the given `descriptor` according to the given `key`. /// Specializes the given `descriptor` according to the given `key`.
fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor); fn specialize(
key: Self::Key,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError>;
/// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial2d::bind_group_layout`]. /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial2d::bind_group_layout`].
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup; fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
@ -172,7 +193,7 @@ impl<M: SpecializedMaterial2d> Plugin for Material2dPlugin<M> {
render_app render_app
.add_render_command::<Transparent2d, DrawMaterial2d<M>>() .add_render_command::<Transparent2d, DrawMaterial2d<M>>()
.init_resource::<Material2dPipeline<M>>() .init_resource::<Material2dPipeline<M>>()
.init_resource::<SpecializedPipelines<Material2dPipeline<M>>>() .init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
.add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::<M>); .add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::<M>);
} }
} }
@ -186,11 +207,21 @@ pub struct Material2dPipeline<M: SpecializedMaterial2d> {
marker: PhantomData<M>, marker: PhantomData<M>,
} }
impl<M: SpecializedMaterial2d> SpecializedPipeline for Material2dPipeline<M> { #[derive(Eq, PartialEq, Clone, Hash)]
type Key = (Mesh2dPipelineKey, M::Key); pub struct Material2dKey<T> {
mesh_key: Mesh2dPipelineKey,
material_key: T,
}
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { impl<M: SpecializedMaterial2d> SpecializedMeshPipeline for Material2dPipeline<M> {
let mut descriptor = self.mesh2d_pipeline.specialize(key.0); type Key = Material2dKey<M::Key>;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh2d_pipeline.specialize(key.mesh_key, layout)?;
if let Some(vertex_shader) = &self.vertex_shader { if let Some(vertex_shader) = &self.vertex_shader {
descriptor.vertex.shader = vertex_shader.clone(); descriptor.vertex.shader = vertex_shader.clone();
} }
@ -204,8 +235,8 @@ impl<M: SpecializedMaterial2d> SpecializedPipeline for Material2dPipeline<M> {
self.mesh2d_pipeline.mesh_layout.clone(), self.mesh2d_pipeline.mesh_layout.clone(),
]); ]);
M::specialize(key.1, &mut descriptor); M::specialize(key.material_key, &mut descriptor, layout)?;
descriptor Ok(descriptor)
} }
} }
@ -259,7 +290,7 @@ impl<M: SpecializedMaterial2d, const I: usize> EntityRenderCommand
pub fn queue_material2d_meshes<M: SpecializedMaterial2d>( pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>, transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
material2d_pipeline: Res<Material2dPipeline<M>>, material2d_pipeline: Res<Material2dPipeline<M>>,
mut pipelines: ResMut<SpecializedPipelines<Material2dPipeline<M>>>, mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
@ -276,42 +307,50 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
.get_id::<DrawMaterial2d<M>>() .get_id::<DrawMaterial2d<M>>()
.unwrap(); .unwrap();
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples);
for visible_entity in &visible_entities.entities { for visible_entity in &visible_entities.entities {
if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) =
material2d_meshes.get(*visible_entity) material2d_meshes.get(*visible_entity)
{ {
if let Some(material2d) = render_materials.get(material2d_handle) { if let Some(material2d) = render_materials.get(material2d_handle) {
let mut mesh2d_key = mesh_key;
if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) {
if mesh.has_tangents { let mesh_key = msaa_key
mesh2d_key |= Mesh2dPipelineKey::VERTEX_TANGENTS; | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
}
mesh2d_key |= let material_key = M::key(material2d);
Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material2d_pipeline,
Material2dKey {
mesh_key,
material_key,
},
&mesh.layout,
);
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
error!("{}", err);
continue;
}
};
let mesh_z = mesh2d_uniform.transform.w_axis.z;
transparent_phase.add(Transparent2d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
pipeline: pipeline_id,
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
// lowest sort key and getting closer should increase. As we have
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
sort_key: FloatOrd(mesh_z),
// This material is not batched
batch_range: None,
});
} }
let specialized_key = M::key(material2d);
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material2d_pipeline,
(mesh2d_key, specialized_key),
);
let mesh_z = mesh2d_uniform.transform.w_axis.z;
transparent_phase.add(Transparent2d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
pipeline: pipeline_id,
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
// lowest sort key and getting closer should increase. As we have
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
sort_key: FloatOrd(mesh_z),
// This material is not batched
batch_range: None,
});
} }
} }
} }

View File

@ -7,7 +7,7 @@ use bevy_ecs::{
use bevy_math::{Mat4, Size}; use bevy_math::{Mat4, Size};
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_render::{ use bevy_render::{
mesh::{GpuBufferInfo, Mesh}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets, render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
@ -62,7 +62,7 @@ impl Plugin for Mesh2dRenderPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app render_app
.init_resource::<Mesh2dPipeline>() .init_resource::<Mesh2dPipeline>()
.init_resource::<SpecializedPipelines<Mesh2dPipeline>>() .init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_mesh2d) .add_system_to_stage(RenderStage::Extract, extract_mesh2d)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups);
@ -232,7 +232,6 @@ bitflags::bitflags! {
// FIXME: make normals optional? // FIXME: make normals optional?
pub struct Mesh2dPipelineKey: u32 { pub struct Mesh2dPipelineKey: u32 {
const NONE = 0; const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
const MSAA_RESERVED_BITS = Mesh2dPipelineKey::MSAA_MASK_BITS << Mesh2dPipelineKey::MSAA_SHIFT_BITS; const MSAA_RESERVED_BITS = Mesh2dPipelineKey::MSAA_MASK_BITS << Mesh2dPipelineKey::MSAA_SHIFT_BITS;
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
} }
@ -274,84 +273,37 @@ impl Mesh2dPipelineKey {
} }
} }
impl SpecializedPipeline for Mesh2dPipeline { impl SpecializedMeshPipeline for Mesh2dPipeline {
type Key = Mesh2dPipelineKey; type Key = Mesh2dPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let (vertex_array_stride, vertex_attributes) = &self,
if key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) { key: Self::Key,
( layout: &MeshVertexBufferLayout,
48, ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
vec![ let mut vertex_attributes = vec![
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
VertexAttribute { Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
format: VertexFormat::Float32x3, Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
offset: 12, ];
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 40,
shader_location: 2,
},
// Tangent
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 24,
shader_location: 3,
},
],
)
} else {
(
32,
vec![
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
],
)
};
let mut shader_defs = Vec::new(); let mut shader_defs = Vec::new();
if key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) { if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS")); shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
} }
#[cfg(feature = "webgl")] #[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
RenderPipelineDescriptor { let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
Ok(RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: MESH2D_SHADER_HANDLE.typed::<Shader>(), shader: MESH2D_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(), entry_point: "vertex".into(),
shader_defs: shader_defs.clone(), shader_defs: shader_defs.clone(),
buffers: vec![VertexBufferLayout { buffers: vec![vertex_buffer_layout],
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
shader: MESH2D_SHADER_HANDLE.typed::<Shader>(), shader: MESH2D_SHADER_HANDLE.typed::<Shader>(),
@ -380,7 +332,7 @@ impl SpecializedPipeline for Mesh2dPipeline {
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
label: Some("transparent_mesh2d_pipeline".into()), label: Some("transparent_mesh2d_pipeline".into()),
} })
} }
} }

View File

@ -113,31 +113,24 @@ impl SpecializedPipeline for SpritePipeline {
type Key = SpritePipelineKey; type Key = SpritePipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut vertex_buffer_layout = VertexBufferLayout { let mut formats = vec![
array_stride: 20, // position
step_mode: VertexStepMode::Vertex, VertexFormat::Float32x3,
attributes: vec![ // uv
VertexAttribute { VertexFormat::Float32x2,
format: VertexFormat::Float32x3, ];
offset: 0,
shader_location: 0, if key.contains(SpritePipelineKey::COLORED) {
}, // color
VertexAttribute { formats.push(VertexFormat::Uint32);
format: VertexFormat::Float32x2, }
offset: 12,
shader_location: 1, let vertex_layout =
}, VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
],
};
let mut shader_defs = Vec::new(); let mut shader_defs = Vec::new();
if key.contains(SpritePipelineKey::COLORED) { if key.contains(SpritePipelineKey::COLORED) {
shader_defs.push("COLORED".to_string()); shader_defs.push("COLORED".to_string());
vertex_buffer_layout.attributes.push(VertexAttribute {
format: VertexFormat::Uint32,
offset: 20,
shader_location: 2,
});
vertex_buffer_layout.array_stride += 4;
} }
RenderPipelineDescriptor { RenderPipelineDescriptor {
@ -145,7 +138,7 @@ impl SpecializedPipeline for SpritePipeline {
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(), shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(), entry_point: "vertex".into(),
shader_defs: shader_defs.clone(), shader_defs: shader_defs.clone(),
buffers: vec![vertex_buffer_layout], buffers: vec![vertex_layout],
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(), shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),

View File

@ -66,29 +66,17 @@ impl SpecializedPipeline for UiPipeline {
type Key = UiPipelineKey; type Key = UiPipelineKey;
/// FIXME: there are no specialization for now, should this be removed? /// FIXME: there are no specialization for now, should this be removed?
fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor {
let vertex_buffer_layout = VertexBufferLayout { let vertex_layout = VertexBufferLayout::from_vertex_formats(
array_stride: 24, VertexStepMode::Vertex,
step_mode: VertexStepMode::Vertex, vec![
attributes: vec![ // position
// Position VertexFormat::Float32x3,
VertexAttribute { // uv
format: VertexFormat::Float32x3, VertexFormat::Float32x2,
offset: 0, // color
shader_location: 0, VertexFormat::Uint32,
},
// UV
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 12,
shader_location: 1,
},
VertexAttribute {
format: VertexFormat::Uint32,
offset: 20,
shader_location: 2,
},
], ],
}; );
let shader_defs = Vec::new(); let shader_defs = Vec::new();
RenderPipelineDescriptor { RenderPipelineDescriptor {
@ -96,7 +84,7 @@ impl SpecializedPipeline for UiPipeline {
shader: super::UI_SHADER_HANDLE.typed::<Shader>(), shader: super::UI_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(), entry_point: "vertex".into(),
shader_defs: shader_defs.clone(), shader_defs: shader_defs.clone(),
buffers: vec![vertex_buffer_layout], buffers: vec![vertex_layout],
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
shader: super::UI_SHADER_HANDLE.typed::<Shader>(), shader: super::UI_SHADER_HANDLE.typed::<Shader>(),

View File

@ -68,11 +68,11 @@ fn star(
v_pos.push([r * a.cos(), r * a.sin(), 0.0]); v_pos.push([r * a.cos(), r * a.sin(), 0.0]);
} }
// Set the position attribute // Set the position attribute
star.set_attribute(Mesh::ATTRIBUTE_POSITION, v_pos); star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
// And a RGB color attribute as well // And a RGB color attribute as well
let mut v_color = vec![[0.0, 0.0, 0.0, 1.0]]; let mut v_color = vec![[0.0, 0.0, 0.0, 1.0]];
v_color.extend_from_slice(&[[1.0, 1.0, 0.0, 1.0]; 10]); v_color.extend_from_slice(&[[1.0, 1.0, 0.0, 1.0]; 10]);
star.set_attribute(Mesh::ATTRIBUTE_COLOR, v_color); star.insert_attribute(Mesh::ATTRIBUTE_COLOR, v_color);
// Now, we specify the indices of the vertex that are going to compose the // Now, we specify the indices of the vertex that are going to compose the
// triangles in our star. Vertices in triangles have to be specified in CCW // triangles in our star. Vertices in triangles have to be specified in CCW

View File

@ -222,6 +222,7 @@ Example | File | Description
Example | File | Description Example | File | Description
--- | --- | --- --- | --- | ---
`custom_vertex_attribute` | [`shader/custom_vertex_attribute.rs`](./shader/custom_vertex_attribute.rs) | Illustrates creating a custom shader material that reads a mesh's custom vertex attribute.
`shader_material` | [`shader/shader_material.rs`](./shader/shader_material.rs) | Illustrates creating a custom material and a shader that uses it `shader_material` | [`shader/shader_material.rs`](./shader/shader_material.rs) | Illustrates creating a custom material and a shader that uses it
`shader_material_glsl` | [`shader/shader_material_glsl.rs`](./shader/shader_material_glsl.rs) | A custom shader using the GLSL shading language. `shader_material_glsl` | [`shader/shader_material_glsl.rs`](./shader/shader_material_glsl.rs) | A custom shader using the GLSL shading language.
`shader_instancing` | [`shader/shader_instancing.rs`](./shader/shader_instancing.rs) | A custom shader showing off rendering a mesh multiple times in one draw call. `shader_instancing` | [`shader/shader_instancing.rs`](./shader/shader_instancing.rs) | A custom shader showing off rendering a mesh multiple times in one draw call.

View File

@ -7,6 +7,8 @@ use bevy::{
}, },
prelude::*, prelude::*,
render::{ render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_phase::{ render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass, SetItemPipeline, TrackedRenderPass,
@ -66,7 +68,7 @@ impl Plugin for CustomMaterialPlugin {
bind_group: None, bind_group: None,
}) })
.init_resource::<CustomPipeline>() .init_resource::<CustomPipeline>()
.init_resource::<SpecializedPipelines<CustomPipeline>>() .init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_time) .add_system_to_stage(RenderStage::Extract, extract_time)
.add_system_to_stage(RenderStage::Extract, extract_custom_material) .add_system_to_stage(RenderStage::Extract, extract_custom_material)
.add_system_to_stage(RenderStage::Prepare, prepare_time) .add_system_to_stage(RenderStage::Prepare, prepare_time)
@ -90,13 +92,15 @@ fn extract_custom_material(
} }
// add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline` // add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline`
#[allow(clippy::too_many_arguments)]
fn queue_custom( fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>, transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
custom_pipeline: Res<CustomPipeline>, custom_pipeline: Res<CustomPipeline>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedPipelines<CustomPipeline>>, mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
material_meshes: Query<(Entity, &MeshUniform), (With<Handle<Mesh>>, With<CustomMaterial>)>, render_meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>), With<CustomMaterial>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>, mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
) { ) {
let draw_custom = transparent_3d_draw_functions let draw_custom = transparent_3d_draw_functions
@ -106,18 +110,22 @@ fn queue_custom(
let key = MeshPipelineKey::from_msaa_samples(msaa.samples) let key = MeshPipelineKey::from_msaa_samples(msaa.samples)
| MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); | MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key);
for (view, mut transparent_phase) in views.iter_mut() { for (view, mut transparent_phase) in views.iter_mut() {
let view_matrix = view.transform.compute_matrix(); let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2); let view_row_2 = view_matrix.row(2);
for (entity, mesh_uniform) in material_meshes.iter() { for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
transparent_phase.add(Transparent3d { if let Some(mesh) = render_meshes.get(mesh_handle) {
entity, let pipeline = pipelines
pipeline, .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout)
draw_function: draw_custom, .unwrap();
distance: view_row_2.dot(mesh_uniform.transform.col(3)), transparent_phase.add(Transparent3d {
}); entity,
pipeline,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});
}
} }
} }
} }
@ -207,11 +215,15 @@ impl FromWorld for CustomPipeline {
} }
} }
impl SpecializedPipeline for CustomPipeline { impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey; type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let mut descriptor = self.mesh_pipeline.specialize(key); &self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader = self.shader.clone();
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();
descriptor.layout = Some(vec![ descriptor.layout = Some(vec![
@ -219,7 +231,7 @@ impl SpecializedPipeline for CustomPipeline {
self.mesh_pipeline.mesh_layout.clone(), self.mesh_pipeline.mesh_layout.clone(),
self.time_bind_group_layout.clone(), self.time_bind_group_layout.clone(),
]); ]);
descriptor Ok(descriptor)
} }
} }

View File

@ -0,0 +1,150 @@
use bevy::{
ecs::system::{lifetimeless::SRes, SystemParamItem},
pbr::MaterialPipeline,
prelude::*,
reflect::TypeUuid,
render::{
mesh::{MeshVertexAttribute, MeshVertexBufferLayout},
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{
std140::{AsStd140, Std140},
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer,
BufferBindingType, BufferInitDescriptor, BufferSize, BufferUsages,
RenderPipelineDescriptor, ShaderStages, SpecializedMeshPipelineError, VertexFormat,
},
renderer::RenderDevice,
},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MaterialPlugin::<CustomMaterial>::default())
.add_startup_system(setup)
.run();
}
// A "high" random id should be used for custom attributes to ensure consistent sorting and avoid collisions with other attributes.
// See the MeshVertexAttribute docs for more info.
const ATTRIBUTE_BLEND_COLOR: MeshVertexAttribute =
MeshVertexAttribute::new("BlendColor", 988540917, VertexFormat::Float32x4);
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<CustomMaterial>>,
) {
let mut mesh = Mesh::from(shape::Cube { size: 1.0 });
mesh.insert_attribute(
ATTRIBUTE_BLEND_COLOR,
// The cube mesh has 24 vertices (6 faces, 4 vertices per face), so we insert one BlendColor for each
vec![[1.0, 0.0, 0.0, 1.0]; 24],
);
// cube
commands.spawn().insert_bundle(MaterialMeshBundle {
mesh: meshes.add(mesh),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
material: materials.add(CustomMaterial {
color: Color::WHITE,
}),
..Default::default()
});
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}
// This is the struct that will be passed to your shader
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
pub struct CustomMaterial {
color: Color,
}
#[derive(Clone)]
pub struct GpuCustomMaterial {
_buffer: Buffer,
bind_group: BindGroup,
}
// The implementation of [`Material`] needs this impl to work properly.
impl RenderAsset for CustomMaterial {
type ExtractedAsset = CustomMaterial;
type PreparedAsset = GpuCustomMaterial;
type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
fn prepare_asset(
extracted_asset: Self::ExtractedAsset,
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
contents: color.as_std140().as_bytes(),
label: None,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
label: None,
layout: &material_pipeline.material_layout,
});
Ok(GpuCustomMaterial {
_buffer: buffer,
bind_group,
})
}
}
impl Material for CustomMaterial {
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
Some(asset_server.load("shaders/custom_vertex_attribute.wgsl"))
}
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
Some(asset_server.load("shaders/custom_vertex_attribute.wgsl"))
}
fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
&render_asset.bind_group
}
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64),
},
count: None,
}],
label: None,
})
}
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
let vertex_layout = layout.get_layout(&[
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
ATTRIBUTE_BLEND_COLOR.at_shader_location(1),
])?;
descriptor.vertex.buffers = vec![vertex_layout];
Ok(())
}
}

View File

@ -6,12 +6,13 @@ use bevy::{
}, },
prelude::*, prelude::*,
render::{ render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets, render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin}, render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{ render_resource::{
RenderPipelineCache, RenderPipelineDescriptor, SpecializedPipeline, RenderPipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline,
SpecializedPipelines, SpecializedMeshPipelineError, SpecializedMeshPipelines,
}, },
view::ExtractedView, view::ExtractedView,
RenderApp, RenderStage, RenderApp, RenderStage,
@ -26,7 +27,7 @@ impl Plugin for IsRedPlugin {
app.sub_app_mut(RenderApp) app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawIsRed>() .add_render_command::<Transparent3d, DrawIsRed>()
.init_resource::<IsRedPipeline>() .init_resource::<IsRedPipeline>()
.init_resource::<SpecializedPipelines<IsRedPipeline>>() .init_resource::<SpecializedMeshPipelines<IsRedPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_custom); .add_system_to_stage(RenderStage::Queue, queue_custom);
} }
} }
@ -98,15 +99,19 @@ impl FromWorld for IsRedPipeline {
} }
} }
impl SpecializedPipeline for IsRedPipeline { impl SpecializedMeshPipeline for IsRedPipeline {
type Key = (IsRed, MeshPipelineKey); type Key = (IsRed, MeshPipelineKey);
fn specialize(&self, (is_red, pbr_pipeline_key): Self::Key) -> RenderPipelineDescriptor { fn specialize(
&self,
(is_red, pbr_pipeline_key): Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = Vec::new(); let mut shader_defs = Vec::new();
if is_red.0 { if is_red.0 {
shader_defs.push("IS_RED".to_string()); shader_defs.push("IS_RED".to_string());
} }
let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key); let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key, layout)?;
descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader = self.shader.clone();
descriptor.vertex.shader_defs = shader_defs.clone(); descriptor.vertex.shader_defs = shader_defs.clone();
let fragment = descriptor.fragment.as_mut().unwrap(); let fragment = descriptor.fragment.as_mut().unwrap();
@ -116,7 +121,7 @@ impl SpecializedPipeline for IsRedPipeline {
self.mesh_pipeline.view_layout.clone(), self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(), self.mesh_pipeline.mesh_layout.clone(),
]); ]);
descriptor Ok(descriptor)
} }
} }
@ -133,7 +138,7 @@ fn queue_custom(
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
custom_pipeline: Res<IsRedPipeline>, custom_pipeline: Res<IsRedPipeline>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedPipelines<IsRedPipeline>>, mut pipelines: ResMut<SpecializedMeshPipelines<IsRedPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
material_meshes: Query<(Entity, &Handle<Mesh>, &MeshUniform, &IsRed)>, material_meshes: Query<(Entity, &Handle<Mesh>, &MeshUniform, &IsRed)>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>, mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
@ -142,15 +147,22 @@ fn queue_custom(
.read() .read()
.get_id::<DrawIsRed>() .get_id::<DrawIsRed>()
.unwrap(); .unwrap();
let key = MeshPipelineKey::from_msaa_samples(msaa.samples); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
for (view, mut transparent_phase) in views.iter_mut() { for (view, mut transparent_phase) in views.iter_mut() {
let view_matrix = view.transform.compute_matrix(); let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2); let view_row_2 = view_matrix.row(2);
for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() { for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() {
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); let key =
let pipeline = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
pipelines.specialize(&mut pipeline_cache, &custom_pipeline, (*is_red, key)); let pipeline = pipelines
.specialize(
&mut pipeline_cache,
&custom_pipeline,
(*is_red, key),
&mesh.layout,
)
.unwrap();
transparent_phase.add(Transparent3d { transparent_phase.add(Transparent3d {
entity, entity,
pipeline, pipeline,

View File

@ -5,7 +5,7 @@ use bevy::{
pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}, pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup},
prelude::*, prelude::*,
render::{ render::{
mesh::GpuBufferInfo, mesh::{GpuBufferInfo, MeshVertexBufferLayout},
render_asset::RenderAssets, render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin}, render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{ render_phase::{
@ -81,7 +81,7 @@ impl Plugin for CustomMaterialPlugin {
app.sub_app_mut(RenderApp) app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>() .add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<CustomPipeline>() .init_resource::<CustomPipeline>()
.init_resource::<SpecializedPipelines<CustomPipeline>>() .init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_custom) .add_system_to_stage(RenderStage::Queue, queue_custom)
.add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers); .add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers);
} }
@ -95,14 +95,16 @@ struct InstanceData {
color: [f32; 4], color: [f32; 4],
} }
#[allow(clippy::too_many_arguments)]
fn queue_custom( fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>, transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
custom_pipeline: Res<CustomPipeline>, custom_pipeline: Res<CustomPipeline>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedPipelines<CustomPipeline>>, mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query< material_meshes: Query<
(Entity, &MeshUniform), (Entity, &MeshUniform, &Handle<Mesh>),
(With<Handle<Mesh>>, With<InstanceMaterialData>), (With<Handle<Mesh>>, With<InstanceMaterialData>),
>, >,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>, mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
@ -112,20 +114,25 @@ fn queue_custom(
.get_id::<DrawCustom>() .get_id::<DrawCustom>()
.unwrap(); .unwrap();
let key = MeshPipelineKey::from_msaa_samples(msaa.samples) let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
| MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key);
for (view, mut transparent_phase) in views.iter_mut() { for (view, mut transparent_phase) in views.iter_mut() {
let view_matrix = view.transform.compute_matrix(); let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2); let view_row_2 = view_matrix.row(2);
for (entity, mesh_uniform) in material_meshes.iter() { for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
transparent_phase.add(Transparent3d { if let Some(mesh) = meshes.get(mesh_handle) {
entity, let key =
pipeline, msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
draw_function: draw_custom, let pipeline = pipelines
distance: view_row_2.dot(mesh_uniform.transform.col(3)), .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout)
}); .unwrap();
transparent_phase.add(Transparent3d {
entity,
pipeline,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});
}
} }
} }
} }
@ -175,11 +182,15 @@ impl FromWorld for CustomPipeline {
} }
} }
impl SpecializedPipeline for CustomPipeline { impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey; type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let mut descriptor = self.mesh_pipeline.specialize(key); &self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader = self.shader.clone();
descriptor.vertex.buffers.push(VertexBufferLayout { descriptor.vertex.buffers.push(VertexBufferLayout {
array_stride: std::mem::size_of::<InstanceData>() as u64, array_stride: std::mem::size_of::<InstanceData>() as u64,
@ -203,7 +214,7 @@ impl SpecializedPipeline for CustomPipeline {
self.mesh_pipeline.mesh_layout.clone(), self.mesh_pipeline.mesh_layout.clone(),
]); ]);
descriptor Ok(descriptor)
} }
} }

View File

@ -4,6 +4,7 @@ use bevy::{
prelude::*, prelude::*,
reflect::TypeUuid, reflect::TypeUuid,
render::{ render::{
mesh::MeshVertexBufferLayout,
render_asset::{PrepareAssetError, RenderAsset}, render_asset::{PrepareAssetError, RenderAsset},
render_resource::{ render_resource::{
std140::{AsStd140, Std140}, std140::{AsStd140, Std140},
@ -95,9 +96,14 @@ impl SpecializedMaterial for CustomMaterial {
fn key(_: &<CustomMaterial as RenderAsset>::PreparedAsset) -> Self::Key {} fn key(_: &<CustomMaterial as RenderAsset>::PreparedAsset) -> Self::Key {}
fn specialize(_: Self::Key, descriptor: &mut RenderPipelineDescriptor) { fn specialize(
descriptor: &mut RenderPipelineDescriptor,
_: Self::Key,
_layout: &MeshVertexBufferLayout,
) -> Result<(), SpecializedMeshPipelineError> {
descriptor.vertex.entry_point = "main".into(); descriptor.vertex.entry_point = "main".into();
descriptor.fragment.as_mut().unwrap().entry_point = "main".into(); descriptor.fragment.as_mut().unwrap().entry_point = "main".into();
Ok(())
} }
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {