bevy/crates/bevy_pbr/src/render/mesh.rs
Patrick Walton 7767a8d161
Refactor batch_and_prepare_binned_render_phase in preparation for bin retention. (#16922)
This commit makes the following changes:

* `IndirectParametersBuffer` has been changed from a `BufferVec` to a
`RawBufferVec`. This won about 20us or so on Bistro by avoiding `encase`
overhead.

* The methods on the `GetFullBatchData` trait no longer have the
`entity` parameter, as it was unused.

* `PreprocessWorkItem`, which specifies a transform-and-cull operation,
now supplies the mesh instance uniform output index directly instead of
having the shader look it up from the indirect draw parameters.
Accordingly, the responsibility of writing the output index to the
indirect draw parameters has been moved from the CPU to the GPU. This is
in preparation for retained indirect instance draw commands, where the
mesh instance uniform output index may change from frame to frame, while
the indirect instance draw commands will be cached. We won't want the
CPU to have to upload the same indirect draw parameters again and again
if a batch didn't change from frame to frame.

* `batch_and_prepare_binned_render_phase` and
`batch_and_prepare_sorted_render_phase` now allocate indirect draw
commands for an entire batch set at a time when possible, instead of one
batch at a time. This change will allow us to retain the indirect draw
commands for whole batch sets.

* `GetFullBatchData::get_batch_indirect_parameters_index` has been
replaced with `GetFullBatchData::write_batch_indirect_parameters`, which
takes an offset and writes into it instead of allocating. This is
necessary in order to use the optimization mentioned in the previous
point.

* At the WGSL level, `IndirectParameters` has been factored out into
`mesh_preprocess_types.wgsl`. This is because we'll need a new compute
shader that zeroes out the instance counts in preparation for a new
frame. That shader will need to access `IndirectParameters`, so it was
moved to a separate file.

* Bins are no longer raw vectors but are instances of a separate type,
`RenderBin`. This is so that the bin can eventually contain its retained
batches.
2024-12-30 20:11:31 +00:00

2833 lines
112 KiB
Rust

use core::mem::size_of;
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
use allocator::MeshAllocator;
use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
oit::{prepare_oit_buffers, OrderIndependentTransparencySettingsOffset},
prepass::MotionVectorPrepass,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::*,
query::ROQueryItem,
system::{lifetimeless::*, SystemParamItem, SystemState},
};
use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo};
use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4};
use bevy_render::{
batching::{
gpu_preprocessing::{
self, GpuPreprocessingSupport, IndirectParameters, IndirectParametersBuffer,
InstanceInputUniformBuffer,
},
no_gpu_preprocessing, GetBatchData, GetFullBatchData, NoAutomaticBatching,
},
camera::Camera,
mesh::*,
primitives::Aabb,
render_asset::RenderAssets,
render_phase::{
BinnedRenderPhasePlugin, PhaseItem, PhaseItemExtraIndex, RenderCommand,
RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass,
},
render_resource::*,
renderer::{RenderAdapter, RenderDevice, RenderQueue},
texture::DefaultImageSampler,
view::{
NoFrustumCulling, NoIndirectDrawing, RenderVisibilityRanges, ViewTarget, ViewUniformOffset,
ViewVisibility, VisibilityRange,
},
Extract,
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::{
default,
hashbrown::hash_map::Entry,
tracing::{error, warn},
HashMap, Parallel,
};
use material_bind_groups::MaterialBindingId;
use render::skin::{self, SkinIndex};
use crate::{
render::{
morph::{
extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices,
MorphUniforms,
},
skin::no_automatic_skin_batching,
},
*,
};
use bevy_render::sync_world::{MainEntity, MainEntityHashMap};
use bytemuck::{Pod, Zeroable};
use nonmax::{NonMaxU16, NonMaxU32};
use smallvec::{smallvec, SmallVec};
use static_assertions::const_assert_eq;
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
/// Provides support for rendering 3D meshes.
#[derive(Default)]
pub struct MeshRenderPlugin {
/// Whether we're building [`MeshUniform`]s on GPU.
///
/// This requires compute shader support and so will be forcibly disabled if
/// the platform doesn't support those.
pub use_gpu_instance_buffer_builder: bool,
}
pub const FORWARD_IO_HANDLE: Handle<Shader> = Handle::weak_from_u128(2645551199423808407);
pub const MESH_VIEW_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(8140454348013264787);
pub const MESH_VIEW_BINDINGS_HANDLE: Handle<Shader> = Handle::weak_from_u128(9076678235888822571);
pub const MESH_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(2506024101911992377);
pub const MESH_BINDINGS_HANDLE: Handle<Shader> = Handle::weak_from_u128(16831548636314682308);
pub const MESH_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(6300874327833745635);
pub const MESH_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(3252377289100772450);
pub const SKINNING_HANDLE: Handle<Shader> = Handle::weak_from_u128(13215291596265391738);
pub const MORPH_HANDLE: Handle<Shader> = Handle::weak_from_u128(970982813587607345);
/// How many textures are allowed in the view bind group layout (`@group(0)`) before
/// broader compatibility with WebGL and WebGPU is at risk, due to the minimum guaranteed
/// values for `MAX_TEXTURE_IMAGE_UNITS` (in WebGL) and `maxSampledTexturesPerShaderStage` (in WebGPU),
/// currently both at 16.
///
/// We use 10 here because it still leaves us, in a worst case scenario, with 6 textures for the other bind groups.
///
/// See: <https://gpuweb.github.io/gpuweb/#limits>
#[cfg(debug_assertions)]
pub const MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES: usize = 10;
impl Plugin for MeshRenderPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, FORWARD_IO_HANDLE, "forward_io.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
MESH_VIEW_TYPES_HANDLE,
"mesh_view_types.wgsl",
Shader::from_wgsl_with_defs,
vec![
ShaderDefVal::UInt(
"MAX_DIRECTIONAL_LIGHTS".into(),
MAX_DIRECTIONAL_LIGHTS as u32
),
ShaderDefVal::UInt(
"MAX_CASCADES_PER_LIGHT".into(),
MAX_CASCADES_PER_LIGHT as u32,
)
]
);
load_internal_asset!(
app,
MESH_VIEW_BINDINGS_HANDLE,
"mesh_view_bindings.wgsl",
Shader::from_wgsl
);
load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
MESH_FUNCTIONS_HANDLE,
"mesh_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(app, MESH_SHADER_HANDLE, "mesh.wgsl", Shader::from_wgsl);
load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl);
if app.get_sub_app(RenderApp).is_none() {
return;
}
app.add_systems(
PostUpdate,
(no_automatic_skin_batching, no_automatic_morph_batching),
)
.add_plugins((
BinnedRenderPhasePlugin::<Opaque3d, MeshPipeline>::default(),
BinnedRenderPhasePlugin::<AlphaMask3d, MeshPipeline>::default(),
BinnedRenderPhasePlugin::<Shadow, MeshPipeline>::default(),
BinnedRenderPhasePlugin::<Opaque3dDeferred, MeshPipeline>::default(),
BinnedRenderPhasePlugin::<AlphaMask3dDeferred, MeshPipeline>::default(),
SortedRenderPhasePlugin::<Transmissive3d, MeshPipeline>::default(),
SortedRenderPhasePlugin::<Transparent3d, MeshPipeline>::default(),
));
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<MeshBindGroups>()
.init_resource::<SkinIndices>()
.init_resource::<MorphUniforms>()
.init_resource::<MorphIndices>()
.init_resource::<MeshCullingDataBuffer>()
.init_resource::<RenderMeshMaterialIds>()
.add_systems(
ExtractSchedule,
(
extract_skins,
extract_morphs,
gpu_preprocessing::clear_batched_gpu_instance_buffers::<MeshPipeline>
.before(ExtractMeshesSet),
),
)
.add_systems(
Render,
(
set_mesh_motion_vector_flags.in_set(RenderSet::PrepareAssets),
prepare_skins.in_set(RenderSet::PrepareResources),
prepare_morphs.in_set(RenderSet::PrepareResources),
prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups),
prepare_mesh_view_bind_groups
.in_set(RenderSet::PrepareBindGroups)
.after(prepare_oit_buffers),
no_gpu_preprocessing::clear_batched_cpu_instance_buffers::<MeshPipeline>
.in_set(RenderSet::Cleanup)
.after(RenderSet::Render),
),
);
}
}
fn finish(&self, app: &mut App) {
let mut mesh_bindings_shader_defs = Vec::with_capacity(1);
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<GpuPreprocessingSupport>()
.init_resource::<SkinUniforms>();
let gpu_preprocessing_support =
render_app.world().resource::<GpuPreprocessingSupport>();
let use_gpu_instance_buffer_builder =
self.use_gpu_instance_buffer_builder && gpu_preprocessing_support.is_available();
let render_mesh_instances = RenderMeshInstances::new(use_gpu_instance_buffer_builder);
render_app.insert_resource(render_mesh_instances);
if use_gpu_instance_buffer_builder {
render_app
.init_resource::<gpu_preprocessing::BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>()
.init_resource::<RenderMeshInstanceGpuQueues>()
.add_systems(
ExtractSchedule,
extract_meshes_for_gpu_building
.in_set(ExtractMeshesSet),
)
.add_systems(
Render,
(
gpu_preprocessing::write_batched_instance_buffers::<MeshPipeline>
.in_set(RenderSet::PrepareResourcesFlush),
gpu_preprocessing::delete_old_work_item_buffers::<MeshPipeline>
.in_set(RenderSet::PrepareResources),
collect_meshes_for_gpu_building
.in_set(RenderSet::PrepareAssets)
.after(allocator::allocate_and_free_meshes)
.after(extract_skins)
// This must be before
// `set_mesh_motion_vector_flags` so it doesn't
// overwrite those flags.
.before(set_mesh_motion_vector_flags),
),
);
} else {
let render_device = render_app.world().resource::<RenderDevice>();
let cpu_batched_instance_buffer =
no_gpu_preprocessing::BatchedInstanceBuffer::<MeshUniform>::new(render_device);
render_app
.insert_resource(cpu_batched_instance_buffer)
.add_systems(
ExtractSchedule,
extract_meshes_for_cpu_building.in_set(ExtractMeshesSet),
)
.add_systems(
Render,
no_gpu_preprocessing::write_batched_instance_buffer::<MeshPipeline>
.in_set(RenderSet::PrepareResourcesFlush),
);
};
let render_device = render_app.world().resource::<RenderDevice>();
if let Some(per_object_buffer_batch_size) =
GpuArrayBuffer::<MeshUniform>::batch_size(render_device)
{
mesh_bindings_shader_defs.push(ShaderDefVal::UInt(
"PER_OBJECT_BUFFER_BATCH_SIZE".into(),
per_object_buffer_batch_size,
));
}
render_app
.init_resource::<MeshPipelineViewLayouts>()
.init_resource::<MeshPipeline>();
}
// Load the mesh_bindings shader module here as it depends on runtime information about
// whether storage buffers are supported, or the maximum uniform buffer binding size.
load_internal_asset!(
app,
MESH_BINDINGS_HANDLE,
"mesh_bindings.wgsl",
Shader::from_wgsl_with_defs,
mesh_bindings_shader_defs
);
}
}
#[derive(Component)]
pub struct MeshTransforms {
pub world_from_local: Affine3,
pub previous_world_from_local: Affine3,
pub flags: u32,
}
#[derive(ShaderType, Clone)]
pub struct MeshUniform {
// Affine 4x3 matrices transposed to 3x4
pub world_from_local: [Vec4; 3],
pub previous_world_from_local: [Vec4; 3],
// 3x3 matrix packed in mat2x4 and f32 as:
// [0].xyz, [1].x,
// [1].yz, [2].xy
// [2].z
pub local_from_world_transpose_a: [Vec4; 2],
pub local_from_world_transpose_b: f32,
pub flags: u32,
// Four 16-bit unsigned normalized UV values packed into a `UVec2`:
//
// <--- MSB LSB --->
// +---- min v ----+ +---- min u ----+
// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu,
// +---- max v ----+ +---- max u ----+
// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU,
//
// (MSB: most significant bit; LSB: least significant bit.)
pub lightmap_uv_rect: UVec2,
/// The index of this mesh's first vertex in the vertex buffer.
///
/// Multiple meshes can be packed into a single vertex buffer (see
/// [`MeshAllocator`]). This value stores the offset of the first vertex in
/// this mesh in that buffer.
pub first_vertex_index: u32,
/// The current skin index, or `u32::MAX` if there's no skin.
pub current_skin_index: u32,
/// The previous skin index, or `u32::MAX` if there's no previous skin.
pub previous_skin_index: u32,
/// The material and lightmap indices, packed into 32 bits.
///
/// Low 16 bits: index of the material inside the bind group data.
/// High 16 bits: index of the lightmap in the binding array.
pub material_and_lightmap_bind_group_slot: u32,
}
/// Information that has to be transferred from CPU to GPU in order to produce
/// the full [`MeshUniform`].
///
/// This is essentially a subset of the fields in [`MeshUniform`] above.
#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct MeshInputUniform {
/// Affine 4x3 matrix transposed to 3x4.
pub world_from_local: [Vec4; 3],
/// Four 16-bit unsigned normalized UV values packed into a `UVec2`:
///
/// ```text
/// <--- MSB LSB --->
/// +---- min v ----+ +---- min u ----+
/// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu,
/// +---- max v ----+ +---- max u ----+
/// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU,
///
/// (MSB: most significant bit; LSB: least significant bit.)
/// ```
pub lightmap_uv_rect: UVec2,
/// Various [`MeshFlags`].
pub flags: u32,
/// The index of this mesh's [`MeshInputUniform`] in the previous frame's
/// buffer, if applicable.
///
/// This is used for TAA. If not present, this will be `u32::MAX`.
pub previous_input_index: u32,
/// The index of this mesh's first vertex in the vertex buffer.
///
/// Multiple meshes can be packed into a single vertex buffer (see
/// [`MeshAllocator`]). This value stores the offset of the first vertex in
/// this mesh in that buffer.
pub first_vertex_index: u32,
/// The current skin index, or `u32::MAX` if there's no skin.
pub current_skin_index: u32,
/// The previous skin index, or `u32::MAX` if there's no previous skin.
pub previous_skin_index: u32,
/// The material and lightmap indices, packed into 32 bits.
///
/// Low 16 bits: index of the material inside the bind group data.
/// High 16 bits: index of the lightmap in the binding array.
pub material_and_lightmap_bind_group_slot: u32,
}
/// Information about each mesh instance needed to cull it on GPU.
///
/// This consists of its axis-aligned bounding box (AABB).
#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default)]
#[repr(C)]
pub struct MeshCullingData {
/// The 3D center of the AABB in model space, padded with an extra unused
/// float value.
pub aabb_center: Vec4,
/// The 3D extents of the AABB in model space, divided by two, padded with
/// an extra unused float value.
pub aabb_half_extents: Vec4,
}
/// A GPU buffer that holds the information needed to cull meshes on GPU.
///
/// At the moment, this simply holds each mesh's AABB.
///
/// To avoid wasting CPU time in the CPU culling case, this buffer will be empty
/// if GPU culling isn't in use.
#[derive(Resource, Deref, DerefMut)]
pub struct MeshCullingDataBuffer(RawBufferVec<MeshCullingData>);
impl MeshUniform {
pub fn new(
mesh_transforms: &MeshTransforms,
first_vertex_index: u32,
material_bind_group_slot: MaterialBindGroupSlot,
maybe_lightmap: Option<(LightmapSlotIndex, Rect)>,
current_skin_index: Option<u32>,
previous_skin_index: Option<u32>,
) -> Self {
let (local_from_world_transpose_a, local_from_world_transpose_b) =
mesh_transforms.world_from_local.inverse_transpose_3x3();
let lightmap_bind_group_slot = match maybe_lightmap {
None => u16::MAX,
Some((slot_index, _)) => slot_index.into(),
};
Self {
world_from_local: mesh_transforms.world_from_local.to_transpose(),
previous_world_from_local: mesh_transforms.previous_world_from_local.to_transpose(),
lightmap_uv_rect: pack_lightmap_uv_rect(maybe_lightmap.map(|(_, uv_rect)| uv_rect)),
local_from_world_transpose_a,
local_from_world_transpose_b,
flags: mesh_transforms.flags,
first_vertex_index,
current_skin_index: current_skin_index.unwrap_or(u32::MAX),
previous_skin_index: previous_skin_index.unwrap_or(u32::MAX),
material_and_lightmap_bind_group_slot: u32::from(material_bind_group_slot)
| ((lightmap_bind_group_slot as u32) << 16),
}
}
}
// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_types.wgsl!
bitflags::bitflags! {
/// Various flags and tightly-packed values on a mesh.
///
/// Flags grow from the top bit down; other values grow from the bottom bit
/// up.
#[repr(transparent)]
pub struct MeshFlags: u32 {
/// Bitmask for the 16-bit index into the LOD array.
///
/// This will be `u16::MAX` if this mesh has no LOD.
const LOD_INDEX_MASK = (1 << 16) - 1;
/// Disables frustum culling for this mesh.
///
/// This corresponds to the
/// [`bevy_render::view::visibility::NoFrustumCulling`] component.
const NO_FRUSTUM_CULLING = 1 << 28;
const SHADOW_RECEIVER = 1 << 29;
const TRANSMITTED_SHADOW_RECEIVER = 1 << 30;
// Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive,
// then the flag should be set, else it should not be set.
const SIGN_DETERMINANT_MODEL_3X3 = 1 << 31;
const NONE = 0;
const UNINITIALIZED = 0xFFFFFFFF;
}
}
impl MeshFlags {
fn from_components(
transform: &GlobalTransform,
lod_index: Option<NonMaxU16>,
no_frustum_culling: bool,
not_shadow_receiver: bool,
transmitted_receiver: bool,
) -> MeshFlags {
let mut mesh_flags = if not_shadow_receiver {
MeshFlags::empty()
} else {
MeshFlags::SHADOW_RECEIVER
};
if no_frustum_culling {
mesh_flags |= MeshFlags::NO_FRUSTUM_CULLING;
}
if transmitted_receiver {
mesh_flags |= MeshFlags::TRANSMITTED_SHADOW_RECEIVER;
}
if transform.affine().matrix3.determinant().is_sign_positive() {
mesh_flags |= MeshFlags::SIGN_DETERMINANT_MODEL_3X3;
}
let lod_index_bits = match lod_index {
None => u16::MAX,
Some(lod_index) => u16::from(lod_index),
};
mesh_flags |=
MeshFlags::from_bits_retain((lod_index_bits as u32) << MeshFlags::LOD_INDEX_SHIFT);
mesh_flags
}
/// The first bit of the LOD index.
pub const LOD_INDEX_SHIFT: u32 = 0;
}
bitflags::bitflags! {
/// Various useful flags for [`RenderMeshInstance`]s.
#[derive(Clone, Copy)]
pub struct RenderMeshInstanceFlags: u8 {
/// The mesh casts shadows.
const SHADOW_CASTER = 1 << 0;
/// The mesh can participate in automatic batching.
const AUTOMATIC_BATCHING = 1 << 1;
/// The mesh had a transform last frame and so is eligible for motion
/// vector computation.
const HAS_PREVIOUS_TRANSFORM = 1 << 2;
/// The mesh had a skin last frame and so that skin should be taken into
/// account for motion vector computation.
const HAS_PREVIOUS_SKIN = 1 << 3;
/// The mesh had morph targets last frame and so they should be taken
/// into account for motion vector computation.
const HAS_PREVIOUS_MORPH = 1 << 4;
}
}
/// CPU data that the render world keeps for each entity, when *not* using GPU
/// mesh uniform building.
#[derive(Deref, DerefMut)]
pub struct RenderMeshInstanceCpu {
/// Data shared between both the CPU mesh uniform building and the GPU mesh
/// uniform building paths.
#[deref]
pub shared: RenderMeshInstanceShared,
/// The transform of the mesh.
///
/// This will be written into the [`MeshUniform`] at the appropriate time.
pub transforms: MeshTransforms,
}
/// CPU data that the render world needs to keep for each entity that contains a
/// mesh when using GPU mesh uniform building.
#[derive(Deref, DerefMut)]
pub struct RenderMeshInstanceGpu {
/// Data shared between both the CPU mesh uniform building and the GPU mesh
/// uniform building paths.
#[deref]
pub shared: RenderMeshInstanceShared,
/// The translation of the mesh.
///
/// This is the only part of the transform that we have to keep on CPU (for
/// distance sorting).
pub translation: Vec3,
/// The index of the [`MeshInputUniform`] in the buffer.
pub current_uniform_index: NonMaxU32,
}
/// CPU data that the render world needs to keep about each entity that contains
/// a mesh.
pub struct RenderMeshInstanceShared {
/// The [`AssetId`] of the mesh.
pub mesh_asset_id: AssetId<Mesh>,
/// A slot for the material bind group index.
pub material_bindings_index: MaterialBindingId,
/// Various flags.
pub flags: RenderMeshInstanceFlags,
}
/// Information that is gathered during the parallel portion of mesh extraction
/// when GPU mesh uniform building is enabled.
///
/// From this, the [`MeshInputUniform`] and [`RenderMeshInstanceGpu`] are
/// prepared.
pub struct RenderMeshInstanceGpuBuilder {
/// Data that will be placed on the [`RenderMeshInstanceGpu`].
pub shared: RenderMeshInstanceShared,
/// The current transform.
pub world_from_local: Affine3,
/// Four 16-bit unsigned normalized UV values packed into a [`UVec2`]:
///
/// ```text
/// <--- MSB LSB --->
/// +---- min v ----+ +---- min u ----+
/// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu,
/// +---- max v ----+ +---- max u ----+
/// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU,
///
/// (MSB: most significant bit; LSB: least significant bit.)
/// ```
pub lightmap_uv_rect: UVec2,
/// The index of the previous mesh input.
pub previous_input_index: Option<NonMaxU32>,
/// Various flags.
pub mesh_flags: MeshFlags,
}
/// The per-thread queues used during [`extract_meshes_for_gpu_building`].
///
/// There are two varieties of these: one for when culling happens on CPU and
/// one for when culling happens on GPU. Having the two varieties avoids wasting
/// space if GPU culling is disabled.
#[derive(Default)]
pub enum RenderMeshInstanceGpuQueue {
/// The default value.
///
/// This becomes [`RenderMeshInstanceGpuQueue::CpuCulling`] or
/// [`RenderMeshInstanceGpuQueue::GpuCulling`] once extraction starts.
#[default]
None,
/// The version of [`RenderMeshInstanceGpuQueue`] that omits the
/// [`MeshCullingData`], so that we don't waste space when GPU
/// culling is disabled.
CpuCulling {
/// Stores GPU data for each entity that became visible or changed in
/// such a way that necessitates updating the [`MeshInputUniform`] (e.g.
/// changed transform).
changed: Vec<(MainEntity, RenderMeshInstanceGpuBuilder)>,
/// Stores the IDs of entities that became invisible this frame.
removed: Vec<MainEntity>,
},
/// The version of [`RenderMeshInstanceGpuQueue`] that contains the
/// [`MeshCullingData`], used when any view has GPU culling
/// enabled.
GpuCulling {
/// Stores GPU data for each entity that became visible or changed in
/// such a way that necessitates updating the [`MeshInputUniform`] (e.g.
/// changed transform).
changed: Vec<(MainEntity, RenderMeshInstanceGpuBuilder, MeshCullingData)>,
/// Stores the IDs of entities that became invisible this frame.
removed: Vec<MainEntity>,
},
}
/// The per-thread queues containing mesh instances, populated during the
/// extract phase.
///
/// These are filled in [`extract_meshes_for_gpu_building`] and consumed in
/// [`collect_meshes_for_gpu_building`].
#[derive(Resource, Default, Deref, DerefMut)]
pub struct RenderMeshInstanceGpuQueues(Parallel<RenderMeshInstanceGpuQueue>);
impl RenderMeshInstanceShared {
fn from_components(
previous_transform: Option<&PreviousGlobalTransform>,
mesh: &Mesh3d,
not_shadow_caster: bool,
no_automatic_batching: bool,
) -> Self {
let mut mesh_instance_flags = RenderMeshInstanceFlags::empty();
mesh_instance_flags.set(RenderMeshInstanceFlags::SHADOW_CASTER, !not_shadow_caster);
mesh_instance_flags.set(
RenderMeshInstanceFlags::AUTOMATIC_BATCHING,
!no_automatic_batching,
);
mesh_instance_flags.set(
RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM,
previous_transform.is_some(),
);
RenderMeshInstanceShared {
mesh_asset_id: mesh.id(),
flags: mesh_instance_flags,
// This gets filled in later, during `RenderMeshGpuBuilder::update`.
material_bindings_index: default(),
}
}
/// Returns true if this entity is eligible to participate in automatic
/// batching.
#[inline]
pub fn should_batch(&self) -> bool {
self.flags
.contains(RenderMeshInstanceFlags::AUTOMATIC_BATCHING)
}
}
/// Information that the render world keeps about each entity that contains a
/// mesh.
///
/// The set of information needed is different depending on whether CPU or GPU
/// [`MeshUniform`] building is in use.
#[derive(Resource)]
pub enum RenderMeshInstances {
/// Information needed when using CPU mesh instance data building.
CpuBuilding(RenderMeshInstancesCpu),
/// Information needed when using GPU mesh instance data building.
GpuBuilding(RenderMeshInstancesGpu),
}
/// Information that the render world keeps about each entity that contains a
/// mesh, when using CPU mesh instance data building.
#[derive(Default, Deref, DerefMut)]
pub struct RenderMeshInstancesCpu(MainEntityHashMap<RenderMeshInstanceCpu>);
/// Information that the render world keeps about each entity that contains a
/// mesh, when using GPU mesh instance data building.
#[derive(Default, Deref, DerefMut)]
pub struct RenderMeshInstancesGpu(MainEntityHashMap<RenderMeshInstanceGpu>);
/// Maps each mesh instance to the material ID, and allocated binding ID,
/// associated with that mesh instance.
#[derive(Resource, Default)]
pub struct RenderMeshMaterialIds {
/// Maps the mesh instance to the material ID.
pub(crate) mesh_to_material: MainEntityHashMap<UntypedAssetId>,
/// Maps the material ID to the binding ID, which describes the location of
/// that material bind group data in memory.
pub(crate) material_to_binding: HashMap<UntypedAssetId, MaterialBindingId>,
}
impl RenderMeshMaterialIds {
/// Returns the mesh material ID for the entity with the given mesh, or a
/// dummy mesh material ID if the mesh has no material ID.
///
/// Meshes almost always have materials, but in very specific circumstances
/// involving custom pipelines they won't. (See the
/// `specialized_mesh_pipelines` example.)
fn mesh_material_binding_id(&self, entity: MainEntity) -> MaterialBindingId {
self.mesh_to_material
.get(&entity)
.and_then(|mesh_material_asset_id| {
self.material_to_binding
.get(mesh_material_asset_id)
.cloned()
})
.unwrap_or_default()
}
}
impl RenderMeshInstances {
/// Creates a new [`RenderMeshInstances`] instance.
fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances {
if use_gpu_instance_buffer_builder {
RenderMeshInstances::GpuBuilding(RenderMeshInstancesGpu::default())
} else {
RenderMeshInstances::CpuBuilding(RenderMeshInstancesCpu::default())
}
}
/// Returns the ID of the mesh asset attached to the given entity, if any.
pub(crate) fn mesh_asset_id(&self, entity: MainEntity) -> Option<AssetId<Mesh>> {
match *self {
RenderMeshInstances::CpuBuilding(ref instances) => instances.mesh_asset_id(entity),
RenderMeshInstances::GpuBuilding(ref instances) => instances.mesh_asset_id(entity),
}
}
/// Constructs [`RenderMeshQueueData`] for the given entity, if it has a
/// mesh attached.
pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option<RenderMeshQueueData> {
match *self {
RenderMeshInstances::CpuBuilding(ref instances) => {
instances.render_mesh_queue_data(entity)
}
RenderMeshInstances::GpuBuilding(ref instances) => {
instances.render_mesh_queue_data(entity)
}
}
}
/// Inserts the given flags into the CPU or GPU render mesh instance data
/// for the given mesh as appropriate.
fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) {
match *self {
RenderMeshInstances::CpuBuilding(ref mut instances) => {
instances.insert_mesh_instance_flags(entity, flags);
}
RenderMeshInstances::GpuBuilding(ref mut instances) => {
instances.insert_mesh_instance_flags(entity, flags);
}
}
}
}
impl RenderMeshInstancesCpu {
fn mesh_asset_id(&self, entity: MainEntity) -> Option<AssetId<Mesh>> {
self.get(&entity)
.map(|render_mesh_instance| render_mesh_instance.mesh_asset_id)
}
fn render_mesh_queue_data(&self, entity: MainEntity) -> Option<RenderMeshQueueData> {
self.get(&entity)
.map(|render_mesh_instance| RenderMeshQueueData {
shared: &render_mesh_instance.shared,
translation: render_mesh_instance.transforms.world_from_local.translation,
})
}
/// Inserts the given flags into the render mesh instance data for the given
/// mesh.
fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) {
if let Some(instance) = self.get_mut(&entity) {
instance.flags.insert(flags);
}
}
}
impl RenderMeshInstancesGpu {
fn mesh_asset_id(&self, entity: MainEntity) -> Option<AssetId<Mesh>> {
self.get(&entity)
.map(|render_mesh_instance| render_mesh_instance.mesh_asset_id)
}
fn render_mesh_queue_data(&self, entity: MainEntity) -> Option<RenderMeshQueueData> {
self.get(&entity)
.map(|render_mesh_instance| RenderMeshQueueData {
shared: &render_mesh_instance.shared,
translation: render_mesh_instance.translation,
})
}
/// Inserts the given flags into the render mesh instance data for the given
/// mesh.
fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) {
if let Some(instance) = self.get_mut(&entity) {
instance.flags.insert(flags);
}
}
}
impl RenderMeshInstanceGpuQueue {
/// Clears out a [`RenderMeshInstanceGpuQueue`], creating or recreating it
/// as necessary.
///
/// `any_gpu_culling` should be set to true if any view has GPU culling
/// enabled.
fn init(&mut self, any_gpu_culling: bool) {
match (any_gpu_culling, &mut *self) {
(true, RenderMeshInstanceGpuQueue::GpuCulling { changed, removed }) => {
changed.clear();
removed.clear();
}
(true, _) => {
*self = RenderMeshInstanceGpuQueue::GpuCulling {
changed: vec![],
removed: vec![],
}
}
(false, RenderMeshInstanceGpuQueue::CpuCulling { changed, removed }) => {
changed.clear();
removed.clear();
}
(false, _) => {
*self = RenderMeshInstanceGpuQueue::CpuCulling {
changed: vec![],
removed: vec![],
}
}
}
}
/// Adds a new mesh to this queue.
fn push(
&mut self,
entity: MainEntity,
instance_builder: RenderMeshInstanceGpuBuilder,
culling_data_builder: Option<MeshCullingData>,
) {
match (&mut *self, culling_data_builder) {
(
&mut RenderMeshInstanceGpuQueue::CpuCulling {
changed: ref mut queue,
..
},
None,
) => {
queue.push((entity, instance_builder));
}
(
&mut RenderMeshInstanceGpuQueue::GpuCulling {
changed: ref mut queue,
..
},
Some(culling_data_builder),
) => {
queue.push((entity, instance_builder, culling_data_builder));
}
(_, None) => {
*self = RenderMeshInstanceGpuQueue::CpuCulling {
changed: vec![(entity, instance_builder)],
removed: vec![],
};
}
(_, Some(culling_data_builder)) => {
*self = RenderMeshInstanceGpuQueue::GpuCulling {
changed: vec![(entity, instance_builder, culling_data_builder)],
removed: vec![],
};
}
}
}
/// Adds the given entity to the `removed` list, queuing it for removal.
///
/// The `gpu_culling` parameter specifies whether GPU culling is enabled.
fn remove(&mut self, entity: MainEntity, gpu_culling: bool) {
match (&mut *self, gpu_culling) {
(RenderMeshInstanceGpuQueue::None, false) => {
*self = RenderMeshInstanceGpuQueue::CpuCulling {
changed: vec![],
removed: vec![entity],
}
}
(RenderMeshInstanceGpuQueue::None, true) => {
*self = RenderMeshInstanceGpuQueue::GpuCulling {
changed: vec![],
removed: vec![entity],
}
}
(RenderMeshInstanceGpuQueue::CpuCulling { removed, .. }, _)
| (RenderMeshInstanceGpuQueue::GpuCulling { removed, .. }, _) => {
removed.push(entity);
}
}
}
}
impl RenderMeshInstanceGpuBuilder {
/// Flushes this mesh instance to the [`RenderMeshInstanceGpu`] and
/// [`MeshInputUniform`] tables, replacing the existing entry if applicable.
#[allow(clippy::too_many_arguments)]
fn update(
mut self,
entity: MainEntity,
render_mesh_instances: &mut MainEntityHashMap<RenderMeshInstanceGpu>,
current_input_buffer: &mut InstanceInputUniformBuffer<MeshInputUniform>,
previous_input_buffer: &mut InstanceInputUniformBuffer<MeshInputUniform>,
mesh_allocator: &MeshAllocator,
mesh_material_ids: &RenderMeshMaterialIds,
render_lightmaps: &RenderLightmaps,
skin_indices: &SkinIndices,
) -> u32 {
let first_vertex_index = match mesh_allocator.mesh_vertex_slice(&self.shared.mesh_asset_id)
{
Some(mesh_vertex_slice) => mesh_vertex_slice.range.start,
None => 0,
};
let current_skin_index = match skin_indices.current.get(&entity) {
Some(skin_indices) => skin_indices.index(),
None => u32::MAX,
};
let previous_skin_index = match skin_indices.prev.get(&entity) {
Some(skin_indices) => skin_indices.index(),
None => u32::MAX,
};
// Look up the material index.
let mesh_material_binding_id = mesh_material_ids.mesh_material_binding_id(entity);
self.shared.material_bindings_index = mesh_material_binding_id;
let lightmap_slot = match render_lightmaps.render_lightmaps.get(&entity) {
Some(render_lightmap) => u16::from(*render_lightmap.slot_index),
None => u16::MAX,
};
// Create the mesh input uniform.
let mut mesh_input_uniform = MeshInputUniform {
world_from_local: self.world_from_local.to_transpose(),
lightmap_uv_rect: self.lightmap_uv_rect,
flags: self.mesh_flags.bits(),
previous_input_index: u32::MAX,
first_vertex_index,
current_skin_index,
previous_skin_index,
material_and_lightmap_bind_group_slot: u32::from(
self.shared.material_bindings_index.slot,
) | ((lightmap_slot as u32) << 16),
};
// Did the last frame contain this entity as well?
let current_uniform_index;
match render_mesh_instances.entry(entity) {
Entry::Occupied(mut occupied_entry) => {
// Yes, it did. Replace its entry with the new one.
// Reserve a slot.
current_uniform_index = u32::from(occupied_entry.get_mut().current_uniform_index);
// Save the old mesh input uniform. The mesh preprocessing
// shader will need it to compute motion vectors.
let previous_mesh_input_uniform = current_input_buffer.get(current_uniform_index);
let previous_input_index = previous_input_buffer.add(previous_mesh_input_uniform);
mesh_input_uniform.previous_input_index = previous_input_index;
// Write in the new mesh input uniform.
current_input_buffer.set(current_uniform_index, mesh_input_uniform);
occupied_entry.replace_entry_with(|_, _| {
Some(RenderMeshInstanceGpu {
translation: self.world_from_local.translation,
shared: self.shared,
current_uniform_index: NonMaxU32::new(current_uniform_index)
.unwrap_or_default(),
})
});
}
Entry::Vacant(vacant_entry) => {
// No, this is a new entity. Push its data on to the buffer.
current_uniform_index = current_input_buffer.add(mesh_input_uniform);
vacant_entry.insert(RenderMeshInstanceGpu {
translation: self.world_from_local.translation,
shared: self.shared,
current_uniform_index: NonMaxU32::new(current_uniform_index)
.unwrap_or_default(),
});
}
}
current_uniform_index
}
}
/// Removes a [`MeshInputUniform`] corresponding to an entity that became
/// invisible from the buffer.
fn remove_mesh_input_uniform(
entity: MainEntity,
render_mesh_instances: &mut MainEntityHashMap<RenderMeshInstanceGpu>,
current_input_buffer: &mut InstanceInputUniformBuffer<MeshInputUniform>,
) -> Option<u32> {
// Remove the uniform data.
let removed_render_mesh_instance = render_mesh_instances.remove(&entity)?;
let removed_uniform_index = removed_render_mesh_instance.current_uniform_index.get();
current_input_buffer.remove(removed_uniform_index);
Some(removed_uniform_index)
}
impl MeshCullingData {
/// Returns a new [`MeshCullingData`] initialized with the given AABB.
///
/// If no AABB is provided, an infinitely-large one is conservatively
/// chosen.
fn new(aabb: Option<&Aabb>) -> Self {
match aabb {
Some(aabb) => MeshCullingData {
aabb_center: aabb.center.extend(0.0),
aabb_half_extents: aabb.half_extents.extend(0.0),
},
None => MeshCullingData {
aabb_center: Vec3::ZERO.extend(0.0),
aabb_half_extents: Vec3::INFINITY.extend(0.0),
},
}
}
/// Flushes this mesh instance culling data to the
/// [`MeshCullingDataBuffer`], replacing the existing entry if applicable.
fn update(
&self,
mesh_culling_data_buffer: &mut MeshCullingDataBuffer,
instance_data_index: usize,
) {
while mesh_culling_data_buffer.len() < instance_data_index + 1 {
mesh_culling_data_buffer.push(MeshCullingData::default());
}
mesh_culling_data_buffer.values_mut()[instance_data_index] = *self;
}
}
impl Default for MeshCullingDataBuffer {
#[inline]
fn default() -> Self {
Self(RawBufferVec::new(BufferUsages::STORAGE))
}
}
/// Data that [`crate::material::queue_material_meshes`] and similar systems
/// need in order to place entities that contain meshes in the right batch.
#[derive(Deref)]
pub struct RenderMeshQueueData<'a> {
/// General information about the mesh instance.
#[deref]
pub shared: &'a RenderMeshInstanceShared,
/// The translation of the mesh instance.
pub translation: Vec3,
}
/// A [`SystemSet`] that encompasses both [`extract_meshes_for_cpu_building`]
/// and [`extract_meshes_for_gpu_building`].
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ExtractMeshesSet;
/// Extracts meshes from the main world into the render world, populating the
/// [`RenderMeshInstances`].
///
/// This is the variant of the system that runs when we're *not* using GPU
/// [`MeshUniform`] building.
pub fn extract_meshes_for_cpu_building(
mut render_mesh_instances: ResMut<RenderMeshInstances>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
mut render_mesh_instance_queues: Local<Parallel<Vec<(Entity, RenderMeshInstanceCpu)>>>,
meshes_query: Extract<
Query<(
Entity,
&ViewVisibility,
&GlobalTransform,
Option<&PreviousGlobalTransform>,
&Mesh3d,
Has<NoFrustumCulling>,
Has<NotShadowReceiver>,
Has<TransmittedShadowReceiver>,
Has<NotShadowCaster>,
Has<NoAutomaticBatching>,
Has<VisibilityRange>,
)>,
>,
) {
meshes_query.par_iter().for_each_init(
|| render_mesh_instance_queues.borrow_local_mut(),
|queue,
(
entity,
view_visibility,
transform,
previous_transform,
mesh,
no_frustum_culling,
not_shadow_receiver,
transmitted_receiver,
not_shadow_caster,
no_automatic_batching,
visibility_range,
)| {
if !view_visibility.get() {
return;
}
let mut lod_index = None;
if visibility_range {
lod_index = render_visibility_ranges.lod_index_for_entity(entity.into());
}
let mesh_flags = MeshFlags::from_components(
transform,
lod_index,
no_frustum_culling,
not_shadow_receiver,
transmitted_receiver,
);
let shared = RenderMeshInstanceShared::from_components(
previous_transform,
mesh,
not_shadow_caster,
no_automatic_batching,
);
let world_from_local = transform.affine();
queue.push((
entity,
RenderMeshInstanceCpu {
transforms: MeshTransforms {
world_from_local: (&world_from_local).into(),
previous_world_from_local: (&previous_transform
.map(|t| t.0)
.unwrap_or(world_from_local))
.into(),
flags: mesh_flags.bits(),
},
shared,
},
));
},
);
// Collect the render mesh instances.
let RenderMeshInstances::CpuBuilding(ref mut render_mesh_instances) = *render_mesh_instances
else {
panic!(
"`extract_meshes_for_cpu_building` should only be called if we're using CPU \
`MeshUniform` building"
);
};
render_mesh_instances.clear();
for queue in render_mesh_instance_queues.iter_mut() {
for (entity, render_mesh_instance) in queue.drain(..) {
render_mesh_instances.insert(entity.into(), render_mesh_instance);
}
}
}
/// Extracts meshes from the main world into the render world and queues
/// [`MeshInputUniform`]s to be uploaded to the GPU.
///
/// This is optimized to only look at entities that have changed since the last
/// frame.
///
/// This is the variant of the system that runs when we're using GPU
/// [`MeshUniform`] building.
#[allow(clippy::too_many_arguments)]
pub fn extract_meshes_for_gpu_building(
mut render_mesh_instances: ResMut<RenderMeshInstances>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
mut render_mesh_instance_queues: ResMut<RenderMeshInstanceGpuQueues>,
changed_meshes_query: Extract<
Query<
(
Entity,
&ViewVisibility,
&GlobalTransform,
Option<&PreviousGlobalTransform>,
Option<&Lightmap>,
Option<&Aabb>,
&Mesh3d,
Has<NoFrustumCulling>,
Has<NotShadowReceiver>,
Has<TransmittedShadowReceiver>,
Has<NotShadowCaster>,
Has<NoAutomaticBatching>,
Has<VisibilityRange>,
),
Or<(
Changed<ViewVisibility>,
Changed<GlobalTransform>,
Changed<PreviousGlobalTransform>,
Changed<Lightmap>,
Changed<Aabb>,
Changed<Mesh3d>,
Changed<NoFrustumCulling>,
Changed<NotShadowReceiver>,
Changed<TransmittedShadowReceiver>,
Changed<NotShadowCaster>,
Changed<NoAutomaticBatching>,
Changed<VisibilityRange>,
)>,
>,
>,
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
mut removed_global_transforms_query: Extract<RemovedComponents<GlobalTransform>>,
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
cameras_query: Extract<Query<(), (With<Camera>, Without<NoIndirectDrawing>)>>,
) {
let any_gpu_culling = !cameras_query.is_empty();
for render_mesh_instance_queue in render_mesh_instance_queues.iter_mut() {
render_mesh_instance_queue.init(any_gpu_culling);
}
// Collect render mesh instances. Build up the uniform buffer.
let RenderMeshInstances::GpuBuilding(ref mut render_mesh_instances) = *render_mesh_instances
else {
panic!(
"`extract_meshes_for_gpu_building` should only be called if we're \
using GPU `MeshUniform` building"
);
};
// Find all meshes that have changed, and record information needed to
// construct the `MeshInputUniform` for them.
changed_meshes_query.par_iter().for_each_init(
|| render_mesh_instance_queues.borrow_local_mut(),
|queue,
(
entity,
view_visibility,
transform,
previous_transform,
lightmap,
aabb,
mesh,
no_frustum_culling,
not_shadow_receiver,
transmitted_receiver,
not_shadow_caster,
no_automatic_batching,
visibility_range,
)| {
if !view_visibility.get() {
queue.remove(entity.into(), any_gpu_culling);
return;
}
let mut lod_index = None;
if visibility_range {
lod_index = render_visibility_ranges.lod_index_for_entity(entity.into());
}
let mesh_flags = MeshFlags::from_components(
transform,
lod_index,
no_frustum_culling,
not_shadow_receiver,
transmitted_receiver,
);
let shared = RenderMeshInstanceShared::from_components(
previous_transform,
mesh,
not_shadow_caster,
no_automatic_batching,
);
let lightmap_uv_rect = pack_lightmap_uv_rect(lightmap.map(|lightmap| lightmap.uv_rect));
let gpu_mesh_culling_data = any_gpu_culling.then(|| MeshCullingData::new(aabb));
let previous_input_index = if shared
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM)
{
render_mesh_instances
.get(&MainEntity::from(entity))
.map(|render_mesh_instance| render_mesh_instance.current_uniform_index)
} else {
None
};
let gpu_mesh_instance_builder = RenderMeshInstanceGpuBuilder {
shared,
world_from_local: (&transform.affine()).into(),
lightmap_uv_rect,
mesh_flags,
previous_input_index,
};
queue.push(
entity.into(),
gpu_mesh_instance_builder,
gpu_mesh_culling_data,
);
},
);
// Also record info about each mesh that became invisible.
let mut queue = render_mesh_instance_queues.borrow_local_mut();
for entity in removed_visibilities_query
.read()
.chain(removed_global_transforms_query.read())
.chain(removed_meshes_query.read())
{
// Only queue a mesh for removal if we didn't pick it up above.
// It's possible that a necessary component was removed and re-added in
// the same frame.
if !changed_meshes_query.contains(entity) {
queue.remove(entity.into(), any_gpu_culling);
}
}
}
/// A system that sets the [`RenderMeshInstanceFlags`] for each mesh based on
/// whether the previous frame had skins and/or morph targets.
///
/// Ordinarily, [`RenderMeshInstanceFlags`] are set during the extraction phase.
/// However, we can't do that for the flags related to skins and morph targets
/// because the previous frame's skin and morph targets are the responsibility
/// of [`extract_skins`] and [`extract_morphs`] respectively. We want to run
/// those systems in parallel with mesh extraction for performance, so we need
/// to defer setting of these mesh instance flags to after extraction, which
/// this system does. An alternative to having skin- and morph-target-related
/// data in [`RenderMeshInstanceFlags`] would be to have
/// [`crate::material::queue_material_meshes`] check the skin and morph target
/// tables for each mesh, but that would be too slow in the hot mesh queuing
/// loop.
fn set_mesh_motion_vector_flags(
mut render_mesh_instances: ResMut<RenderMeshInstances>,
skin_indices: Res<SkinIndices>,
morph_indices: Res<MorphIndices>,
) {
for &entity in skin_indices.prev.keys() {
render_mesh_instances
.insert_mesh_instance_flags(entity, RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN);
}
for &entity in morph_indices.prev.keys() {
render_mesh_instances
.insert_mesh_instance_flags(entity, RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH);
}
}
/// Creates the [`RenderMeshInstanceGpu`]s and [`MeshInputUniform`]s when GPU
/// mesh uniforms are built.
#[allow(clippy::too_many_arguments)]
pub fn collect_meshes_for_gpu_building(
render_mesh_instances: ResMut<RenderMeshInstances>,
batched_instance_buffers: ResMut<
gpu_preprocessing::BatchedInstanceBuffers<MeshUniform, MeshInputUniform>,
>,
mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,
mut render_mesh_instance_queues: ResMut<RenderMeshInstanceGpuQueues>,
mesh_allocator: Res<MeshAllocator>,
mesh_material_ids: Res<RenderMeshMaterialIds>,
render_lightmaps: Res<RenderLightmaps>,
skin_indices: Res<SkinIndices>,
) {
let RenderMeshInstances::GpuBuilding(ref mut render_mesh_instances) =
render_mesh_instances.into_inner()
else {
return;
};
// Collect render mesh instances. Build up the uniform buffer.
let gpu_preprocessing::BatchedInstanceBuffers {
ref mut current_input_buffer,
ref mut previous_input_buffer,
..
} = batched_instance_buffers.into_inner();
previous_input_buffer.clear();
// Build the [`RenderMeshInstance`]s and [`MeshInputUniform`]s.
for queue in render_mesh_instance_queues.iter_mut() {
match *queue {
RenderMeshInstanceGpuQueue::None => {
// This can only happen if the queue is empty.
}
RenderMeshInstanceGpuQueue::CpuCulling {
ref mut changed,
ref mut removed,
} => {
for (entity, mesh_instance_builder) in changed.drain(..) {
mesh_instance_builder.update(
entity,
&mut *render_mesh_instances,
current_input_buffer,
previous_input_buffer,
&mesh_allocator,
&mesh_material_ids,
&render_lightmaps,
&skin_indices,
);
}
for entity in removed.drain(..) {
remove_mesh_input_uniform(
entity,
&mut *render_mesh_instances,
current_input_buffer,
);
}
}
RenderMeshInstanceGpuQueue::GpuCulling {
ref mut changed,
ref mut removed,
} => {
for (entity, mesh_instance_builder, mesh_culling_builder) in changed.drain(..) {
let instance_data_index = mesh_instance_builder.update(
entity,
&mut *render_mesh_instances,
current_input_buffer,
previous_input_buffer,
&mesh_allocator,
&mesh_material_ids,
&render_lightmaps,
&skin_indices,
);
mesh_culling_builder
.update(&mut mesh_culling_data_buffer, instance_data_index as usize);
}
for entity in removed.drain(..) {
remove_mesh_input_uniform(
entity,
&mut *render_mesh_instances,
current_input_buffer,
);
}
}
}
}
// Buffers can't be empty. Make sure there's something in the previous input buffer.
previous_input_buffer.ensure_nonempty();
}
/// All data needed to construct a pipeline for rendering 3D meshes.
#[derive(Resource, Clone)]
pub struct MeshPipeline {
/// A reference to all the mesh pipeline view layouts.
pub view_layouts: MeshPipelineViewLayouts,
// This dummy white texture is to be used in place of optional StandardMaterial textures
pub dummy_white_gpu_image: GpuImage,
pub clustered_forward_buffer_binding_type: BufferBindingType,
pub mesh_layouts: MeshLayouts,
/// `MeshUniform`s are stored in arrays in buffers. If storage buffers are available, they
/// are used and this will be `None`, otherwise uniform buffers will be used with batches
/// of this many `MeshUniform`s, stored at dynamic offsets within the uniform buffer.
/// Use code like this in custom shaders:
/// ```wgsl
/// ##ifdef PER_OBJECT_BUFFER_BATCH_SIZE
/// @group(1) @binding(0) var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
/// ##else
/// @group(1) @binding(0) var<storage> mesh: array<Mesh>;
/// ##endif // PER_OBJECT_BUFFER_BATCH_SIZE
/// ```
pub per_object_buffer_batch_size: Option<u32>,
/// Whether binding arrays (a.k.a. bindless textures) are usable on the
/// current render device.
///
/// This affects whether reflection probes can be used.
pub binding_arrays_are_usable: bool,
/// Whether skins will use uniform buffers on account of storage buffers
/// being unavailable on this platform.
pub skins_use_uniform_buffers: bool,
}
impl FromWorld for MeshPipeline {
fn from_world(world: &mut World) -> Self {
let mut system_state: SystemState<(
Res<RenderDevice>,
Res<RenderAdapter>,
Res<DefaultImageSampler>,
Res<RenderQueue>,
Res<MeshPipelineViewLayouts>,
)> = SystemState::new(world);
let (render_device, render_adapter, default_sampler, render_queue, view_layouts) =
system_state.get_mut(world);
let clustered_forward_buffer_binding_type = render_device
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
let dummy_white_gpu_image = {
let image = Image::default();
let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = match image.sampler {
ImageSampler::Default => (**default_sampler).clone(),
ImageSampler::Descriptor(ref descriptor) => {
render_device.create_sampler(&descriptor.as_wgpu())
}
};
let format_size = image.texture_descriptor.format.pixel_size();
render_queue.write_texture(
texture.as_image_copy(),
&image.data,
ImageDataLayout {
offset: 0,
bytes_per_row: Some(image.width() * format_size as u32),
rows_per_image: None,
},
image.texture_descriptor.size,
);
let texture_view = texture.create_view(&TextureViewDescriptor::default());
GpuImage {
texture,
texture_view,
texture_format: image.texture_descriptor.format,
sampler,
size: image.texture_descriptor.size,
mip_level_count: image.texture_descriptor.mip_level_count,
}
};
MeshPipeline {
view_layouts: view_layouts.clone(),
clustered_forward_buffer_binding_type,
dummy_white_gpu_image,
mesh_layouts: MeshLayouts::new(&render_device, &render_adapter),
per_object_buffer_batch_size: GpuArrayBuffer::<MeshUniform>::batch_size(&render_device),
binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device),
}
}
}
impl MeshPipeline {
pub fn get_image_texture<'a>(
&'a self,
gpu_images: &'a RenderAssets<GpuImage>,
handle_option: &Option<Handle<Image>>,
) -> Option<(&'a TextureView, &'a Sampler)> {
if let Some(handle) = handle_option {
let gpu_image = gpu_images.get(handle)?;
Some((&gpu_image.texture_view, &gpu_image.sampler))
} else {
Some((
&self.dummy_white_gpu_image.texture_view,
&self.dummy_white_gpu_image.sampler,
))
}
}
pub fn get_view_layout(&self, layout_key: MeshPipelineViewLayoutKey) -> &BindGroupLayout {
self.view_layouts.get_view_layout(layout_key)
}
}
impl GetBatchData for MeshPipeline {
type Param = (
SRes<RenderMeshInstances>,
SRes<RenderLightmaps>,
SRes<RenderAssets<RenderMesh>>,
SRes<MeshAllocator>,
SRes<SkinIndices>,
);
// The material bind group ID, the mesh ID, and the lightmap ID,
// respectively.
type CompareData = (
MaterialBindGroupIndex,
AssetId<Mesh>,
Option<AssetId<Image>>,
);
type BufferData = MeshUniform;
fn get_batch_data(
(mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem<Self::Param>,
(_entity, main_entity): (Entity, MainEntity),
) -> Option<(Self::BufferData, Option<Self::CompareData>)> {
let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else {
error!(
"`get_batch_data` should never be called in GPU mesh uniform \
building mode"
);
return None;
};
let mesh_instance = mesh_instances.get(&main_entity)?;
let first_vertex_index =
match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) {
Some(mesh_vertex_slice) => mesh_vertex_slice.range.start,
None => 0,
};
let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity);
let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index);
let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index);
let material_bind_group_index = mesh_instance.material_bindings_index;
Some((
MeshUniform::new(
&mesh_instance.transforms,
first_vertex_index,
material_bind_group_index.slot,
maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)),
current_skin_index,
previous_skin_index,
),
mesh_instance.should_batch().then_some((
material_bind_group_index.group,
mesh_instance.mesh_asset_id,
maybe_lightmap.map(|lightmap| lightmap.image),
)),
))
}
}
impl GetFullBatchData for MeshPipeline {
type BufferInputData = MeshInputUniform;
fn get_index_and_compare_data(
(mesh_instances, lightmaps, _, _, _): &SystemParamItem<Self::Param>,
main_entity: MainEntity,
) -> Option<(NonMaxU32, Option<Self::CompareData>)> {
// This should only be called during GPU building.
let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else {
error!(
"`get_index_and_compare_data` should never be called in CPU mesh uniform building \
mode"
);
return None;
};
let mesh_instance = mesh_instances.get(&main_entity)?;
let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity);
Some((
mesh_instance.current_uniform_index,
mesh_instance.should_batch().then_some((
mesh_instance.material_bindings_index.group,
mesh_instance.mesh_asset_id,
maybe_lightmap.map(|lightmap| lightmap.image),
)),
))
}
fn get_binned_batch_data(
(mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem<Self::Param>,
main_entity: MainEntity,
) -> Option<Self::BufferData> {
let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else {
error!(
"`get_binned_batch_data` should never be called in GPU mesh uniform building mode"
);
return None;
};
let mesh_instance = mesh_instances.get(&main_entity)?;
let first_vertex_index =
match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) {
Some(mesh_vertex_slice) => mesh_vertex_slice.range.start,
None => 0,
};
let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity);
let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index);
let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index);
Some(MeshUniform::new(
&mesh_instance.transforms,
first_vertex_index,
mesh_instance.material_bindings_index.slot,
maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)),
current_skin_index,
previous_skin_index,
))
}
fn get_binned_index(
(mesh_instances, _, _, _, _): &SystemParamItem<Self::Param>,
main_entity: MainEntity,
) -> Option<NonMaxU32> {
// This should only be called during GPU building.
let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else {
error!(
"`get_binned_index` should never be called in CPU mesh uniform \
building mode"
);
return None;
};
mesh_instances
.get(&main_entity)
.map(|entity| entity.current_uniform_index)
}
fn write_batch_indirect_parameters(
(mesh_instances, _, meshes, mesh_allocator, _): &SystemParamItem<Self::Param>,
indirect_parameters_buffer: &mut IndirectParametersBuffer,
indirect_parameters_offset: u32,
main_entity: MainEntity,
) {
write_batch_indirect_parameters(
mesh_instances,
meshes,
mesh_allocator,
indirect_parameters_buffer,
indirect_parameters_offset,
main_entity,
);
}
}
/// Pushes a set of [`IndirectParameters`] onto the [`IndirectParametersBuffer`]
/// for the given mesh instance, and returns the index of those indirect
/// parameters.
fn write_batch_indirect_parameters(
mesh_instances: &RenderMeshInstances,
meshes: &RenderAssets<RenderMesh>,
mesh_allocator: &MeshAllocator,
indirect_parameters_buffer: &mut IndirectParametersBuffer,
indirect_parameters_offset: u32,
main_entity: MainEntity,
) {
// This should only be called during GPU building.
let RenderMeshInstances::GpuBuilding(ref mesh_instances) = *mesh_instances else {
error!(
"`write_batch_indirect_parameters_index` should never be called in CPU mesh uniform \
building mode"
);
return;
};
let Some(mesh_instance) = mesh_instances.get(&main_entity) else {
return;
};
let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else {
return;
};
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)
else {
return;
};
// Note that `IndirectParameters` covers both of these structures, even
// though they actually have distinct layouts. See the comment above that
// type for more information.
let indirect_parameters = match mesh.buffer_info {
RenderMeshBufferInfo::Indexed {
count: index_count, ..
} => {
let Some(index_buffer_slice) =
mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id)
else {
return;
};
IndirectParameters {
vertex_or_index_count: index_count,
instance_count: 0,
first_vertex_or_first_index: index_buffer_slice.range.start,
base_vertex_or_first_instance: vertex_buffer_slice.range.start,
first_instance: 0,
}
}
RenderMeshBufferInfo::NonIndexed => IndirectParameters {
vertex_or_index_count: mesh.vertex_count,
instance_count: 0,
first_vertex_or_first_index: vertex_buffer_slice.range.start,
base_vertex_or_first_instance: 0,
// Use `0xffffffff` as a placeholder to tell the mesh preprocessing
// shader that this is a non-indexed mesh.
first_instance: !0,
},
};
indirect_parameters_buffer.set(indirect_parameters_offset, indirect_parameters);
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
// NOTE: Apparently quadro drivers support up to 64x MSAA.
/// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.
pub struct MeshPipelineKey: u64 {
// Nothing
const NONE = 0;
// Inherited bits
const MORPH_TARGETS = BaseMeshPipelineKey::MORPH_TARGETS.bits();
// Flag bits
const HDR = 1 << 0;
const TONEMAP_IN_SHADER = 1 << 1;
const DEBAND_DITHER = 1 << 2;
const DEPTH_PREPASS = 1 << 3;
const NORMAL_PREPASS = 1 << 4;
const DEFERRED_PREPASS = 1 << 5;
const MOTION_VECTOR_PREPASS = 1 << 6;
const MAY_DISCARD = 1 << 7; // Guards shader codepaths that may discard, allowing early depth tests in most cases
// See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test
const ENVIRONMENT_MAP = 1 << 8;
const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9;
const UNCLIPPED_DEPTH_ORTHO = 1 << 10; // Disables depth clipping for use with directional light shadow views
// Emulated via fragment shader depth on hardware that doesn't support it natively
// See: https://www.w3.org/TR/webgpu/#depth-clipping and https://therealmjp.github.io/posts/shadow-maps/#disabling-z-clipping
const TEMPORAL_JITTER = 1 << 11;
const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12;
const LIGHTMAPPED = 1 << 13;
const IRRADIANCE_VOLUME = 1 << 14;
const VISIBILITY_RANGE_DITHER = 1 << 15;
const SCREEN_SPACE_REFLECTIONS = 1 << 16;
const HAS_PREVIOUS_SKIN = 1 << 17;
const HAS_PREVIOUS_MORPH = 1 << 18;
const OIT_ENABLED = 1 << 19;
const LAST_FLAG = Self::OIT_ENABLED.bits();
// Bitfields
const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask
const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; // ← As blend states is on 3 bits, it can range from 0 to 7
const BLEND_MULTIPLY = 2 << Self::BLEND_SHIFT_BITS; // ← See `BLEND_MASK_BITS` for the number of bits available
const BLEND_ALPHA = 3 << Self::BLEND_SHIFT_BITS; //
const BLEND_ALPHA_TO_COVERAGE = 4 << Self::BLEND_SHIFT_BITS; // ← We still have room for three more values without adding more bits
const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_GAUSSIAN = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_TEMPORAL = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS = Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW = 0 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM = 1 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH = 2 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA = 3 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
const ALL_RESERVED_BITS =
Self::BLEND_RESERVED_BITS.bits() |
Self::MSAA_RESERVED_BITS.bits() |
Self::TONEMAP_METHOD_RESERVED_BITS.bits() |
Self::SHADOW_FILTER_METHOD_RESERVED_BITS.bits() |
Self::VIEW_PROJECTION_RESERVED_BITS.bits() |
Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS.bits();
}
}
impl MeshPipelineKey {
const MSAA_MASK_BITS: u64 = 0b111;
const MSAA_SHIFT_BITS: u64 = Self::LAST_FLAG.bits().trailing_zeros() as u64 + 1;
const BLEND_MASK_BITS: u64 = 0b111;
const BLEND_SHIFT_BITS: u64 = Self::MSAA_MASK_BITS.count_ones() as u64 + Self::MSAA_SHIFT_BITS;
const TONEMAP_METHOD_MASK_BITS: u64 = 0b111;
const TONEMAP_METHOD_SHIFT_BITS: u64 =
Self::BLEND_MASK_BITS.count_ones() as u64 + Self::BLEND_SHIFT_BITS;
const SHADOW_FILTER_METHOD_MASK_BITS: u64 = 0b11;
const SHADOW_FILTER_METHOD_SHIFT_BITS: u64 =
Self::TONEMAP_METHOD_MASK_BITS.count_ones() as u64 + Self::TONEMAP_METHOD_SHIFT_BITS;
const VIEW_PROJECTION_MASK_BITS: u64 = 0b11;
const VIEW_PROJECTION_SHIFT_BITS: u64 = Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones()
as u64
+ Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS: u64 = 0b11;
const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u64 =
Self::VIEW_PROJECTION_MASK_BITS.count_ones() as u64 + Self::VIEW_PROJECTION_SHIFT_BITS;
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
let msaa_bits =
(msaa_samples.trailing_zeros() as u64 & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
Self::from_bits_retain(msaa_bits)
}
pub fn from_hdr(hdr: bool) -> Self {
if hdr {
MeshPipelineKey::HDR
} else {
MeshPipelineKey::NONE
}
}
pub fn msaa_samples(&self) -> u32 {
1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)
}
pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self {
let primitive_topology_bits = ((primitive_topology as u64)
& BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS)
<< BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
Self::from_bits_retain(primitive_topology_bits)
}
pub fn primitive_topology(&self) -> PrimitiveTopology {
let primitive_topology_bits = (self.bits()
>> BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS)
& BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS;
match primitive_topology_bits {
x if x == PrimitiveTopology::PointList as u64 => PrimitiveTopology::PointList,
x if x == PrimitiveTopology::LineList as u64 => PrimitiveTopology::LineList,
x if x == PrimitiveTopology::LineStrip as u64 => PrimitiveTopology::LineStrip,
x if x == PrimitiveTopology::TriangleList as u64 => PrimitiveTopology::TriangleList,
x if x == PrimitiveTopology::TriangleStrip as u64 => PrimitiveTopology::TriangleStrip,
_ => PrimitiveTopology::default(),
}
}
}
// Ensure that we didn't overflow the number of bits available in `MeshPipelineKey`.
const_assert_eq!(
(((MeshPipelineKey::LAST_FLAG.bits() << 1) - 1) | MeshPipelineKey::ALL_RESERVED_BITS.bits())
& BaseMeshPipelineKey::all().bits(),
0
);
// Ensure that the reserved bits don't overlap with the topology bits
const_assert_eq!(
(BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS
<< BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS)
& MeshPipelineKey::ALL_RESERVED_BITS.bits(),
0
);
fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool {
layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
&& layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
}
pub fn setup_morph_and_skinning_defs(
mesh_layouts: &MeshLayouts,
layout: &MeshVertexBufferLayoutRef,
offset: u32,
key: &MeshPipelineKey,
shader_defs: &mut Vec<ShaderDefVal>,
vertex_attributes: &mut Vec<VertexAttributeDescriptor>,
skins_use_uniform_buffers: bool,
) -> BindGroupLayout {
let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS);
let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED);
let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS);
if skins_use_uniform_buffers {
shader_defs.push("SKINS_USE_UNIFORM_BUFFERS".into());
}
let mut add_skin_data = || {
shader_defs.push("SKINNED".into());
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset));
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1));
};
match (
is_skinned(layout),
is_morphed,
is_lightmapped,
motion_vector_prepass,
) {
(true, false, _, true) => {
add_skin_data();
mesh_layouts.skinned_motion.clone()
}
(true, false, _, false) => {
add_skin_data();
mesh_layouts.skinned.clone()
}
(true, true, _, true) => {
add_skin_data();
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_skinned_motion.clone()
}
(true, true, _, false) => {
add_skin_data();
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_skinned.clone()
}
(false, true, _, true) => {
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed_motion.clone()
}
(false, true, _, false) => {
shader_defs.push("MORPH_TARGETS".into());
mesh_layouts.morphed.clone()
}
(false, false, true, _) => mesh_layouts.lightmapped.clone(),
(false, false, false, _) => mesh_layouts.model_only.clone(),
}
}
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = Vec::new();
let mut vertex_attributes = Vec::new();
// Let the shader code know that it's running in a mesh pipeline.
shader_defs.push("MESH_PIPELINE".into());
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
shader_defs.push("VERTEX_POSITIONS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}
if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {
shader_defs.push("VERTEX_NORMALS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1));
}
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_A".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2));
}
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3));
}
if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push("VERTEX_TANGENTS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
}
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
shader_defs.push("VERTEX_COLORS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5));
}
if cfg!(feature = "pbr_transmission_textures") {
shader_defs.push("PBR_TRANSMISSION_TEXTURES_SUPPORTED".into());
}
if cfg!(feature = "pbr_multi_layer_material_textures") {
shader_defs.push("PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED".into());
}
if cfg!(feature = "pbr_anisotropy_texture") {
shader_defs.push("PBR_ANISOTROPY_TEXTURE_SUPPORTED".into());
}
let mut bind_group_layout = vec![self.get_view_layout(key.into()).clone()];
if key.msaa_samples() > 1 {
shader_defs.push("MULTISAMPLED".into());
};
bind_group_layout.push(setup_morph_and_skinning_defs(
&self.mesh_layouts,
layout,
6,
&key,
&mut shader_defs,
&mut vertex_attributes,
self.skins_use_uniform_buffers,
));
if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into());
}
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
let (label, blend, depth_write_enabled);
let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
let (mut is_opaque, mut alpha_to_coverage_enabled) = (false, false);
if key.contains(MeshPipelineKey::OIT_ENABLED) && pass == MeshPipelineKey::BLEND_ALPHA {
label = "oit_mesh_pipeline".into();
// TODO tail blending would need alpha blending
blend = None;
shader_defs.push("OIT_ENABLED".into());
// TODO it should be possible to use this to combine MSAA and OIT
// alpha_to_coverage_enabled = true;
depth_write_enabled = false;
} else if pass == MeshPipelineKey::BLEND_ALPHA {
label = "alpha_blend_mesh_pipeline".into();
blend = Some(BlendState::ALPHA_BLENDING);
// For the transparent pass, fragments that are closer will be alpha blended
// but their depth is not written to the depth buffer
depth_write_enabled = false;
} else if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA {
label = "premultiplied_alpha_mesh_pipeline".into();
blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING);
shader_defs.push("PREMULTIPLY_ALPHA".into());
shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into());
// For the transparent pass, fragments that are closer will be alpha blended
// but their depth is not written to the depth buffer
depth_write_enabled = false;
} else if pass == MeshPipelineKey::BLEND_MULTIPLY {
label = "multiply_mesh_pipeline".into();
blend = Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::Dst,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent::OVER,
});
shader_defs.push("PREMULTIPLY_ALPHA".into());
shader_defs.push("BLEND_MULTIPLY".into());
// For the multiply pass, fragments that are closer will be alpha blended
// but their depth is not written to the depth buffer
depth_write_enabled = false;
} else if pass == MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE {
label = "alpha_to_coverage_mesh_pipeline".into();
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases
blend = None;
// For the opaque and alpha mask passes, fragments that are closer will replace
// the current fragment value in the output and the depth is written to the
// depth buffer
depth_write_enabled = true;
is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
alpha_to_coverage_enabled = true;
shader_defs.push("ALPHA_TO_COVERAGE".into());
} else {
label = "opaque_mesh_pipeline".into();
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases
blend = None;
// For the opaque and alpha mask passes, fragments that are closer will replace
// the current fragment value in the output and the depth is written to the
// depth buffer
depth_write_enabled = true;
is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
}
if key.contains(MeshPipelineKey::NORMAL_PREPASS) {
shader_defs.push("NORMAL_PREPASS".into());
}
if key.contains(MeshPipelineKey::DEPTH_PREPASS) {
shader_defs.push("DEPTH_PREPASS".into());
}
if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
shader_defs.push("MOTION_VECTOR_PREPASS".into());
}
if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
shader_defs.push("HAS_PREVIOUS_SKIN".into());
}
if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
shader_defs.push("HAS_PREVIOUS_MORPH".into());
}
if key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
shader_defs.push("DEFERRED_PREPASS".into());
}
if key.contains(MeshPipelineKey::NORMAL_PREPASS) && key.msaa_samples() == 1 && is_opaque {
shader_defs.push("LOAD_PREPASS_NORMALS".into());
}
let view_projection = key.intersection(MeshPipelineKey::VIEW_PROJECTION_RESERVED_BITS);
if view_projection == MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD {
shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into());
} else if view_projection == MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE {
shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into());
} else if view_projection == MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC {
shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into());
}
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("WEBGL2".into());
#[cfg(feature = "experimental_pbr_pcss")]
shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into());
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
shader_defs.push("TONEMAP_IN_SHADER".into());
shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
));
shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
));
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
if method == MeshPipelineKey::TONEMAP_METHOD_NONE {
shader_defs.push("TONEMAP_METHOD_NONE".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD {
shader_defs.push("TONEMAP_METHOD_REINHARD".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {
shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED {
shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_AGX {
shader_defs.push("TONEMAP_METHOD_AGX".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM {
shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
}
// Debanding is tied to tonemapping in the shader, cannot run without it.
if key.contains(MeshPipelineKey::DEBAND_DITHER) {
shader_defs.push("DEBAND_DITHER".into());
}
}
if key.contains(MeshPipelineKey::MAY_DISCARD) {
shader_defs.push("MAY_DISCARD".into());
}
if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
shader_defs.push("ENVIRONMENT_MAP".into());
}
if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) && IRRADIANCE_VOLUMES_ARE_USABLE {
shader_defs.push("IRRADIANCE_VOLUME".into());
}
if key.contains(MeshPipelineKey::LIGHTMAPPED) {
shader_defs.push("LIGHTMAP".into());
}
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
shader_defs.push("TEMPORAL_JITTER".into());
}
let shadow_filter_method =
key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
}
let blur_quality =
key.intersection(MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS);
shader_defs.push(ShaderDefVal::Int(
"SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS".into(),
match blur_quality {
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW => 4,
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM => 8,
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH => 16,
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA => 32,
_ => unreachable!(), // Not possible, since the mask is 2 bits, and we've covered all 4 cases
},
));
if key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) {
shader_defs.push("VISIBILITY_RANGE_DITHER".into());
}
if self.binding_arrays_are_usable {
shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
}
if IRRADIANCE_VOLUMES_ARE_USABLE {
shader_defs.push("IRRADIANCE_VOLUMES_ARE_USABLE".into());
}
let format = if key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
// This is defined here so that custom shaders that use something other than
// the mesh binding from bevy_pbr::mesh_bindings can easily make use of this
// in their own shaders.
if let Some(per_object_buffer_batch_size) = self.per_object_buffer_batch_size {
shader_defs.push(ShaderDefVal::UInt(
"PER_OBJECT_BUFFER_BATCH_SIZE".into(),
per_object_buffer_batch_size,
));
}
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: MESH_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: MESH_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend,
write_mask: ColorWrites::ALL,
})],
}),
layout: bind_group_layout,
push_constant_ranges: vec![],
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: key.primitive_topology(),
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled,
depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled,
},
label: Some(label),
zero_initialize_workgroup_memory: false,
})
}
}
/// Bind groups for meshes currently loaded.
#[derive(Resource, Default)]
pub struct MeshBindGroups {
model_only: Option<BindGroup>,
skinned: Option<MeshBindGroupPair>,
morph_targets: HashMap<AssetId<Mesh>, MeshBindGroupPair>,
lightmaps: HashMap<LightmapSlabIndex, BindGroup>,
}
pub struct MeshBindGroupPair {
motion_vectors: BindGroup,
no_motion_vectors: BindGroup,
}
impl MeshBindGroups {
pub fn reset(&mut self) {
self.model_only = None;
self.skinned = None;
self.morph_targets.clear();
self.lightmaps.clear();
}
/// Get the `BindGroup` for `RenderMesh` with given `handle_id` and lightmap
/// key `lightmap`.
pub fn get(
&self,
asset_id: AssetId<Mesh>,
lightmap: Option<LightmapSlabIndex>,
is_skinned: bool,
morph: bool,
motion_vectors: bool,
) -> Option<&BindGroup> {
match (is_skinned, morph, lightmap) {
(_, true, _) => self
.morph_targets
.get(&asset_id)
.map(|bind_group_pair| bind_group_pair.get(motion_vectors)),
(true, false, _) => self
.skinned
.as_ref()
.map(|bind_group_pair| bind_group_pair.get(motion_vectors)),
(false, false, Some(lightmap_slab)) => self.lightmaps.get(&lightmap_slab),
(false, false, None) => self.model_only.as_ref(),
}
}
}
impl MeshBindGroupPair {
fn get(&self, motion_vectors: bool) -> &BindGroup {
if motion_vectors {
&self.motion_vectors
} else {
&self.no_motion_vectors
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_mesh_bind_group(
meshes: Res<RenderAssets<RenderMesh>>,
mut groups: ResMut<MeshBindGroups>,
mesh_pipeline: Res<MeshPipeline>,
render_device: Res<RenderDevice>,
cpu_batched_instance_buffer: Option<
Res<no_gpu_preprocessing::BatchedInstanceBuffer<MeshUniform>>,
>,
gpu_batched_instance_buffers: Option<
Res<gpu_preprocessing::BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
>,
skins_uniform: Res<SkinUniforms>,
weights_uniform: Res<MorphUniforms>,
mut render_lightmaps: ResMut<RenderLightmaps>,
) {
groups.reset();
let layouts = &mesh_pipeline.mesh_layouts;
let model = if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer {
cpu_batched_instance_buffer
.into_inner()
.instance_data_binding()
} else if let Some(gpu_batched_instance_buffers) = gpu_batched_instance_buffers {
gpu_batched_instance_buffers
.into_inner()
.instance_data_binding()
} else {
return;
};
let Some(model) = model else { return };
groups.model_only = Some(layouts.model_only(&render_device, &model));
// Create the skinned mesh bind group with the current and previous buffers
// (the latter being for motion vector computation). If there's no previous
// buffer, just use the current one as the shader will ignore it.
let skin = skins_uniform.current_buffer.buffer();
if let Some(skin) = skin {
let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin);
groups.skinned = Some(MeshBindGroupPair {
motion_vectors: layouts.skinned_motion(&render_device, &model, skin, prev_skin),
no_motion_vectors: layouts.skinned(&render_device, &model, skin),
});
}
// Create the morphed bind groups just like we did for the skinned bind
// group.
if let Some(weights) = weights_uniform.current_buffer.buffer() {
let prev_weights = weights_uniform.prev_buffer.buffer().unwrap_or(weights);
for (id, gpu_mesh) in meshes.iter() {
if let Some(targets) = gpu_mesh.morph_targets.as_ref() {
let bind_group_pair = match skin.filter(|_| is_skinned(&gpu_mesh.layout)) {
Some(skin) => {
let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin);
MeshBindGroupPair {
motion_vectors: layouts.morphed_skinned_motion(
&render_device,
&model,
skin,
weights,
targets,
prev_skin,
prev_weights,
),
no_motion_vectors: layouts.morphed_skinned(
&render_device,
&model,
skin,
weights,
targets,
),
}
}
None => MeshBindGroupPair {
motion_vectors: layouts.morphed_motion(
&render_device,
&model,
weights,
targets,
prev_weights,
),
no_motion_vectors: layouts.morphed(
&render_device,
&model,
weights,
targets,
),
},
};
groups.morph_targets.insert(id, bind_group_pair);
}
}
}
// Create lightmap bindgroups. There will be one bindgroup for each slab.
let bindless_supported = render_lightmaps.bindless_supported;
for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter_mut().enumerate() {
groups.lightmaps.insert(
LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()),
layouts.lightmapped(&render_device, &model, lightmap_slab, bindless_supported),
);
}
}
pub struct SetMeshViewBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I> {
type Param = ();
type ViewQuery = (
Read<ViewUniformOffset>,
Read<ViewLightsUniformOffset>,
Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>,
Read<ViewScreenSpaceReflectionsUniformOffset>,
Read<ViewEnvironmentMapUniformOffset>,
Read<MeshViewBindGroup>,
Option<Read<OrderIndependentTransparencySettingsOffset>>,
);
type ItemQuery = ();
#[inline]
fn render<'w>(
_item: &P,
(
view_uniform,
view_lights,
view_fog,
view_light_probes,
view_ssr,
view_environment_map,
mesh_view_bind_group,
maybe_oit_layers_count_offset,
): ROQueryItem<'w, Self::ViewQuery>,
_entity: Option<()>,
_: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let mut offsets: SmallVec<[u32; 8]> = smallvec![
view_uniform.offset,
view_lights.offset,
view_fog.offset,
**view_light_probes,
**view_ssr,
**view_environment_map,
];
if let Some(layers_count_offset) = maybe_oit_layers_count_offset {
offsets.push(layers_count_offset.offset);
}
pass.set_bind_group(I, &mesh_view_bind_group.value, &offsets);
RenderCommandResult::Success
}
}
pub struct SetMeshBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
type Param = (
SRes<RenderDevice>,
SRes<MeshBindGroups>,
SRes<RenderMeshInstances>,
SRes<SkinIndices>,
SRes<MorphIndices>,
SRes<RenderLightmaps>,
);
type ViewQuery = Has<MotionVectorPrepass>;
type ItemQuery = ();
#[inline]
fn render<'w>(
item: &P,
has_motion_vector_prepass: bool,
_item_query: Option<()>,
(
render_device,
bind_groups,
mesh_instances,
skin_indices,
morph_indices,
lightmaps,
): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let bind_groups = bind_groups.into_inner();
let mesh_instances = mesh_instances.into_inner();
let skin_indices = skin_indices.into_inner();
let morph_indices = morph_indices.into_inner();
let entity = &item.main_entity();
let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(*entity) else {
return RenderCommandResult::Success;
};
let current_skin_index = skin_indices.current.get(entity);
let prev_skin_index = skin_indices.prev.get(entity);
let current_morph_index = morph_indices.current.get(entity);
let prev_morph_index = morph_indices.prev.get(entity);
let is_skinned = current_skin_index.is_some();
let is_morphed = current_morph_index.is_some();
let lightmap_slab_index = lightmaps
.render_lightmaps
.get(entity)
.map(|render_lightmap| render_lightmap.slab_index);
let Some(bind_group) = bind_groups.get(
mesh_asset_id,
lightmap_slab_index,
is_skinned,
is_morphed,
has_motion_vector_prepass,
) else {
return RenderCommandResult::Failure(
"The MeshBindGroups resource wasn't set in the render phase. \
It should be set by the prepare_mesh_bind_group system.\n\
This is a bevy bug! Please open an issue.",
);
};
let mut dynamic_offsets: [u32; 3] = Default::default();
let mut offset_count = 0;
if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {
dynamic_offsets[offset_count] = dynamic_offset;
offset_count += 1;
}
if let Some(current_skin_index) = current_skin_index {
if skin::skins_use_uniform_buffers(&render_device) {
dynamic_offsets[offset_count] = current_skin_index.byte_offset;
offset_count += 1;
}
}
if let Some(current_morph_index) = current_morph_index {
dynamic_offsets[offset_count] = current_morph_index.index;
offset_count += 1;
}
// Attach motion vectors if needed.
if has_motion_vector_prepass {
// Attach the previous skin index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_skin_index.is_some() && skin::skins_use_uniform_buffers(&render_device) {
match prev_skin_index {
Some(prev_skin_index) => {
dynamic_offsets[offset_count] = prev_skin_index.byte_offset;
}
None => dynamic_offsets[offset_count] = 0,
}
offset_count += 1;
}
// Attach the previous morph index for motion vector computation. If
// there isn't one, just use zero as the shader will ignore it.
if current_morph_index.is_some() {
match prev_morph_index {
Some(prev_morph_index) => {
dynamic_offsets[offset_count] = prev_morph_index.index;
}
None => dynamic_offsets[offset_count] = 0,
}
offset_count += 1;
}
}
pass.set_bind_group(I, bind_group, &dynamic_offsets[0..offset_count]);
RenderCommandResult::Success
}
}
pub struct DrawMesh;
impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
type Param = (
SRes<RenderAssets<RenderMesh>>,
SRes<RenderMeshInstances>,
SRes<IndirectParametersBuffer>,
SRes<PipelineCache>,
SRes<MeshAllocator>,
Option<SRes<PreprocessPipelines>>,
);
type ViewQuery = Has<PreprocessBindGroup>;
type ItemQuery = ();
#[inline]
fn render<'w>(
item: &P,
has_preprocess_bind_group: ROQueryItem<Self::ViewQuery>,
_item_query: Option<()>,
(
meshes,
mesh_instances,
indirect_parameters_buffer,
pipeline_cache,
mesh_allocator,
preprocess_pipelines,
): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
// If we're using GPU preprocessing, then we're dependent on that
// compute shader having been run, which of course can only happen if
// it's compiled. Otherwise, our mesh instance data won't be present.
if let Some(preprocess_pipelines) = preprocess_pipelines {
if !has_preprocess_bind_group
|| !preprocess_pipelines.pipelines_are_loaded(&pipeline_cache)
{
return RenderCommandResult::Skip;
}
}
let meshes = meshes.into_inner();
let mesh_instances = mesh_instances.into_inner();
let indirect_parameters_buffer = indirect_parameters_buffer.into_inner();
let mesh_allocator = mesh_allocator.into_inner();
let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(item.main_entity()) else {
return RenderCommandResult::Skip;
};
let Some(gpu_mesh) = meshes.get(mesh_asset_id) else {
return RenderCommandResult::Skip;
};
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_asset_id) else {
return RenderCommandResult::Skip;
};
// Calculate the indirect offset, and look up the buffer.
let indirect_parameters = match item.extra_index() {
PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => None,
PhaseItemExtraIndex::IndirectParametersIndex(indices) => {
match indirect_parameters_buffer.buffer() {
None => {
warn!(
"Not rendering mesh because indirect parameters buffer wasn't present"
);
return RenderCommandResult::Skip;
}
Some(buffer) => Some((
indices.start as u64 * size_of::<IndirectParameters>() as u64,
indices.end - indices.start,
buffer,
)),
}
}
};
pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));
let batch_range = item.batch_range();
// Draw either directly or indirectly, as appropriate. If we're in
// indirect mode, we can additionally multi-draw. (We can't multi-draw
// in direct mode because `wgpu` doesn't expose that functionality.)
match &gpu_mesh.buffer_info {
RenderMeshBufferInfo::Indexed {
index_format,
count,
} => {
let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(&mesh_asset_id)
else {
return RenderCommandResult::Skip;
};
pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format);
match indirect_parameters {
None => {
pass.draw_indexed(
index_buffer_slice.range.start
..(index_buffer_slice.range.start + *count),
vertex_buffer_slice.range.start as i32,
batch_range.clone(),
);
}
Some((
indirect_parameters_offset,
indirect_parameters_count,
indirect_parameters_buffer,
)) => {
pass.multi_draw_indexed_indirect(
indirect_parameters_buffer,
indirect_parameters_offset,
indirect_parameters_count,
);
}
}
}
RenderMeshBufferInfo::NonIndexed => match indirect_parameters {
None => {
pass.draw(vertex_buffer_slice.range, batch_range.clone());
}
Some((
indirect_parameters_offset,
indirect_parameters_count,
indirect_parameters_buffer,
)) => {
pass.multi_draw_indirect(
indirect_parameters_buffer,
indirect_parameters_offset,
indirect_parameters_count,
);
}
},
}
RenderCommandResult::Success
}
}
#[cfg(test)]
mod tests {
use super::MeshPipelineKey;
#[test]
fn mesh_key_msaa_samples() {
for i in [1, 2, 4, 8, 16, 32, 64, 128] {
assert_eq!(MeshPipelineKey::from_msaa_samples(i).msaa_samples(), i);
}
}
}