bevy/crates/bevy_pbr/src/material.rs
Alice Cecile 206c7ce219 Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.

# Objective

- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45

## Solution

- [x]  Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests

## Changelog

### Added

- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`

### Removed

- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.

### Changed

- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
-  `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. 
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.

## Migration Guide

- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage`  enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
  - Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
  - Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
  - For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with 
  - `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`

## TODO

- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
  - [x] unbreak directional lights
  - [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
  - [x] game menu example shows loading screen and menu simultaneously
  - [x] display settings menu is a blank screen
  - [x] `without_winit` example panics
- [x] ensure all tests pass
  - [x] SubApp doc test fails
  - [x] runs_spawn_local tasks fails
  - [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)

## Points of Difficulty and Controversy

**Reviewers, please give feedback on these and look closely**

1.  Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.

## Future Work (ideally before 0.10)

- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00

644 lines
23 KiB
Rust

use crate::{
AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, PrepassPlugin,
SetMeshBindGroup, SetMeshViewBindGroup,
};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
tonemapping::Tonemapping,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::*,
system::{
lifetimeless::{Read, SRes},
SystemParamItem,
},
};
use bevy_reflect::TypeUuid;
use bevy_render::{
extract_component::ExtractComponentPlugin,
mesh::{Mesh, MeshVertexBufferLayout},
prelude::Image,
render_asset::{PrepareAssetLabel, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
RenderPhase, SetItemPipeline, TrackedRenderPass,
},
render_resource::{
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
renderer::RenderDevice,
texture::FallbackImage,
view::{ExtractedView, Msaa, VisibleEntities},
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_utils::{tracing::error, HashMap, HashSet};
use std::hash::Hash;
use std::marker::PhantomData;
/// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle)
/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level
/// way to render [`Mesh`] entities with custom shader logic.
///
/// Materials 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 Material implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
/// check out the [`AsBindGroup`] documentation.
/// ```
/// # use bevy_pbr::{Material, MaterialMeshBundle};
/// # 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 `Material` have default impls. You only need to implement the
/// // functions that are relevant for your material.
/// impl Material 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(MaterialMeshBundle {
/// 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
/// @group(1) @binding(0)
/// var<uniform> color: vec4<f32>;
/// @group(1) @binding(1)
/// var color_texture: texture_2d<f32>;
/// @group(1) @binding(2)
/// var color_sampler: sampler;
/// ```
pub trait Material: 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 [`ShaderRef::Default`] is returned, the default mesh fragment shader
/// will be used.
#[allow(unused_variables)]
fn fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
#[inline]
fn alpha_mode(&self) -> AlphaMode {
AlphaMode::Opaque
}
#[inline]
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
/// for meshes with equal depth, to avoid z-fighting.
fn depth_bias(&self) -> f32 {
0.0
}
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
/// will be used.
fn prepass_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader
/// will be used.
#[allow(unused_variables)]
fn prepass_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
#[allow(unused_variables)]
#[inline]
fn specialize(
pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
}
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
/// asset type.
pub struct MaterialPlugin<M: Material> {
/// Controls if the prepass is enabled for the Material.
/// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
///
/// When it is enabled, it will automatically add the [`PrepassPlugin`]
/// required to make the prepass work on this Material.
pub prepass_enabled: bool,
pub _marker: PhantomData<M>,
}
impl<M: Material> Default for MaterialPlugin<M> {
fn default() -> Self {
Self {
prepass_enabled: true,
_marker: Default::default(),
}
}
}
impl<M: Material> Plugin for MaterialPlugin<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());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<Transparent3d, DrawMaterial<M>>()
.add_render_command::<Opaque3d, DrawMaterial<M>>()
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
.init_resource::<MaterialPipeline<M>>()
.init_resource::<ExtractedMaterials<M>>()
.init_resource::<RenderMaterials<M>>()
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
.add_system_to_schedule(ExtractSchedule, extract_materials::<M>)
.add_system(
prepare_materials::<M>
.after(PrepareAssetLabel::PreAssetPrepare)
.in_set(RenderSet::Prepare),
)
.add_system(queue_material_meshes::<M>.in_set(RenderSet::Queue));
}
if self.prepass_enabled {
app.add_plugin(PrepassPlugin::<M>::default());
}
}
}
/// A key uniquely identifying a specialized [`MaterialPipeline`].
pub struct MaterialPipelineKey<M: Material> {
pub mesh_key: MeshPipelineKey,
pub bind_group_data: M::Data,
}
impl<M: Material> Eq for MaterialPipelineKey<M> where M::Data: PartialEq {}
impl<M: Material> PartialEq for MaterialPipelineKey<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: Material> Clone for MaterialPipelineKey<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: Material> Hash for MaterialPipelineKey<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);
}
}
/// Render pipeline data for a given [`Material`].
#[derive(Resource)]
pub struct MaterialPipeline<M: Material> {
pub mesh_pipeline: MeshPipeline,
pub material_layout: BindGroupLayout,
pub vertex_shader: Option<Handle<Shader>>,
pub fragment_shader: Option<Handle<Shader>>,
marker: PhantomData<M>,
}
impl<M: Material> Clone for MaterialPipeline<M> {
fn clone(&self) -> Self {
Self {
mesh_pipeline: self.mesh_pipeline.clone(),
material_layout: self.material_layout.clone(),
vertex_shader: self.vertex_shader.clone(),
fragment_shader: self.fragment_shader.clone(),
marker: PhantomData,
}
}
}
impl<M: Material> SpecializedMeshPipeline for MaterialPipeline<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
type Key = MaterialPipelineKey<M>;
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();
}
if let Some(fragment_shader) = &self.fragment_shader {
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
}
// MeshPipeline::specialize's current implementation guarantees that the returned
// specialized descriptor has a populated layout
let descriptor_layout = descriptor.layout.as_mut().unwrap();
descriptor_layout.insert(1, self.material_layout.clone());
M::specialize(self, &mut descriptor, layout, key)?;
Ok(descriptor)
}
}
impl<M: Material> FromWorld for MaterialPipeline<M> {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
let render_device = world.resource::<RenderDevice>();
MaterialPipeline {
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
material_layout: M::bind_group_layout(render_device),
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,
}
}
}
type DrawMaterial<M> = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMaterialBindGroup<M, 1>,
SetMeshBindGroup<2>,
DrawMesh,
);
/// Sets the bind group for a given [`Material`] at the configured `I` index.
pub struct SetMaterialBindGroup<M: Material, const I: usize>(PhantomData<M>);
impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterialBindGroup<M, I> {
type Param = SRes<RenderMaterials<M>>;
type ViewWorldQuery = ();
type ItemWorldQuery = Read<Handle<M>>;
#[inline]
fn render<'w>(
_item: &P,
_view: (),
material_handle: &'_ Handle<M>,
materials: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let material = materials.into_inner().get(material_handle).unwrap();
pass.set_bind_group(I, &material.bind_group, &[]);
RenderCommandResult::Success
}
}
#[allow(clippy::too_many_arguments)]
pub fn queue_material_meshes<M: Material>(
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
material_pipeline: Res<MaterialPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials<M>>,
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
Option<&Tonemapping>,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transparent3d>,
)>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
for (
view,
visible_entities,
tonemapping,
mut opaque_phase,
mut alpha_mask_phase,
mut transparent_phase,
) in &mut views
{
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping {
if !view.hdr {
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
if *deband_dither {
view_key |= MeshPipelineKey::DEBAND_DITHER;
}
}
}
let rangefinder = view.rangefinder3d();
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) {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let mut mesh_key =
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
| view_key;
let alpha_mode = material.properties.alpha_mode;
if let AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add =
alpha_mode
{
// Blend, Premultiplied and Add all share the same pipeline key
// They're made distinct in the PBR shader, via `premultiply_alpha()`
mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA;
} else if let AlphaMode::Multiply = alpha_mode {
mesh_key |= MeshPipelineKey::BLEND_MULTIPLY;
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&material_pipeline,
MaterialPipelineKey {
mesh_key,
bind_group_data: material.key.clone(),
},
&mesh.layout,
);
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
error!("{}", err);
continue;
}
};
let distance = rangefinder.distance(&mesh_uniform.transform)
+ material.properties.depth_bias;
match alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
});
}
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
distance,
});
}
AlphaMode::Blend
| AlphaMode::Premultiplied
| AlphaMode::Add
| AlphaMode::Multiply => {
transparent_phase.add(Transparent3d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
pipeline: pipeline_id,
distance,
});
}
}
}
}
}
}
}
}
/// Common [`Material`] properties, calculated for a specific material instance.
pub struct MaterialProperties {
/// The [`AlphaMode`] of this material.
pub alpha_mode: AlphaMode,
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
/// for meshes with equal depth, to avoid z-fighting.
pub depth_bias: f32,
}
/// Data prepared for a [`Material`] instance.
pub struct PreparedMaterial<T: Material> {
pub bindings: Vec<OwnedBindingResource>,
pub bind_group: BindGroup,
pub key: T::Data,
pub properties: MaterialProperties,
}
#[derive(Resource)]
struct ExtractedMaterials<M: Material> {
extracted: Vec<(Handle<M>, M)>,
removed: Vec<Handle<M>>,
}
impl<M: Material> Default for ExtractedMaterials<M> {
fn default() -> Self {
Self {
extracted: Default::default(),
removed: Default::default(),
}
}
}
/// Stores all prepared representations of [`Material`] assets for as long as they exist.
#[derive(Resource, Deref, DerefMut)]
pub struct RenderMaterials<T: Material>(pub HashMap<Handle<T>, PreparedMaterial<T>>);
impl<T: Material> Default for RenderMaterials<T> {
fn default() -> Self {
Self(Default::default())
}
}
/// This system extracts all created or modified assets of the corresponding [`Material`] type
/// into the "render world".
fn extract_materials<M: Material>(
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(ExtractedMaterials {
extracted: extracted_assets,
removed,
});
}
/// All [`Material`] values of a given type that should be prepared next frame.
pub struct PrepareNextFrameMaterials<M: Material> {
assets: Vec<(Handle<M>, M)>,
}
impl<M: Material> Default for PrepareNextFrameMaterials<M> {
fn default() -> Self {
Self {
assets: Default::default(),
}
}
}
/// This system prepares all assets of the corresponding [`Material`] type
/// which where extracted this frame for the GPU.
fn prepare_materials<M: Material>(
mut prepare_next_frame: Local<PrepareNextFrameMaterials<M>>,
mut extracted_assets: ResMut<ExtractedMaterials<M>>,
mut render_materials: ResMut<RenderMaterials<M>>,
render_device: Res<RenderDevice>,
images: Res<RenderAssets<Image>>,
fallback_image: Res<FallbackImage>,
pipeline: Res<MaterialPipeline<M>>,
) {
let queued_assets = std::mem::take(&mut prepare_next_frame.assets);
for (handle, material) in queued_assets.into_iter() {
match prepare_material(
&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_material(
&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_material<M: Material>(
material: &M,
render_device: &RenderDevice,
images: &RenderAssets<Image>,
fallback_image: &FallbackImage,
pipeline: &MaterialPipeline<M>,
) -> Result<PreparedMaterial<M>, AsBindGroupError> {
let prepared = material.as_bind_group(
&pipeline.material_layout,
render_device,
images,
fallback_image,
)?;
Ok(PreparedMaterial {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
key: prepared.data,
properties: MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
},
})
}