Support AsBindGroup for 2d materials as well (#5312)

Port changes made to Material in #5053 to Material2d as well.

This is more or less an exact copy of the implementation in bevy_pbr; I
simply pretended the API existed, then copied stuff over until it
started building and the shapes example was working again.

# Objective

The changes in #5053 makes it possible to add custom materials with a lot less boiler plate. However, the implementation isn't shared with Material 2d as it's a kind of fork of the bevy_pbr version. It should be possible to use AsBindGroup on the 2d version as well.

## Solution

This makes the same kind of changes in Material2d in bevy_sprite.

This makes the following work:

```rust
//! Draws a circular purple bevy in the middle of the screen using a custom shader

use bevy::{
    prelude::*,
    reflect::TypeUuid,
    render::render_resource::{AsBindGroup, ShaderRef},
    sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle},
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugin(Material2dPlugin::<CustomMaterial>::default())
        .add_startup_system(setup)
        .run();
}

/// set up a simple 2D scene
fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<CustomMaterial>>,
    asset_server: Res<AssetServer>,
) {
    commands.spawn_bundle(MaterialMesh2dBundle {
        mesh: meshes.add(shape::Circle::new(50.).into()).into(),
        material: materials.add(CustomMaterial {
            color: Color::PURPLE,
            color_texture: Some(asset_server.load("branding/icon.png")),
        }),
        transform: Transform::from_translation(Vec3::new(-100., 0., 0.)),
        ..default()
    });

    commands.spawn_bundle(Camera2dBundle::default());
}

/// The Material2d trait is very configurable, but comes with sensible defaults for all methods.
/// You only need to implement functions for features that need non-default behavior. See the Material api docs for details!
impl Material2d for CustomMaterial {
    fn fragment_shader() -> ShaderRef {
        "shaders/custom_material.wgsl".into()
    }
}

// This is the struct that will be passed to your shader
#[derive(AsBindGroup, TypeUuid, Debug, Clone)]
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
pub struct CustomMaterial {
    #[uniform(0)]
    color: Color,
    #[texture(1)]
    #[sampler(2)]
    color_texture: Option<Handle<Image>>,
}
```
This commit is contained in:
Johan Klokkhammer Helsing 2022-07-16 00:20:04 +00:00
parent e0a8087408
commit 8810a73e87
3 changed files with 340 additions and 391 deletions

View file

@ -1,18 +1,12 @@
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, AssetServer, Assets, Handle, HandleUntyped};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
use bevy_math::Vec4;
use bevy_reflect::TypeUuid;
use bevy_render::{
color::Color,
prelude::Shader,
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::*,
renderer::RenderDevice,
texture::Image,
color::Color, prelude::Shader, render_asset::RenderAssets, render_resource::*, texture::Image,
};
use crate::{Material2d, Material2dPipeline, Material2dPlugin, MaterialMesh2dBundle};
use crate::{Material2d, Material2dPlugin, MaterialMesh2dBundle};
pub const COLOR_MATERIAL_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3253086872234592509);
@ -44,10 +38,13 @@ impl Plugin for ColorMaterialPlugin {
}
/// A [2d material](Material2d) that renders [2d meshes](crate::Mesh2dHandle) with a texture tinted by a uniform color
#[derive(Debug, Clone, TypeUuid)]
#[derive(AsBindGroup, Debug, Clone, TypeUuid)]
#[uuid = "e228a544-e3ca-4e1e-bb9d-4d8bc1ad8c19"]
#[uniform(0, ColorMaterialUniform)]
pub struct ColorMaterial {
pub color: Color,
#[texture(1)]
#[sampler(2)]
pub texture: Option<Handle<Image>>,
}
@ -90,142 +87,28 @@ bitflags::bitflags! {
/// The GPU representation of the uniform data of a [`ColorMaterial`].
#[derive(Clone, Default, ShaderType)]
pub struct ColorMaterialUniformData {
pub struct ColorMaterialUniform {
pub color: Vec4,
pub flags: u32,
}
/// The GPU representation of a [`ColorMaterial`].
#[derive(Debug, Clone)]
pub struct GpuColorMaterial {
/// A buffer containing the [`ColorMaterialUniformData`] of the material.
pub buffer: Buffer,
/// The bind group specifying how the [`ColorMaterialUniformData`] and
/// the texture of the material are bound.
pub bind_group: BindGroup,
pub flags: ColorMaterialFlags,
pub texture: Option<Handle<Image>>,
}
impl RenderAsset for ColorMaterial {
type ExtractedAsset = ColorMaterial;
type PreparedAsset = GpuColorMaterial;
type Param = (
SRes<RenderDevice>,
SRes<Material2dPipeline<ColorMaterial>>,
SRes<RenderAssets<Image>>,
);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
fn prepare_asset(
material: Self::ExtractedAsset,
(render_device, color_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let (texture_view, sampler) = if let Some(result) = color_pipeline
.mesh2d_pipeline
.get_image_texture(gpu_images, &material.texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
fn as_bind_group_shader_type(&self, _images: &RenderAssets<Image>) -> ColorMaterialUniform {
let mut flags = ColorMaterialFlags::NONE;
if material.texture.is_some() {
if self.texture.is_some() {
flags |= ColorMaterialFlags::TEXTURE;
}
let value = ColorMaterialUniformData {
color: material.color.as_linear_rgba_f32().into(),
ColorMaterialUniform {
color: self.color.as_linear_rgba_f32().into(),
flags: flags.bits(),
};
let byte_buffer = [0u8; ColorMaterialUniformData::SHADER_SIZE.get() as usize];
let mut buffer = encase::UniformBuffer::new(byte_buffer);
buffer.write(&value).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("color_material_uniform_buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: buffer.as_ref(),
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(texture_view),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Sampler(sampler),
},
],
label: Some("color_material_bind_group"),
layout: &color_pipeline.material2d_layout,
});
Ok(GpuColorMaterial {
buffer,
bind_group,
flags,
texture: material.texture,
})
}
}
}
impl Material2d for ColorMaterial {
fn fragment_shader(_asset_server: &AssetServer) -> Option<Handle<Shader>> {
Some(COLOR_MATERIAL_SHADER_HANDLE.typed())
}
#[inline]
fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
&render_asset.bind_group
}
fn bind_group_layout(
render_device: &RenderDevice,
) -> bevy_render::render_resource::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: Some(ColorMaterialUniformData::min_size()),
},
count: None,
},
// Texture
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Texture Sampler
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
label: Some("color_material_layout"),
})
fn fragment_shader() -> ShaderRef {
COLOR_MATERIAL_SHADER_HANDLE.typed().into()
}
}

View file

@ -1,34 +1,40 @@
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Asset, AssetServer, Handle};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
use bevy_core_pipeline::core_2d::Transparent2d;
use bevy_ecs::{
entity::Entity,
event::EventReader,
prelude::{Bundle, World},
schedule::ParallelSystemDescriptorCoercion,
system::{
lifetimeless::{Read, SQuery, SRes},
Query, Res, ResMut, SystemParamItem,
Commands, Local, Query, Res, ResMut, SystemParamItem,
},
world::FromWorld,
};
use bevy_log::error;
use bevy_reflect::TypeUuid;
use bevy_render::{
extract_component::ExtractComponentPlugin,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
prelude::Image,
render_asset::{PrepareAssetLabel, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
},
render_resource::{
BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
renderer::RenderDevice,
texture::FallbackImage,
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities},
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::FloatOrd;
use bevy_utils::{FloatOrd, HashMap, HashSet};
use std::hash::Hash;
use std::marker::PhantomData;
@ -39,36 +45,83 @@ use crate::{
/// Materials are used alongside [`Material2dPlugin`] and [`MaterialMesh2dBundle`]
/// to spawn entities that are rendered with a specific [`Material2d`] type. They serve as an easy to use high level
/// way to render [`Mesh2dHandle`] entities with custom shader logic. For materials that can specialize their [`RenderPipelineDescriptor`]
/// based on specific material values, see [`SpecializedMaterial2d`]. [`Material2d`] automatically implements [`SpecializedMaterial2d`]
/// and can be used anywhere that type is used (such as [`Material2dPlugin`]).
pub trait Material2d: Asset + RenderAsset {
/// Returns this material's [`BindGroup`]. This should match the layout returned by [`Material2d::bind_group_layout`].
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
/// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`Material2d::bind_group`].
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
/// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used.
/// Defaults to [`None`].
#[allow(unused_variables)]
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
None
/// to spawn entities that are rendered with a specific [`Material2d`] type. They serve as an easy to use high level
/// way to render [`Mesh2dHandle`] entities with custom shader logic.
///
/// Material2ds must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
///
/// Materials must also implement [`TypeUuid`] so they can be treated as an [`Asset`](bevy_asset::Asset).
///
/// # Example
///
/// Here is a simple Material2d implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
/// check out the [`AsBindGroup`] documentation.
/// ```
/// # use bevy_sprite::{Material2d, MaterialMesh2dBundle};
/// # use bevy_ecs::prelude::*;
/// # use bevy_reflect::TypeUuid;
/// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color};
/// # use bevy_asset::{Handle, AssetServer, Assets};
///
/// #[derive(AsBindGroup, TypeUuid, Debug, Clone)]
/// #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
/// pub struct CustomMaterial {
/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
/// #[uniform(0)]
/// color: Color,
/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
/// // add the sampler attribute with a different binding index.
/// #[texture(1)]
/// #[sampler(2)]
/// color_texture: Handle<Image>,
/// }
///
/// // All functions on `Material2d` have default impls. You only need to implement the
/// // functions that are relevant for your material.
/// impl Material2d for CustomMaterial {
/// fn fragment_shader() -> ShaderRef {
/// "shaders/custom_material.wgsl".into()
/// }
/// }
///
/// // Spawn an entity using `CustomMaterial`.
/// fn setup(mut commands: Commands, mut materials: ResMut<Assets<CustomMaterial>>, asset_server: Res<AssetServer>) {
/// commands.spawn_bundle(MaterialMesh2dBundle {
/// material: materials.add(CustomMaterial {
/// color: Color::RED,
/// color_texture: asset_server.load("some_image.png"),
/// }),
/// ..Default::default()
/// });
/// }
/// ```
/// In WGSL shaders, the material's binding would look like this:
///
/// ```wgsl
/// struct CustomMaterial {
/// color: vec4<f32>;
/// };
///
/// [[group(1), binding(0)]]
/// var<uniform> material: CustomMaterial;
/// [[group(1), binding(1)]]
/// var color_texture: texture_2d<f32>;
/// [[group(1), binding(2)]]
/// var color_sampler: sampler;
/// ```
pub trait Material2d: AsBindGroup + Send + Sync + Clone + TypeUuid + Sized + 'static {
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader
/// will be used.
fn vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used.
/// Defaults to [`None`].
#[allow(unused_variables)]
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
None
}
/// The dynamic uniform indices to set for the given `material`'s [`BindGroup`].
/// Defaults to an empty array / no dynamic uniform indices.
#[allow(unused_variables)]
#[inline]
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
&[]
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader
/// will be used.
fn fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Customizes the default [`RenderPipelineDescriptor`].
@ -77,136 +130,48 @@ pub trait Material2d: Asset + RenderAsset {
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
key: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
}
impl<M: Material2d> SpecializedMaterial2d for M {
type Key = ();
#[inline]
fn key(
_render_device: &RenderDevice,
_material: &<Self as RenderAsset>::PreparedAsset,
) -> Self::Key {
}
#[inline]
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 {
<M as Material2d>::bind_group(material)
}
#[inline]
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
<M as Material2d>::bind_group_layout(render_device)
}
#[inline]
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
<M as Material2d>::vertex_shader(asset_server)
}
#[inline]
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
<M as Material2d>::fragment_shader(asset_server)
}
#[allow(unused_variables)]
#[inline]
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
<M as Material2d>::dynamic_uniform_indices(material)
}
}
/// Materials are used alongside [`Material2dPlugin`] and [`MaterialMesh2dBundle`](crate::MaterialMesh2dBundle)
/// to spawn entities that are rendered with a specific [`SpecializedMaterial2d`] type. They serve as an easy to use high level
/// way to render [`Mesh2dHandle`] entities with custom shader logic. [`SpecializedMaterial2d`s](SpecializedMaterial2d) use their [`SpecializedMaterial2d::Key`]
/// to customize their [`RenderPipelineDescriptor`] based on specific material values. The slightly simpler [`Material2d`] trait
/// should be used for materials that do not need specialization. [`Material2d`] types automatically implement [`SpecializedMaterial2d`].
pub trait SpecializedMaterial2d: Asset + RenderAsset {
/// The key used to specialize this material's [`RenderPipelineDescriptor`].
type Key: PartialEq + Eq + Hash + Clone + Send + Sync;
/// Extract the [`SpecializedMaterial2d::Key`] for the "prepared" version of this material. This key will be
/// passed in to the [`SpecializedMaterial2d::specialize`] function when compiling the [`RenderPipeline`](bevy_render::render_resource::RenderPipeline)
/// for a given entity's material.
fn key(
render_device: &RenderDevice,
material: &<Self as RenderAsset>::PreparedAsset,
) -> Self::Key;
/// Specializes the given `descriptor` according to the given `key`.
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;
/// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`SpecializedMaterial2d::bind_group`].
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
/// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used.
/// Defaults to [`None`].
#[allow(unused_variables)]
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
None
}
/// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used.
/// Defaults to [`None`].
#[allow(unused_variables)]
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
None
}
/// The dynamic uniform indices to set for the given `material`'s [`BindGroup`].
/// Defaults to an empty array / no dynamic uniform indices.
#[allow(unused_variables)]
#[inline]
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
&[]
}
}
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`SpecializedMaterial2d`]
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material2d`]
/// asset type (which includes [`Material2d`] types).
pub struct Material2dPlugin<M: SpecializedMaterial2d>(PhantomData<M>);
pub struct Material2dPlugin<M: Material2d>(PhantomData<M>);
impl<M: SpecializedMaterial2d> Default for Material2dPlugin<M> {
impl<M: Material2d> Default for Material2dPlugin<M> {
fn default() -> Self {
Self(Default::default())
}
}
impl<M: SpecializedMaterial2d> Plugin for Material2dPlugin<M> {
impl<M: Material2d> Plugin for Material2dPlugin<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
fn build(&self, app: &mut App) {
app.add_asset::<M>()
.add_plugin(ExtractComponentPlugin::<Handle<M>>::extract_visible())
.add_plugin(RenderAssetPlugin::<M>::default());
.add_plugin(ExtractComponentPlugin::<Handle<M>>::extract_visible());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
.init_resource::<Material2dPipeline<M>>()
.init_resource::<ExtractedMaterials2d<M>>()
.init_resource::<RenderMaterials2d<M>>()
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
.add_system_to_stage(RenderStage::Extract, extract_materials_2d::<M>)
.add_system_to_stage(
RenderStage::Prepare,
prepare_materials_2d::<M>.after(PrepareAssetLabel::PreAssetPrepare),
)
.add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::<M>);
}
}
}
pub struct Material2dPipeline<M: SpecializedMaterial2d> {
/// Render pipeline data for a given [`Material2d`]
pub struct Material2dPipeline<M: Material2d> {
pub mesh2d_pipeline: Mesh2dPipeline,
pub material2d_layout: BindGroupLayout,
pub vertex_shader: Option<Handle<Shader>>,
@ -214,14 +179,49 @@ pub struct Material2dPipeline<M: SpecializedMaterial2d> {
marker: PhantomData<M>,
}
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct Material2dKey<T> {
pub struct Material2dKey<M: Material2d> {
pub mesh_key: Mesh2dPipelineKey,
pub material_key: T,
pub bind_group_data: M::Data,
}
impl<M: SpecializedMaterial2d> SpecializedMeshPipeline for Material2dPipeline<M> {
type Key = Material2dKey<M::Key>;
impl<M: Material2d> Eq for Material2dKey<M> where M::Data: PartialEq {}
impl<M: Material2d> PartialEq for Material2dKey<M>
where
M::Data: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data
}
}
impl<M: Material2d> Clone for Material2dKey<M>
where
M::Data: Clone,
{
fn clone(&self) -> Self {
Self {
mesh_key: self.mesh_key,
bind_group_data: self.bind_group_data.clone(),
}
}
}
impl<M: Material2d> Hash for Material2dKey<M>
where
M::Data: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.mesh_key.hash(state);
self.bind_group_data.hash(state);
}
}
impl<M: Material2d> SpecializedMeshPipeline for Material2dPipeline<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
type Key = Material2dKey<M>;
fn specialize(
&self,
@ -242,12 +242,12 @@ impl<M: SpecializedMaterial2d> SpecializedMeshPipeline for Material2dPipeline<M>
self.mesh2d_pipeline.mesh_layout.clone(),
]);
M::specialize(key.material_key, &mut descriptor, layout)?;
M::specialize(&mut descriptor, layout, key)?;
Ok(descriptor)
}
}
impl<M: SpecializedMaterial2d> FromWorld for Material2dPipeline<M> {
impl<M: Material2d> FromWorld for Material2dPipeline<M> {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
let render_device = world.resource::<RenderDevice>();
@ -256,8 +256,16 @@ impl<M: SpecializedMaterial2d> FromWorld for Material2dPipeline<M> {
Material2dPipeline {
mesh2d_pipeline: world.resource::<Mesh2dPipeline>().clone(),
material2d_layout,
vertex_shader: M::vertex_shader(asset_server),
fragment_shader: M::fragment_shader(asset_server),
vertex_shader: match M::vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
fragment_shader: match M::fragment_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
marker: PhantomData,
}
}
@ -271,11 +279,9 @@ type DrawMaterial2d<M> = (
DrawMesh2d,
);
pub struct SetMaterial2dBindGroup<M: SpecializedMaterial2d, const I: usize>(PhantomData<M>);
impl<M: SpecializedMaterial2d, const I: usize> EntityRenderCommand
for SetMaterial2dBindGroup<M, I>
{
type Param = (SRes<RenderAssets<M>>, SQuery<Read<Handle<M>>>);
pub struct SetMaterial2dBindGroup<M: Material2d, const I: usize>(PhantomData<M>);
impl<M: Material2d, const I: usize> EntityRenderCommand for SetMaterial2dBindGroup<M, I> {
type Param = (SRes<RenderMaterials2d<M>>, SQuery<Read<Handle<M>>>);
fn render<'w>(
_view: Entity,
item: Entity,
@ -284,32 +290,28 @@ impl<M: SpecializedMaterial2d, const I: usize> EntityRenderCommand
) -> RenderCommandResult {
let material2d_handle = query.get(item).unwrap();
let material2d = materials.into_inner().get(material2d_handle).unwrap();
pass.set_bind_group(
I,
M::bind_group(material2d),
M::dynamic_uniform_indices(material2d),
);
pass.set_bind_group(I, &material2d.bind_group, &[]);
RenderCommandResult::Success
}
}
#[allow(clippy::too_many_arguments)]
pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
pub fn queue_material2d_meshes<M: Material2d>(
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
material2d_pipeline: Res<Material2dPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
mut pipeline_cache: ResMut<PipelineCache>,
render_device: Res<RenderDevice>,
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderAssets<M>>,
render_materials: Res<RenderMaterials2d<M>>,
material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>,
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>,
) {
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
if material2d_meshes.is_empty() {
return;
}
let render_device = render_device.into_inner();
for (visible_entities, mut transparent_phase) in &mut views {
let draw_transparent_pbr = transparent_draw_functions
.read()
@ -327,13 +329,12 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
let mesh_key = msaa_key
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
let material_key = M::key(render_device, material2d);
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&material2d_pipeline,
Material2dKey {
mesh_key,
material_key,
bind_group_data: material2d.key.clone(),
},
&mesh.layout,
);
@ -366,9 +367,151 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
}
}
/// A component bundle for entities with a [`Mesh2dHandle`] and a [`SpecializedMaterial2d`].
/// Data prepared for a [`Material2d`] instance.
pub struct PreparedMaterial2d<T: Material2d> {
pub bindings: Vec<OwnedBindingResource>,
pub bind_group: BindGroup,
pub key: T::Data,
}
struct ExtractedMaterials2d<M: Material2d> {
extracted: Vec<(Handle<M>, M)>,
removed: Vec<Handle<M>>,
}
impl<M: Material2d> Default for ExtractedMaterials2d<M> {
fn default() -> Self {
Self {
extracted: Default::default(),
removed: Default::default(),
}
}
}
/// Stores all prepared representations of [`Material2d`] assets for as long as they exist.
pub type RenderMaterials2d<T> = HashMap<Handle<T>, PreparedMaterial2d<T>>;
/// This system extracts all created or modified assets of the corresponding [`Material2d`] type
/// into the "render world".
fn extract_materials_2d<M: Material2d>(
mut commands: Commands,
mut events: Extract<EventReader<AssetEvent<M>>>,
assets: Extract<Res<Assets<M>>>,
) {
let mut changed_assets = HashSet::default();
let mut removed = Vec::new();
for event in events.iter() {
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
changed_assets.insert(handle.clone_weak());
}
AssetEvent::Removed { handle } => {
changed_assets.remove(handle);
removed.push(handle.clone_weak());
}
}
}
let mut extracted_assets = Vec::new();
for handle in changed_assets.drain() {
if let Some(asset) = assets.get(&handle) {
extracted_assets.push((handle, asset.clone()));
}
}
commands.insert_resource(ExtractedMaterials2d {
extracted: extracted_assets,
removed,
});
}
/// All [`Material2d`] values of a given type that should be prepared next frame.
pub struct PrepareNextFrameMaterials<M: Material2d> {
assets: Vec<(Handle<M>, M)>,
}
impl<M: Material2d> Default for PrepareNextFrameMaterials<M> {
fn default() -> Self {
Self {
assets: Default::default(),
}
}
}
/// This system prepares all assets of the corresponding [`Material2d`] type
/// which where extracted this frame for the GPU.
fn prepare_materials_2d<M: Material2d>(
mut prepare_next_frame: Local<PrepareNextFrameMaterials<M>>,
mut extracted_assets: ResMut<ExtractedMaterials2d<M>>,
mut render_materials: ResMut<RenderMaterials2d<M>>,
render_device: Res<RenderDevice>,
images: Res<RenderAssets<Image>>,
fallback_image: Res<FallbackImage>,
pipeline: Res<Material2dPipeline<M>>,
) {
let mut queued_assets = std::mem::take(&mut prepare_next_frame.assets);
for (handle, material) in queued_assets.drain(..) {
match prepare_material2d(
&material,
&render_device,
&images,
&fallback_image,
&pipeline,
) {
Ok(prepared_asset) => {
render_materials.insert(handle, prepared_asset);
}
Err(AsBindGroupError::RetryNextUpdate) => {
prepare_next_frame.assets.push((handle, material));
}
}
}
for removed in std::mem::take(&mut extracted_assets.removed) {
render_materials.remove(&removed);
}
for (handle, material) in std::mem::take(&mut extracted_assets.extracted) {
match prepare_material2d(
&material,
&render_device,
&images,
&fallback_image,
&pipeline,
) {
Ok(prepared_asset) => {
render_materials.insert(handle, prepared_asset);
}
Err(AsBindGroupError::RetryNextUpdate) => {
prepare_next_frame.assets.push((handle, material));
}
}
}
}
fn prepare_material2d<M: Material2d>(
material: &M,
render_device: &RenderDevice,
images: &RenderAssets<Image>,
fallback_image: &FallbackImage,
pipeline: &Material2dPipeline<M>,
) -> Result<PreparedMaterial2d<M>, AsBindGroupError> {
let prepared = material.as_bind_group(
&pipeline.material2d_layout,
render_device,
images,
fallback_image,
)?;
Ok(PreparedMaterial2d {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
key: prepared.data,
})
}
/// A component bundle for entities with a [`Mesh2dHandle`] and a [`Material2d`].
#[derive(Bundle, Clone)]
pub struct MaterialMesh2dBundle<M: SpecializedMaterial2d> {
pub struct MaterialMesh2dBundle<M: Material2d> {
pub mesh: Mesh2dHandle,
pub material: Handle<M>,
pub transform: Transform,
@ -379,7 +522,7 @@ pub struct MaterialMesh2dBundle<M: SpecializedMaterial2d> {
pub computed_visibility: ComputedVisibility,
}
impl<M: SpecializedMaterial2d> Default for MaterialMesh2dBundle<M> {
impl<M: Material2d> Default for MaterialMesh2dBundle<M> {
fn default() -> Self {
Self {
mesh: Default::default(),

View file

@ -5,22 +5,17 @@
use bevy::{
core_pipeline::clear_color::ClearColorConfig,
ecs::system::{lifetimeless::SRes, SystemParamItem},
prelude::*,
reflect::TypeUuid,
render::{
camera::{Camera, RenderTarget},
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
Extent3d, SamplerBindingType, ShaderStages, TextureDescriptor, TextureDimension,
TextureFormat, TextureSampleType, TextureUsages, TextureViewDimension,
AsBindGroup, Extent3d, ShaderRef, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages,
},
renderer::RenderDevice,
view::RenderLayers,
},
sprite::{Material2d, Material2dPipeline, Material2dPlugin, MaterialMesh2dBundle},
sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle},
};
fn main() {
@ -44,7 +39,10 @@ fn setup(
mut post_processing_materials: ResMut<Assets<PostProcessingMaterial>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
asset_server: Res<AssetServer>,
) {
asset_server.watch_for_changes().unwrap();
let window = windows.get_primary_mut().unwrap();
let size = Extent3d {
width: window.physical_width(),
@ -166,92 +164,17 @@ fn main_camera_cube_rotator_system(
// Region below declares of the custom material handling post processing effect
/// Our custom post processing material
#[derive(TypeUuid, Clone)]
#[derive(AsBindGroup, TypeUuid, Clone)]
#[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"]
struct PostProcessingMaterial {
/// In this example, this image will be the result of the main camera.
#[texture(0)]
#[sampler(1)]
source_image: Handle<Image>,
}
struct PostProcessingMaterialGPU {
bind_group: BindGroup,
}
impl Material2d for PostProcessingMaterial {
fn bind_group(material: &PostProcessingMaterialGPU) -> &BindGroup {
&material.bind_group
}
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
view_dimension: TextureViewDimension::D2,
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
})
}
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
asset_server.watch_for_changes().unwrap();
Some(asset_server.load("shaders/custom_material_chromatic_aberration.wgsl"))
}
}
impl RenderAsset for PostProcessingMaterial {
type ExtractedAsset = PostProcessingMaterial;
type PreparedAsset = PostProcessingMaterialGPU;
type Param = (
SRes<RenderDevice>,
SRes<Material2dPipeline<PostProcessingMaterial>>,
SRes<RenderAssets<Image>>,
);
fn prepare_asset(
extracted_asset: PostProcessingMaterial,
(render_device, pipeline, images): &mut SystemParamItem<Self::Param>,
) -> Result<PostProcessingMaterialGPU, PrepareAssetError<PostProcessingMaterial>> {
let (view, sampler) = if let Some(result) = pipeline
.mesh2d_pipeline
.get_image_texture(images, &Some(extracted_asset.source_image.clone()))
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(extracted_asset));
};
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.material2d_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(sampler),
},
],
});
Ok(PostProcessingMaterialGPU { bind_group })
}
fn extract_asset(&self) -> PostProcessingMaterial {
self.clone()
fn fragment_shader() -> ShaderRef {
"shaders/custom_material_chromatic_aberration.wgsl".into()
}
}