Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5600,3 +5600,14 @@ name = "Light Probe Blending"
description = "Demonstrates blending between multiple reflection probes"
category = "3D Rendering"
wasm = false

[[example]]
name = "shader_material_2d_bindless"
path = "examples/shader/shader_material_2d_bindless.rs"
doc-scrape-examples = true

[package.metadata.example.shader_material_2d_bindless]
name = "Material - 2D Bindless"
description = "Demonstrates bindless materials in 2D"
category = "Shaders"
wasm = false
11 changes: 5 additions & 6 deletions crates/bevy_core_pipeline/src/core_2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ use bevy_render::{
ViewSortedRenderPhases,
},
render_resource::{
BindGroupId, CachedRenderPipelineId, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages,
CachedRenderPipelineId, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
},
renderer::RenderDevice,
sync_world::MainEntity,
Expand Down Expand Up @@ -112,7 +111,7 @@ pub struct Opaque2d {
}

/// Data that must be identical in order to batch phase items together.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Opaque2dBinKey {
/// The identifier of the render pipeline.
pub pipeline: CachedRenderPipelineId,
Expand All @@ -124,7 +123,7 @@ pub struct Opaque2dBinKey {
/// the ID of another type of asset.
pub asset_id: UntypedAssetId,
/// The ID of a bind group specific to the material.
pub material_bind_group_id: Option<BindGroupId>,
pub material_bind_group_index: Option<u32>,
}

