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"
# Shaders
[[example]]
name = "custom_vertex_attribute"
path = "examples/shader/custom_vertex_attribute.rs"
[[example]]
name = "shader_defs"
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()
.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
.read_normals()
.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
.read_tangents()
.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
.read_tex_coords(0)
.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 {
let len = mesh.count_vertices();
let uvs = vec![[0.0, 0.0]; len];
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
// .read_colors(0)
// .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() {

View file

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

View file

@ -15,7 +15,7 @@ use bevy_ecs::{
world::FromWorld,
};
use bevy_render::{
mesh::Mesh,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
@ -24,12 +24,13 @@ use bevy_render::{
},
render_resource::{
BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader,
SpecializedPipeline, SpecializedPipelines,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
renderer::RenderDevice,
view::{ExtractedView, Msaa, VisibleEntities},
RenderApp, RenderStage,
};
use bevy_utils::tracing::error;
use std::hash::Hash;
use std::marker::PhantomData;
@ -72,6 +73,16 @@ pub trait Material: Asset + RenderAsset {
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 {
@ -81,7 +92,13 @@ impl<M: Material> SpecializedMaterial for M {
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
#[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]
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;
/// 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`].
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::<AlphaMask3d, DrawMaterial<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>);
}
}
}
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct MaterialPipelineKey<T> {
mesh_key: MeshPipelineKey,
material_key: T,
}
pub struct MaterialPipeline<M: SpecializedMaterial> {
pub mesh_pipeline: MeshPipeline,
pub material_layout: BindGroupLayout,
@ -202,11 +229,15 @@ pub struct MaterialPipeline<M: SpecializedMaterial> {
marker: PhantomData<M>,
}
impl<M: SpecializedMaterial> SpecializedPipeline for MaterialPipeline<M> {
type Key = (MeshPipelineKey, M::Key);
impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
type Key = MaterialPipelineKey<M::Key>;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut descriptor = self.mesh_pipeline.specialize(key.0);
fn specialize(
&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 {
descriptor.vertex.shader = vertex_shader.clone();
}
@ -220,8 +251,8 @@ impl<M: SpecializedMaterial> SpecializedPipeline for MaterialPipeline<M> {
self.mesh_pipeline.mesh_layout.clone(),
]);
M::specialize(key.1, &mut descriptor);
descriptor
M::specialize(&mut descriptor, key.material_key, layout)?;
Ok(descriptor)
}
}
@ -275,7 +306,7 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
material_pipeline: Res<MaterialPipeline<M>>,
mut pipelines: ResMut<SpecializedPipelines<MaterialPipeline<M>>>,
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
msaa: Res<Msaa>,
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_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 {
if let Ok((material_handle, mesh_handle, mesh_uniform)) =
material_meshes.get(*visible_entity)
{
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 mesh.has_tangents {
mesh_key |= MeshPipelineKey::VERTEX_TANGENTS;
let mut mesh_key =
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 pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material_pipeline,
(mesh_key, specialized_key),
);
let material_key = M::key(material);
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3));
match alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
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
// -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::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr,
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,
});
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material_pipeline,
MaterialPipelineKey {
mesh_key,
material_key,
},
&mesh.layout,
);
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
error!("{}", err);
continue;
}
};
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3));
match alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
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
// -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::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr,
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_render::{
color::Color,
mesh::MeshVertexBufferLayout,
prelude::Shader,
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
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 {
descriptor
.fragment
@ -350,6 +355,7 @@ impl SpecializedMaterial for StandardMaterial {
if let Some(label) = &mut descriptor.label {
*label = format!("pbr_{}", *label).into();
}
Ok(())
}
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::{
camera::{Camera, CameraProjection},
color::Color,
mesh::Mesh,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{
@ -28,7 +28,10 @@ use bevy_render::{
view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::{tracing::warn, HashMap};
use bevy_utils::{
tracing::{error, warn},
HashMap,
};
use std::num::NonZeroU32;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
@ -214,7 +217,6 @@ bitflags::bitflags! {
#[repr(transparent)]
pub struct ShadowPipelineKey: u32 {
const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
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;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let (vertex_array_stride, vertex_attributes) =
if key.contains(ShadowPipelineKey::VERTEX_TANGENTS) {
(
48,
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 (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 {
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let vertex_buffer_layout =
layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?;
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![VertexBufferLayout {
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
buffers: vec![vertex_buffer_layout],
},
fragment: None,
layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]),
@ -344,7 +293,7 @@ impl SpecializedPipeline for ShadowPipeline {
}),
multisample: MultisampleState::default(),
label: Some("shadow_pipeline".into()),
}
})
}
}
@ -1090,7 +1039,7 @@ pub fn queue_shadows(
shadow_pipeline: Res<ShadowPipeline>,
casting_meshes: Query<&Handle<Mesh>, Without<NotShadowCaster>>,
render_meshes: Res<RenderAssets<Mesh>>,
mut pipelines: ResMut<SpecializedPipelines<ShadowPipeline>>,
mut pipelines: ResMut<SpecializedMeshPipelines<ShadowPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
view_lights: Query<&ViewLightEntities>,
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
// so no meshes will be queued
for entity in visible_entities.iter().copied() {
let mut key = ShadowPipelineKey::empty();
if let Ok(mesh_handle) = casting_meshes.get(entity) {
if let Some(mesh) = render_meshes.get(mesh_handle) {
if mesh.has_tangents {
key |= ShadowPipelineKey::VERTEX_TANGENTS;
}
key |= ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
}
let pipeline_id =
pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key);
let key =
ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&shadow_pipeline,
key,
&mesh.layout,
);
shadow_phase.add(Shadow {
draw_function: draw_shadow_mesh,
pipeline: pipeline_id,
entity,
distance: 0.0, // TODO: sort back-to-front
});
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
error!("{}", err);
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_reflect::TypeUuid;
use bevy_render::{
mesh::{GpuBufferInfo, Mesh},
mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
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.
pub struct MeshPipelineKey: u32 {
const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
const TRANSPARENT_MAIN_PASS = (1 << 1);
const TRANSPARENT_MAIN_PASS = (1 << 0);
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;
}
@ -409,71 +408,28 @@ impl MeshPipelineKey {
}
}
impl SpecializedPipeline for MeshPipeline {
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let (vertex_array_stride, vertex_attributes) =
if key.contains(MeshPipelineKey::VERTEX_TANGENTS) {
(
48,
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 (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,
},
],
)
};
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
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 key.contains(MeshPipelineKey::VERTEX_TANGENTS) {
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)?;
let (label, blend, depth_write_enabled);
if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) {
label = "transparent_mesh_pipeline".into();
@ -493,16 +449,12 @@ impl SpecializedPipeline for MeshPipeline {
#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
RenderPipelineDescriptor {
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: vec![VertexBufferLayout {
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(),
@ -546,7 +498,7 @@ impl SpecializedPipeline for MeshPipeline {
alpha_to_coverage_enabled: false,
},
label: Some(label),
}
})
}
}

View file

@ -5,15 +5,20 @@ use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
use bevy_core_pipeline::Opaque3d;
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
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::{
mesh::Mesh,
render_asset::RenderAssets,
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines},
render_resource::{RenderPipelineCache, Shader},
view::{ExtractedView, Msaa},
RenderApp, RenderStage,
};
use bevy_utils::tracing::error;
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
@ -36,7 +41,7 @@ impl Plugin for WireframePlugin {
render_app
.add_render_command::<Opaque3d, DrawWireframes>()
.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_wireframe_config)
.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;
fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor {
let mut descriptor = self.mesh_pipeline.specialize(key);
fn specialize(
&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.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
descriptor.primitive.polygon_mode = PolygonMode::Line;
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
descriptor
Ok(descriptor)
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
fn queue_wireframes(
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
render_meshes: Res<RenderAssets<Mesh>>,
wireframe_config: Res<WireframeConfig>,
wireframe_pipeline: Res<WireframePipeline>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
mut specialized_pipelines: ResMut<SpecializedPipelines<WireframePipeline>>,
mut specialized_pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
msaa: Res<Msaa>,
mut material_meshes: QuerySet<(
QueryState<(Entity, &Handle<Mesh>, &MeshUniform)>,
@ -112,7 +122,7 @@ fn queue_wireframes(
.read()
.get_id::<DrawWireframes>()
.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() {
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
@ -120,15 +130,24 @@ fn queue_wireframes(
let add_render_phase =
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let key =
key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let key = msaa_key
| 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 {
entity,
pipeline: specialized_pipelines.specialize(
&mut pipeline_cache,
&wireframe_pipeline,
key,
),
pipeline: pipeline_id,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});

View file

@ -3,17 +3,19 @@ mod conversions;
use crate::{
primitives::Aabb,
render_asset::{PrepareAssetError, RenderAsset},
render_resource::Buffer,
render_resource::{Buffer, VertexBufferLayout},
renderer::RenderDevice,
};
use bevy_core::cast_slice;
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::*;
use bevy_reflect::TypeUuid;
use bevy_utils::EnumVariantMeta;
use std::{borrow::Cow, collections::BTreeMap};
use bevy_utils::{EnumVariantMeta, Hashed};
use std::{collections::BTreeMap, hash::Hash};
use thiserror::Error;
use wgpu::{
util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat,
util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexAttribute,
VertexFormat, VertexStepMode,
};
pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
@ -25,10 +27,10 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;
pub struct Mesh {
primitive_topology: PrimitiveTopology,
/// `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,
/// which allows easy stable VertexBuffers (i.e. same buffer order)
attributes: BTreeMap<Cow<'static, str>, VertexAttributeValues>,
attributes: BTreeMap<MeshVertexAttributeId, MeshAttributeData>,
indices: Option<Indices>,
}
@ -44,29 +46,39 @@ pub struct Mesh {
/// # use bevy_render::render_resource::PrimitiveTopology;
/// fn create_triangle() -> Mesh {
/// 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
/// }
/// ```
impl Mesh {
/// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`]
pub const ATTRIBUTE_COLOR: &'static str = "Vertex_Color";
/// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_POSITION: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3);
/// The direction the vertex normal is facing in.
/// Use in conjunction with [`Mesh::set_attribute`]
pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal";
/// Use in conjunction with [`Mesh::insert_attribute`]
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
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`]
pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position";
/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`]
pub const ATTRIBUTE_UV_0: &'static str = "Vertex_Uv";
/// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_COLOR: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Uint32);
/// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::set_attribute`]
pub const ATTRIBUTE_JOINT_WEIGHT: &'static str = "Vertex_JointWeight";
/// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`]
pub const ATTRIBUTE_JOINT_INDEX: &'static str = "Vertex_JointIndex";
/// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`]
pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4);
/// 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
/// 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
/// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
pub fn set_attribute(
#[inline]
pub fn insert_attribute(
&mut self,
name: impl Into<Cow<'static, str>>,
attribute: MeshVertexAttribute,
values: impl Into<VertexAttributeValues>,
) {
let values: VertexAttributeValues = values.into();
self.attributes.insert(name.into(), values);
self.attributes.insert(
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`.
pub fn attribute(&self, name: impl Into<Cow<'static, str>>) -> Option<&VertexAttributeValues> {
self.attributes.get(&name.into())
#[inline]
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.
#[inline]
pub fn attribute_mut(
&mut self,
name: impl Into<Cow<'static, str>>,
id: impl Into<MeshVertexAttributeId>,
) -> 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
/// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants
/// that use triangles.
#[inline]
pub fn set_indices(&mut self, indices: Option<Indices>) {
self.indices = indices;
}
/// Retrieves the vertex `indices` of the mesh.
#[inline]
pub fn indices(&self) -> Option<&Indices> {
self.indices.as_ref()
}
/// Retrieves the vertex `indices` of the mesh mutably.
#[inline]
pub fn indices_mut(&mut self) -> Option<&mut Indices> {
self.indices.as_mut()
}
@ -134,27 +167,32 @@ impl Mesh {
})
}
// pub fn get_vertex_buffer_layout(&self) -> VertexBufferLayout {
// let mut attributes = Vec::new();
// let mut accumulated_offset = 0;
// for (attribute_name, attribute_values) in self.attributes.iter() {
// let vertex_format = VertexFormat::from(attribute_values);
// attributes.push(VertexAttribute {
// name: attribute_name.clone(),
// offset: accumulated_offset,
// format: vertex_format,
// shader_location: 0,
// });
// accumulated_offset += vertex_format.get_size();
// }
/// For a given `descriptor` returns a [`VertexBufferLayout`] compatible with this mesh. If this
/// mesh is not compatible with the given `descriptor` (ex: it is missing vertex attributes), [`None`] will
/// be returned.
pub fn get_mesh_vertex_buffer_layout(&self) -> MeshVertexBufferLayout {
let mut attributes = Vec::with_capacity(self.attributes.len());
let mut attribute_ids = Vec::with_capacity(self.attributes.len());
let mut accumulated_offset = 0;
for (index, data) in self.attributes.values().enumerate() {
attribute_ids.push(data.attribute.id);
attributes.push(VertexAttribute {
offset: accumulated_offset,
format: data.attribute.format,
shader_location: index as u32,
});
accumulated_offset += data.attribute.format.get_size();
}
// VertexBufferLayout {
// name: Default::default(),
// stride: accumulated_offset,
// step_mode: InputStepMode::Vertex,
// attributes,
// }
// }
MeshVertexBufferLayout::new(InnerMeshVertexBufferLayout {
layout: VertexBufferLayout {
array_stride: accumulated_offset,
step_mode: VertexStepMode::Vertex,
attributes,
},
attribute_ids,
})
}
/// Counts all vertices of the mesh.
///
@ -162,11 +200,11 @@ impl Mesh {
/// Panics if the attributes have different vertex counts.
pub fn count_vertices(&self) -> usize {
let mut vertex_count: Option<usize> = None;
for (attribute_name, attribute_data) in &self.attributes {
let attribute_len = attribute_data.len();
for (attribute_id, attribute_data) in self.attributes.iter() {
let attribute_len = attribute_data.values.len();
if let Some(previous_vertex_count) = vertex_count {
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);
}
@ -182,8 +220,8 @@ impl Mesh {
/// Panics if the attributes have different vertex counts.
pub fn get_vertex_buffer_data(&self) -> Vec<u8> {
let mut vertex_size = 0;
for attribute_values in self.attributes.values() {
let vertex_format = VertexFormat::from(attribute_values);
for attribute_data in self.attributes.values() {
let vertex_format = attribute_data.attribute.format;
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];
// bundle into interleaved buffers
let mut attribute_offset = 0;
for attribute_values in self.attributes.values() {
let vertex_format = VertexFormat::from(attribute_values);
let attribute_size = vertex_format.get_size() as usize;
let attributes_bytes = attribute_values.get_bytes();
for attribute_data in self.attributes.values() {
let attribute_size = attribute_data.attribute.format.get_size() as usize;
let attributes_bytes = attribute_data.values.get_bytes();
for (vertex_index, attribute_bytes) in
attributes_bytes.chunks_exact(attribute_size).enumerate()
{
@ -232,7 +269,7 @@ impl Mesh {
};
for attributes in self.attributes.values_mut() {
let indices = indices.iter();
match attributes {
match &mut attributes.values {
VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices),
VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices),
VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices),
@ -285,7 +322,7 @@ impl Mesh {
.flat_map(|normal| [normal; 3])
.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
@ -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_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.
///
/// 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.
pub vertex_buffer: Buffer,
pub buffer_info: GpuBufferInfo,
pub has_tangents: bool,
pub primitive_topology: PrimitiveTopology,
pub layout: MeshVertexBufferLayout,
}
/// 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 {
vertex_buffer,
buffer_info,
has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT),
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);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vs);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vns);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vts);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vs);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vns);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vts);
mesh.set_indices(Some(Indices::U32(tris)));
mesh
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use bevy_reflect::Uuid;
use std::{borrow::Cow, ops::Deref, sync::Arc};
use wgpu::{
BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState,
VertexAttribute, VertexStepMode,
VertexAttribute, VertexFormat, VertexStepMode,
};
/// A [`RenderPipeline`] identifier.
@ -87,7 +87,7 @@ impl Deref for ComputePipeline {
}
/// Describes a render (graphics) pipeline.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct RenderPipelineDescriptor {
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification.
pub label: Option<Cow<'static, str>>,
@ -105,7 +105,7 @@ pub struct RenderPipelineDescriptor {
pub fragment: Option<FragmentState>,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VertexState {
/// The compiled shader module for this stage.
pub shader: Handle<Shader>,
@ -118,7 +118,7 @@ pub struct VertexState {
}
/// Describes how the vertex buffer is interpreted.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)]
pub struct VertexBufferLayout {
/// The stride, in bytes, between elements of this buffer.
pub array_stride: BufferAddress,
@ -128,8 +128,36 @@ pub struct VertexBufferLayout {
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.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct FragmentState {
/// The compiled shader module for this stage.
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 std::{hash::Hash, ops::Deref, sync::Arc};
use thiserror::Error;
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout};
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout};
use super::ProcessedShader;
@ -244,6 +244,11 @@ impl RenderPipelineCache {
&self.pipelines[id.0].state
}
#[inline]
pub fn get_descriptor(&self, id: CachedPipelineId) -> &RenderPipelineDescriptor {
&self.pipelines[id.0].descriptor
}
#[inline]
pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> {
if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state {
@ -345,7 +350,7 @@ impl RenderPipelineCache {
.vertex
.buffers
.iter()
.map(|layout| VertexBufferLayout {
.map(|layout| RawVertexBufferLayout {
array_stride: layout.array_stride,
attributes: &layout.attributes,
step_mode: layout.step_mode,

View file

@ -1,6 +1,20 @@
use crate::render_resource::{CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor};
use bevy_utils::HashMap;
use crate::{
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 thiserror::Error;
pub trait SpecializedPipeline {
type Key: Clone + Hash + PartialEq + Eq;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;
}
pub struct SpecializedPipelines<S: SpecializedPipeline> {
cache: HashMap<S::Key, CachedPipelineId>,
@ -28,7 +42,89 @@ impl<S: SpecializedPipeline> SpecializedPipelines<S> {
}
}
pub trait SpecializedPipeline {
type Key: Clone + Hash + PartialEq + Eq;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;
#[derive(Error, Debug)]
pub enum SpecializedMeshPipelineError {
#[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,
};
use bevy_log::error;
use bevy_render::{
mesh::Mesh,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
@ -21,7 +22,7 @@ use bevy_render::{
},
render_resource::{
BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader,
SpecializedPipeline, SpecializedPipelines,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
renderer::RenderDevice,
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities},
@ -69,6 +70,16 @@ pub trait Material2d: Asset + RenderAsset {
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 {
@ -78,7 +89,13 @@ impl<M: Material2d> SpecializedMaterial2d for M {
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
#[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]
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;
/// 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`].
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
@ -172,7 +193,7 @@ impl<M: SpecializedMaterial2d> Plugin for Material2dPlugin<M> {
render_app
.add_render_command::<Transparent2d, DrawMaterial2d<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>);
}
}
@ -186,11 +207,21 @@ pub struct Material2dPipeline<M: SpecializedMaterial2d> {
marker: PhantomData<M>,
}
impl<M: SpecializedMaterial2d> SpecializedPipeline for Material2dPipeline<M> {
type Key = (Mesh2dPipelineKey, M::Key);
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct Material2dKey<T> {
mesh_key: Mesh2dPipelineKey,
material_key: T,
}
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut descriptor = self.mesh2d_pipeline.specialize(key.0);
impl<M: SpecializedMaterial2d> SpecializedMeshPipeline for Material2dPipeline<M> {
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 {
descriptor.vertex.shader = vertex_shader.clone();
}
@ -204,8 +235,8 @@ impl<M: SpecializedMaterial2d> SpecializedPipeline for Material2dPipeline<M> {
self.mesh2d_pipeline.mesh_layout.clone(),
]);
M::specialize(key.1, &mut descriptor);
descriptor
M::specialize(key.material_key, &mut descriptor, layout)?;
Ok(descriptor)
}
}
@ -259,7 +290,7 @@ impl<M: SpecializedMaterial2d, const I: usize> EntityRenderCommand
pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
material2d_pipeline: Res<Material2dPipeline<M>>,
mut pipelines: ResMut<SpecializedPipelines<Material2dPipeline<M>>>,
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
@ -276,42 +307,50 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
.get_id::<DrawMaterial2d<M>>()
.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 {
if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) =
material2d_meshes.get(*visible_entity)
{
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 mesh.has_tangents {
mesh2d_key |= Mesh2dPipelineKey::VERTEX_TANGENTS;
}
mesh2d_key |=
Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
let mesh_key = msaa_key
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
let material_key = M::key(material2d);
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_reflect::TypeUuid;
use bevy_render::{
mesh::{GpuBufferInfo, Mesh},
mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
@ -62,7 +62,7 @@ impl Plugin for Mesh2dRenderPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<Mesh2dPipeline>()
.init_resource::<SpecializedPipelines<Mesh2dPipeline>>()
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
.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_view_bind_groups);
@ -232,7 +232,6 @@ bitflags::bitflags! {
// FIXME: make normals optional?
pub struct Mesh2dPipelineKey: u32 {
const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
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;
}
@ -274,84 +273,37 @@ impl Mesh2dPipelineKey {
}
}
impl SpecializedPipeline for Mesh2dPipeline {
impl SpecializedMeshPipeline for Mesh2dPipeline {
type Key = Mesh2dPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let (vertex_array_stride, vertex_attributes) =
if key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) {
(
48,
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 (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,
},
],
)
};
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
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 key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) {
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
RenderPipelineDescriptor {
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: MESH2D_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: vec![VertexBufferLayout {
array_stride: vertex_array_stride,
step_mode: VertexStepMode::Vertex,
attributes: vertex_attributes,
}],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: MESH2D_SHADER_HANDLE.typed::<Shader>(),
@ -380,7 +332,7 @@ impl SpecializedPipeline for Mesh2dPipeline {
alpha_to_coverage_enabled: false,
},
label: Some("transparent_mesh2d_pipeline".into()),
}
})
}
}

View file

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

View file

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

View file

@ -7,6 +7,8 @@ use bevy::{
},
prelude::*,
render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
@ -66,7 +68,7 @@ impl Plugin for CustomMaterialPlugin {
bind_group: None,
})
.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_custom_material)
.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`
#[allow(clippy::too_many_arguments)]
fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
custom_pipeline: Res<CustomPipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedPipelines<CustomPipeline>>,
mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,
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>)>,
) {
let draw_custom = transparent_3d_draw_functions
@ -106,18 +110,22 @@ fn queue_custom(
let 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() {
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (entity, mesh_uniform) in material_meshes.iter() {
transparent_phase.add(Transparent3d {
entity,
pipeline,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});
for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let pipeline = pipelines
.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)),
});
}
}
}
}
@ -207,11 +215,15 @@ impl FromWorld for CustomPipeline {
}
}
impl SpecializedPipeline for CustomPipeline {
impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut descriptor = self.mesh_pipeline.specialize(key);
fn specialize(
&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.fragment.as_mut().unwrap().shader = self.shader.clone();
descriptor.layout = Some(vec![
@ -219,7 +231,7 @@ impl SpecializedPipeline for CustomPipeline {
self.mesh_pipeline.mesh_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::*,
render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{
RenderPipelineCache, RenderPipelineDescriptor, SpecializedPipeline,
SpecializedPipelines,
RenderPipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline,
SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
view::ExtractedView,
RenderApp, RenderStage,
@ -26,7 +27,7 @@ impl Plugin for IsRedPlugin {
app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawIsRed>()
.init_resource::<IsRedPipeline>()
.init_resource::<SpecializedPipelines<IsRedPipeline>>()
.init_resource::<SpecializedMeshPipelines<IsRedPipeline>>()
.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);
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();
if is_red.0 {
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_defs = shader_defs.clone();
let fragment = descriptor.fragment.as_mut().unwrap();
@ -116,7 +121,7 @@ impl SpecializedPipeline for IsRedPipeline {
self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
]);
descriptor
Ok(descriptor)
}
}
@ -133,7 +138,7 @@ fn queue_custom(
render_meshes: Res<RenderAssets<Mesh>>,
custom_pipeline: Res<IsRedPipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedPipelines<IsRedPipeline>>,
mut pipelines: ResMut<SpecializedMeshPipelines<IsRedPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
material_meshes: Query<(Entity, &Handle<Mesh>, &MeshUniform, &IsRed)>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
@ -142,15 +147,22 @@ fn queue_custom(
.read()
.get_id::<DrawIsRed>()
.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() {
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline =
pipelines.specialize(&mut pipeline_cache, &custom_pipeline, (*is_red, key));
let key =
msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline = pipelines
.specialize(
&mut pipeline_cache,
&custom_pipeline,
(*is_red, key),
&mesh.layout,
)
.unwrap();
transparent_phase.add(Transparent3d {
entity,
pipeline,

View file

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

View file

@ -4,6 +4,7 @@ use bevy::{
prelude::*,
reflect::TypeUuid,
render::{
mesh::MeshVertexBufferLayout,
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{
std140::{AsStd140, Std140},
@ -95,9 +96,14 @@ impl SpecializedMaterial for CustomMaterial {
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.fragment.as_mut().unwrap().entry_point = "main".into();
Ok(())
}
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {