Skip to content

Commit 0ddebc9

Browse files
authored
fail fast in erased material preparation (#22581)
# Objective while writing the tests for #22557 i noticed that the perf was much worse for failed materials compared to older bevy versions pre #19667. this is probably because of the allocations in `ErasedMaterialKey::new` which was called regardless of whether the material was ready or not. ## Solution fail faster by checking bindgroup (the only fallible part of the material preparation) before doing any other work. ## Testing using the `gpu_transfer_limits` example from #22557, the time spent preparing failing materials drops by ~80% <img width="744" height="346" alt="image" src="https://github.com/user-attachments/assets/5123dac7-b90b-41a4-b139-2cc3890fc05b" />
1 parent b236cab commit 0ddebc9

File tree

1 file changed

+79
-109
lines changed

1 file changed

+79
-109
lines changed

crates/bevy_pbr/src/material.rs

Lines changed: 79 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,64 @@ where
15671567
material_param,
15681568
): &mut SystemParamItem<Self::Param>,
15691569
) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
1570+
let material_layout = M::bind_group_layout_descriptor(render_device);
1571+
let actual_material_layout = pipeline_cache.get_bind_group_layout(&material_layout);
1572+
1573+
let binding = match material.unprepared_bind_group(
1574+
&actual_material_layout,
1575+
render_device,
1576+
material_param,
1577+
false,
1578+
) {
1579+
Ok(unprepared) => {
1580+
let bind_group_allocator =
1581+
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1582+
// Allocate or update the material.
1583+
match render_material_bindings.entry(material_id.into()) {
1584+
Entry::Occupied(mut occupied_entry) => {
1585+
// TODO: Have a fast path that doesn't require
1586+
// recreating the bind group if only buffer contents
1587+
// change. For now, we just delete and recreate the bind
1588+
// group.
1589+
bind_group_allocator.free(*occupied_entry.get());
1590+
let new_binding =
1591+
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
1592+
*occupied_entry.get_mut() = new_binding;
1593+
new_binding
1594+
}
1595+
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
1596+
bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
1597+
),
1598+
}
1599+
}
1600+
Err(AsBindGroupError::RetryNextUpdate) => {
1601+
return Err(PrepareAssetError::RetryNextUpdate(material))
1602+
}
1603+
Err(AsBindGroupError::CreateBindGroupDirectly) => {
1604+
match material.as_bind_group(
1605+
&material_layout,
1606+
render_device,
1607+
pipeline_cache,
1608+
material_param,
1609+
) {
1610+
Ok(prepared_bind_group) => {
1611+
let bind_group_allocator =
1612+
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1613+
// Store the resulting bind group directly in the slot.
1614+
let material_binding_id =
1615+
bind_group_allocator.allocate_prepared(prepared_bind_group);
1616+
render_material_bindings.insert(material_id.into(), material_binding_id);
1617+
material_binding_id
1618+
}
1619+
Err(AsBindGroupError::RetryNextUpdate) => {
1620+
return Err(PrepareAssetError::RetryNextUpdate(material))
1621+
}
1622+
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
1623+
}
1624+
}
1625+
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
1626+
};
1627+
15701628
let shadows_enabled = M::enable_shadows();
15711629
let prepass_enabled = M::enable_prepass();
15721630

@@ -1673,115 +1731,27 @@ where
16731731
let bind_group_data = material.bind_group_data();
16741732
let material_key = ErasedMaterialKey::new(bind_group_data);
16751733