impl PhaseItem for Opaque2d {
Expand Down Expand Up @@ -226,7 +225,7 @@ pub struct AlphaMask2d {
}

/// Data that must be identical in order to batch phase items together.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct AlphaMask2dBinKey {
/// The identifier of the render pipeline.
pub pipeline: CachedRenderPipelineId,
Expand All @@ -238,7 +237,7 @@ pub struct AlphaMask2dBinKey {
/// the ID of another type of asset.
pub asset_id: UntypedAssetId,
/// The ID of a bind group specific to the material.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the comment should refer to an index, not ID

pub material_bind_group_id: Option<BindGroupId>,
pub material_bind_group_index: Option<u32>,
}

impl PhaseItem for AlphaMask2d {
Expand Down
6 changes: 1 addition & 5 deletions crates/bevy_gizmos_render/src/pipeline_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ impl Plugin for LineGizmo2dPlugin {
GizmoRenderSystems::QueueLineGizmos2d
.in_set(RenderSystems::Queue)
.ambiguous_with(bevy_sprite_render::queue_sprites)
.ambiguous_with(
bevy_sprite_render::queue_material2d_meshes::<
bevy_sprite_render::ColorMaterial,
>,
),
.ambiguous_with(bevy_sprite_render::queue_material2d_meshes),
)
.add_systems(
RenderStartup,
Expand Down
18 changes: 15 additions & 3 deletions crates/bevy_mesh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub use mesh::*;
pub use mikktspace::*;
pub use primitives::*;
pub use vertex::*;
use wgpu_types::IndexFormat;
pub use wgpu_types::VertexFormat;

/// The mesh prelude.
Expand Down Expand Up @@ -87,14 +88,14 @@ impl BaseMeshPipelineKey {
/// For non-strip topologies, [`Self::STRIP_INDEX_FORMAT_NONE`] is set regardless of the `strip_index_format` argument.
pub fn from_primitive_topology_and_strip_index(
primitive_topology: PrimitiveTopology,
strip_index_format: Option<wgpu_types::IndexFormat>,
strip_index_format: Option<IndexFormat>,
) -> Self {
let index_bits = if primitive_topology.is_strip() {
match strip_index_format {
None => BaseMeshPipelineKey::STRIP_INDEX_FORMAT_NONE,
Some(index_format) => match index_format {
wgpu_types::IndexFormat::Uint16 => BaseMeshPipelineKey::STRIP_INDEX_FORMAT_U16,
wgpu_types::IndexFormat::Uint32 => BaseMeshPipelineKey::STRIP_INDEX_FORMAT_U32,
IndexFormat::Uint16 => BaseMeshPipelineKey::STRIP_INDEX_FORMAT_U16,
IndexFormat::Uint32 => BaseMeshPipelineKey::STRIP_INDEX_FORMAT_U32,
},
}
} else {
Expand All @@ -119,4 +120,15 @@ impl BaseMeshPipelineKey {
_ => PrimitiveTopology::default(),
}
}

/// Returns the [`IndexFormat`] that this mesh pipeline key specifies.
pub fn strip_index_format(&self) -> Option<IndexFormat> {
let index_bits = self.bits() & Self::STRIP_INDEX_FORMAT_RESERVED_BITS.bits();
match index_bits {
x if x == Self::STRIP_INDEX_FORMAT_U16.bits() => Some(IndexFormat::Uint16),
x if x == Self::STRIP_INDEX_FORMAT_U32.bits() => Some(IndexFormat::Uint32),
x if x == Self::STRIP_INDEX_FORMAT_NONE.bits() => None,
_ => unreachable!(),
}
}
}
6 changes: 4 additions & 2 deletions crates/bevy_pbr/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use bevy_app::{Plugin, PreUpdate};
use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
use bevy_ecs::{resource::Resource, system::Res};
use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use bevy_render::{Extract, ExtractSchedule, RenderApp};
use bevy_render::{
material_bind_groups::MaterialBindGroupAllocators, Extract, ExtractSchedule, RenderApp,
};

use crate::{Material, MaterialBindGroupAllocators};
use crate::Material;

pub struct MaterialAllocatorDiagnosticPlugin<M: Material> {
suffix: &'static str,
Expand Down
12 changes: 2 additions & 10 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ mod fog;
mod light_probe;
mod lightmap;
mod material;
mod material_bind_groups;
mod medium;
mod mesh_material;
mod parallax;
Expand All @@ -75,7 +74,6 @@ pub use fog::*;
pub use light_probe::*;
pub use lightmap::*;
pub use material::*;
pub use material_bind_groups::*;
pub use medium::*;
pub use mesh_material::*;
pub use parallax::*;
Expand Down Expand Up @@ -367,11 +365,7 @@ impl Plugin for PbrPlugin {
render_app
.add_systems(
RenderStartup,
(
init_shadow_samplers,
init_global_clusterable_object_meta,
init_fallback_bindless_resources,
),
(init_shadow_samplers, init_global_clusterable_object_meta),
)
.add_systems(
ExtractSchedule,
Expand Down Expand Up @@ -409,9 +403,7 @@ impl Plugin for PbrPlugin {
),
)
.init_gpu_resource::<LightMeta>()
.init_gpu_resource::<RenderMaterialBindings>()
.init_resource::<RenderShadowLodOrigin>()
.allow_ambiguous_resource::<RenderMaterialBindings>();
.init_resource::<RenderShadowLodOrigin>();

render_app.world_mut().add_observer(add_light_view_entities);
render_app
Expand Down
145 changes: 15 additions & 130 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use crate::material_bind_groups::{
FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
};
use crate::*;
use alloc::sync::Arc;
use bevy_asset::prelude::AssetChanged;
Expand Down Expand Up @@ -43,8 +40,12 @@ use bevy_render::camera::{DirtySpecializationSystems, DirtySpecializations, Pend
use bevy_render::erased_render_asset::{
ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError,
};
use bevy_render::material_bind_groups::{
material_uses_bindless_resources, MaterialBindGroupAllocators, MaterialBindingId,
RenderMaterialBindings,
};
use bevy_render::render_asset::{prepare_assets, RenderAssets};
use bevy_render::renderer::RenderQueue;
use bevy_render::view::RenderVisibleEntities;
use bevy_render::GpuResourceAppExt;
use bevy_render::RenderStartup;
use bevy_render::{
Expand All @@ -60,7 +61,6 @@ use bevy_render::{
Extract,
};
use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
use bevy_shader::{Shader, ShaderDefVal};
use bevy_utils::Parallel;
use core::{
Expand Down Expand Up @@ -371,8 +371,6 @@ impl Plugin for MaterialsPlugin {
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<RenderMaterialInstances>()
.allow_ambiguous_resource::<RenderMaterialInstances>()
.init_resource::<MaterialBindGroupAllocators>()
.allow_ambiguous_resource::<MaterialBindGroupAllocators>()
.init_gpu_resource::<PendingMeshMaterialQueues>()
.allow_ambiguous_resource::<PendingMeshMaterialQueues>()
.init_gpu_resource::<PendingShadowQueues>()
Expand All @@ -397,15 +395,6 @@ impl Plugin for MaterialsPlugin {
queue_material_meshes.in_set(RenderSystems::QueueMeshes),
),
)
.add_systems(
Render,
(
prepare_material_bind_groups,
write_material_bind_group_buffers,
)
.chain()
.in_set(RenderSystems::PrepareBindGroups),
)
.add_systems(
Render,
(
Expand Down Expand Up @@ -485,18 +474,7 @@ fn add_material_bind_group_allocator<M: Material>(
render_device: Res<RenderDevice>,
mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
) {
bind_group_allocators.insert(
TypeId::of::<M>(),
MaterialBindGroupAllocator::new(
&render_device,
M::label(),
material_uses_bindless_resources::<M>(&render_device)
.then(|| M::bindless_descriptor())
.flatten(),
M::bind_group_layout_descriptor(&render_device),
M::bindless_slot_count(),
),
);
bind_group_allocators.add::<M>(&render_device);
}

/// A key uniquely identifying a specialized [`MaterialPipeline`].
Expand Down Expand Up @@ -1496,14 +1474,6 @@ pub struct ShadowsDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct ShadowsDepthOnlyDrawFunction;

/// A resource that maps each untyped material ID to its binding.
///
/// This duplicates information in `RenderAssets<M>`, but it doesn't have the
/// `M` type parameter, so it can be used in untyped contexts like
/// [`crate::render::mesh::collect_meshes_for_gpu_building`].
#[derive(Resource, Default, Deref, DerefMut)]
pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);

/// Data prepared for a [`Material`] instance.
pub struct PreparedMaterial {
pub binding: MaterialBindingId,
Expand Down Expand Up @@ -1631,62 +1601,16 @@ where
): &mut SystemParamItem<Self::Param>,
) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
let material_layout = M::bind_group_layout_descriptor(render_device);
let actual_material_layout = pipeline_cache.get_bind_group_layout(&material_layout);

let binding = match material.unprepared_bind_group(
&actual_material_layout,
render_device,
let binding = render_material_bindings.prepare_material(
&material,
material_id,
material_param,
false,
) {
Ok(unprepared) => {
let bind_group_allocator =
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
// Allocate or update the material.
match render_material_bindings.entry(material_id.into()) {
Entry::Occupied(mut occupied_entry) => {
// TODO: Have a fast path that doesn't require
// recreating the bind group if only buffer contents
// change. For now, we just delete and recreate the bind
// group.
bind_group_allocator.free(*occupied_entry.get());
let new_binding =
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
*occupied_entry.get_mut() = new_binding;
new_binding
}
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
),
}
}
Err(AsBindGroupError::RetryNextUpdate) => {
return Err(PrepareAssetError::RetryNextUpdate(material))
}
Err(AsBindGroupError::CreateBindGroupDirectly) => {
match material.as_bind_group(
&material_layout,
render_device,
pipeline_cache,
material_param,
) {
Ok(prepared_bind_group) => {
let bind_group_allocator =
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
// Store the resulting bind group directly in the slot.
let material_binding_id =
bind_group_allocator.allocate_prepared(prepared_bind_group);
render_material_bindings.insert(material_id.into(), material_binding_id);
material_binding_id
}
Err(AsBindGroupError::RetryNextUpdate) => {
return Err(PrepareAssetError::RetryNextUpdate(material))
}
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
}
}
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
};
&material_layout,
bind_group_allocators,
render_device,
pipeline_cache,
)?;

let shadows_enabled = M::enable_shadows();
let prepass_enabled = M::enable_prepass();
Expand Down Expand Up @@ -1800,46 +1724,7 @@ where
Self::Param,
>,
) {
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
else {
return;
};
let bind_group_allocator = bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
bind_group_allocator.free(material_binding_id);
}
}

/// Creates and/or recreates any bind groups that contain materials that were
/// modified this frame.
pub fn prepare_material_bind_groups(
mut allocators: ResMut<MaterialBindGroupAllocators>,
render_device: Res<RenderDevice>,
pipeline_cache: Res<PipelineCache>,
fallback_image: Res<FallbackImage>,
fallback_resources: Res<FallbackBindlessResources>,
) {
for (_, allocator) in allocators.iter_mut() {
allocator.prepare_bind_groups(
&render_device,
&pipeline_cache,
&fallback_resources,
&fallback_image,
);
}
}

/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
/// manages to the GPU.
///
/// Non-bindless allocators don't currently manage any buffers, so this method
/// only has an effect for bindless allocators.
pub fn write_material_bind_group_buffers(
mut allocators: ResMut<MaterialBindGroupAllocators>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
for (_, allocator) in allocators.iter_mut() {
allocator.write_buffers(&render_device, &render_queue);
render_material_bindings.unload_material(source_asset, bind_group_allocators);
}
}

Expand Down
Loading
Loading