mirror of
https://github.com/bevyengine/bevy
synced 2025-01-23 10:25:13 +00:00
206c7ce219
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.
576 lines
20 KiB
Rust
576 lines
20 KiB
Rust
use bevy_app::{App, Plugin};
|
|
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
|
|
use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
prelude::*,
|
|
query::ROQueryItem,
|
|
system::{
|
|
lifetimeless::{Read, SRes},
|
|
SystemParamItem,
|
|
},
|
|
};
|
|
use bevy_log::error;
|
|
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::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
|
|
Extract, ExtractSchedule, RenderApp, RenderSet,
|
|
};
|
|
use bevy_transform::components::{GlobalTransform, Transform};
|
|
use bevy_utils::{FloatOrd, HashMap, HashSet};
|
|
use std::hash::Hash;
|
|
use std::marker::PhantomData;
|
|
|
|
use crate::{
|
|
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, SetMesh2dBindGroup,
|
|
SetMesh2dViewBindGroup,
|
|
};
|
|
|
|
/// 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.
|
|
///
|
|
/// 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(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 [`ShaderRef::Default`] is returned, the default mesh fragment shader
|
|
/// will be used.
|
|
fn fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Customizes the default [`RenderPipelineDescriptor`].
|
|
#[allow(unused_variables)]
|
|
#[inline]
|
|
fn specialize(
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
layout: &MeshVertexBufferLayout,
|
|
key: Material2dKey<Self>,
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// 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: Material2d>(PhantomData<M>);
|
|
|
|
impl<M: Material2d> Default for Material2dPlugin<M> {
|
|
fn default() -> Self {
|
|
Self(Default::default())
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
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_schedule(ExtractSchedule, extract_materials_2d::<M>)
|
|
.add_system(
|
|
prepare_materials_2d::<M>
|
|
.after(PrepareAssetLabel::PreAssetPrepare)
|
|
.in_set(RenderSet::Prepare),
|
|
)
|
|
.add_system(queue_material2d_meshes::<M>.in_set(RenderSet::Queue));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Render pipeline data for a given [`Material2d`]
|
|
#[derive(Resource)]
|
|
pub struct Material2dPipeline<M: Material2d> {
|
|
pub mesh2d_pipeline: Mesh2dPipeline,
|
|
pub material2d_layout: BindGroupLayout,
|
|
pub vertex_shader: Option<Handle<Shader>>,
|
|
pub fragment_shader: Option<Handle<Shader>>,
|
|
marker: PhantomData<M>,
|
|
}
|
|
|
|
pub struct Material2dKey<M: Material2d> {
|
|
pub mesh_key: Mesh2dPipelineKey,
|
|
pub bind_group_data: M::Data,
|
|
}
|
|
|
|
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> Clone for Material2dPipeline<M> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
mesh2d_pipeline: self.mesh2d_pipeline.clone(),
|
|
material2d_layout: self.material2d_layout.clone(),
|
|
vertex_shader: self.vertex_shader.clone(),
|
|
fragment_shader: self.fragment_shader.clone(),
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: Material2d> SpecializedMeshPipeline for Material2dPipeline<M>
|
|
where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
type Key = Material2dKey<M>;
|
|
|
|
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();
|
|
}
|
|
|
|
if let Some(fragment_shader) = &self.fragment_shader {
|
|
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
|
|
}
|
|
descriptor.layout = Some(vec![
|
|
self.mesh2d_pipeline.view_layout.clone(),
|
|
self.material2d_layout.clone(),
|
|
self.mesh2d_pipeline.mesh_layout.clone(),
|
|
]);
|
|
|
|
M::specialize(&mut descriptor, layout, key)?;
|
|
Ok(descriptor)
|
|
}
|
|
}
|
|
|
|
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>();
|
|
let material2d_layout = M::bind_group_layout(render_device);
|
|
|
|
Material2dPipeline {
|
|
mesh2d_pipeline: world.resource::<Mesh2dPipeline>().clone(),
|
|
material2d_layout,
|
|
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 DrawMaterial2d<M> = (
|
|
SetItemPipeline,
|
|
SetMesh2dViewBindGroup<0>,
|
|
SetMaterial2dBindGroup<M, 1>,
|
|
SetMesh2dBindGroup<2>,
|
|
DrawMesh2d,
|
|
);
|
|
|
|
pub struct SetMaterial2dBindGroup<M: Material2d, const I: usize>(PhantomData<M>);
|
|
impl<P: PhaseItem, M: Material2d, const I: usize> RenderCommand<P>
|
|
for SetMaterial2dBindGroup<M, I>
|
|
{
|
|
type Param = SRes<RenderMaterials2d<M>>;
|
|
type ViewWorldQuery = ();
|
|
type ItemWorldQuery = Read<Handle<M>>;
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
_item: &P,
|
|
_view: (),
|
|
material2d_handle: ROQueryItem<'_, Self::ItemWorldQuery>,
|
|
materials: SystemParamItem<'w, '_, Self::Param>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
let material2d = materials.into_inner().get(material2d_handle).unwrap();
|
|
pass.set_bind_group(I, &material2d.bind_group, &[]);
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn queue_material2d_meshes<M: Material2d>(
|
|
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
|
material2d_pipeline: Res<Material2dPipeline<M>>,
|
|
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
|
pipeline_cache: Res<PipelineCache>,
|
|
msaa: Res<Msaa>,
|
|
render_meshes: Res<RenderAssets<Mesh>>,
|
|
render_materials: Res<RenderMaterials2d<M>>,
|
|
material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>,
|
|
mut views: Query<(
|
|
&ExtractedView,
|
|
&VisibleEntities,
|
|
Option<&Tonemapping>,
|
|
&mut RenderPhase<Transparent2d>,
|
|
)>,
|
|
) where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
if material2d_meshes.is_empty() {
|
|
return;
|
|
}
|
|
|
|
for (view, visible_entities, tonemapping, mut transparent_phase) in &mut views {
|
|
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
|
|
|
|
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
|
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
|
|
|
if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping {
|
|
if !view.hdr {
|
|
view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER;
|
|
|
|
if *deband_dither {
|
|
view_key |= Mesh2dPipelineKey::DEBAND_DITHER;
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) {
|
|
let mesh_key = view_key
|
|
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
|
|
|
let pipeline_id = pipelines.specialize(
|
|
&pipeline_cache,
|
|
&material2d_pipeline,
|
|
Material2dKey {
|
|
mesh_key,
|
|
bind_group_data: material2d.key.clone(),
|
|
},
|
|
&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,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Data prepared for a [`Material2d`] instance.
|
|
pub struct PreparedMaterial2d<T: Material2d> {
|
|
pub bindings: Vec<OwnedBindingResource>,
|
|
pub bind_group: BindGroup,
|
|
pub key: T::Data,
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
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.
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
pub struct RenderMaterials2d<T: Material2d>(HashMap<Handle<T>, PreparedMaterial2d<T>>);
|
|
|
|
impl<T: Material2d> Default for RenderMaterials2d<T> {
|
|
fn default() -> Self {
|
|
Self(Default::default())
|
|
}
|
|
}
|
|
|
|
/// 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 queued_assets = std::mem::take(&mut prepare_next_frame.assets);
|
|
for (handle, material) in queued_assets {
|
|
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: Material2d> {
|
|
pub mesh: Mesh2dHandle,
|
|
pub material: Handle<M>,
|
|
pub transform: Transform,
|
|
pub global_transform: GlobalTransform,
|
|
/// User indication of whether an entity is visible
|
|
pub visibility: Visibility,
|
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
pub computed_visibility: ComputedVisibility,
|
|
}
|
|
|
|
impl<M: Material2d> Default for MaterialMesh2dBundle<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
mesh: Default::default(),
|
|
material: Default::default(),
|
|
transform: Default::default(),
|
|
global_transform: Default::default(),
|
|
visibility: Default::default(),
|
|
computed_visibility: Default::default(),
|
|
}
|
|
}
|
|
}
|