1676-
let material_layout = M::bind_group_layout_descriptor(render_device);
1677-
let actual_material_layout = pipeline_cache.get_bind_group_layout(&material_layout);
1678-
1679-
match material.unprepared_bind_group(
1680-
&actual_material_layout,
1681-
render_device,
1682-
material_param,
1683-
false,
1684-
) {
1685-
Ok(unprepared) => {
1686-
let bind_group_allocator =
1687-
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1688-
// Allocate or update the material.
1689-
let binding = match render_material_bindings.entry(material_id.into()) {
1690-
Entry::Occupied(mut occupied_entry) => {
1691-
// TODO: Have a fast path that doesn't require
1692-
// recreating the bind group if only buffer contents
1693-
// change. For now, we just delete and recreate the bind
1694-
// group.
1695-
bind_group_allocator.free(*occupied_entry.get());
1696-
let new_binding =
1697-
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
1698-
*occupied_entry.get_mut() = new_binding;
1699-
new_binding
1700-
}
1701-
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
1702-
bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
1703-
),
1704-
};
1705-
1706-
Ok(PreparedMaterial {
1707-
binding,
1708-
properties: Arc::new(MaterialProperties {
1709-
alpha_mode: material.alpha_mode(),
1710-
depth_bias: material.depth_bias(),
1711-
reads_view_transmission_texture,
1712-
render_phase_type,
1713-
render_method,
1714-
mesh_pipeline_key_bits,
1715-
material_layout: Some(material_layout),
1716-
draw_functions,
1717-
shaders,
1718-
bindless,
1719-
base_specialize: Some(base_specialize),
1720-
prepass_specialize: Some(prepass_specialize),
1721-
user_specialize: Some(user_specialize::<M>),
1722-
material_key,
1723-
shadows_enabled,
1724-
prepass_enabled,
1725-
}),
1726-
})
1727-
}
1728-
1729-
Err(AsBindGroupError::RetryNextUpdate) => {
1730-
Err(PrepareAssetError::RetryNextUpdate(material))
1731-
}
1732-
1733-
Err(AsBindGroupError::CreateBindGroupDirectly) => {
1734-
// This material has opted out of automatic bind group creation
1735-
// and is requesting a fully-custom bind group. Invoke
1736-
// `as_bind_group` as requested, and store the resulting bind
1737-
// group in the slot.
1738-
match material.as_bind_group(
1739-
&material_layout,
1740-
render_device,
1741-
pipeline_cache,
1742-
material_param,
1743-
) {
1744-
Ok(prepared_bind_group) => {
1745-
let bind_group_allocator =
1746-
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1747-
// Store the resulting bind group directly in the slot.
1748-
let material_binding_id =
1749-
bind_group_allocator.allocate_prepared(prepared_bind_group);
1750-
render_material_bindings.insert(material_id.into(), material_binding_id);
1751-
1752-
Ok(PreparedMaterial {
1753-
binding: material_binding_id,
1754-
properties: Arc::new(MaterialProperties {
1755-
alpha_mode: material.alpha_mode(),
1756-
depth_bias: material.depth_bias(),
1757-
reads_view_transmission_texture,
1758-
render_phase_type,
1759-
render_method,
1760-
mesh_pipeline_key_bits,
1761-
material_layout: Some(material_layout),
1762-
draw_functions,
1763-
shaders,
1764-
bindless,
1765-
base_specialize: Some(base_specialize),
1766-
prepass_specialize: Some(prepass_specialize),
1767-
user_specialize: Some(user_specialize::<M>),
1768-
material_key,
1769-
shadows_enabled,
1770-
prepass_enabled,
1771-
}),
1772-
})
1773-
}
1774-
1775-
Err(AsBindGroupError::RetryNextUpdate) => {
1776-
Err(PrepareAssetError::RetryNextUpdate(material))
1777-
}
1778-
1779-
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1780-
}
1781-
}
1782-
1783-
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1784-
}
1734+
Ok(PreparedMaterial {
1735+
binding,
1736+
properties: Arc::new(MaterialProperties {
1737+
alpha_mode: material.alpha_mode(),
1738+
depth_bias: material.depth_bias(),
1739+
reads_view_transmission_texture,
1740+
render_phase_type,
1741+
render_method,
1742+
mesh_pipeline_key_bits,
1743+
material_layout: Some(material_layout),
1744+
draw_functions,
1745+
shaders,
1746+
bindless,
1747+
base_specialize: Some(base_specialize),
1748+
prepass_specialize: Some(prepass_specialize),
1749+
user_specialize: Some(user_specialize::<M>),
1750+
material_key,
1751+
shadows_enabled,
1752+
prepass_enabled,
1753+
}),
1754+
})
17851755
}
17861756

17871757
fn unload_asset(

0 commit comments

Comments
 (0)