Use SmallVec instead of HashMap in MaterialProperties (#19846)

# Objective

- MaterialProperties uses HashMap for some data that is generally going
to be really small. This is likely using more memory than necessary

## Solution

- Use a SmallVec instead
- I used the size a StandardMaterial would need for all the backing
arrays

## Testing

- Tested the 3d_scene to confirm it still works

## Notes

I'm not sure if it made a measurable difference since I'm not sure how
to measure this. It's a bit hard to create an artificial workflow where
this would be the main bottleneck. This is very in the realm of
microoptimization.
This commit is contained in:
IceSentry 2025-06-28 14:43:56 -04:00 committed by GitHub
parent fb2bbb043c
commit 37bbbf753d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 31 deletions

View File

@ -1300,8 +1300,11 @@ pub struct MaterialProperties {
pub reads_view_transmission_texture: bool, pub reads_view_transmission_texture: bool,
pub render_phase_type: RenderPhaseType, pub render_phase_type: RenderPhaseType,
pub material_layout: Option<BindGroupLayout>, pub material_layout: Option<BindGroupLayout>,
pub draw_functions: HashMap<InternedDrawFunctionLabel, DrawFunctionId>, /// Backing array is a size of 4 because the `StandardMaterial` needs 4 draw functions by default
pub shaders: HashMap<InternedShaderLabel, Handle<Shader>>, pub draw_functions: SmallVec<[(InternedDrawFunctionLabel, DrawFunctionId); 4]>,
/// Backing array is a size of 3 because the `StandardMaterial` has 3 custom shaders (`frag`, `prepass_frag`, `deferred_frag`) which is the
/// most common use case
pub shaders: SmallVec<[(InternedShaderLabel, Handle<Shader>); 3]>,
/// Whether this material *actually* uses bindless resources, taking the /// Whether this material *actually* uses bindless resources, taking the
/// platform support (or lack thereof) of bindless resources into account. /// platform support (or lack thereof) of bindless resources into account.
pub bindless: bool, pub bindless: bool,
@ -1320,27 +1323,31 @@ pub struct MaterialProperties {
impl MaterialProperties { impl MaterialProperties {
pub fn get_shader(&self, label: impl ShaderLabel) -> Option<Handle<Shader>> { pub fn get_shader(&self, label: impl ShaderLabel) -> Option<Handle<Shader>> {
self.shaders.get(&label.intern()).cloned() self.shaders
.iter()
.find(|(inner_label, _)| inner_label == &label.intern())
.map(|(_, shader)| shader)
.cloned()
} }
pub fn add_shader( pub fn add_shader(&mut self, label: impl ShaderLabel, shader: Handle<Shader>) {
&mut self, self.shaders.push((label.intern(), shader));
label: impl ShaderLabel,
shader: Handle<Shader>,
) -> Option<Handle<Shader>> {
self.shaders.insert(label.intern(), shader)
} }
pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option<DrawFunctionId> { pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option<DrawFunctionId> {
self.draw_functions.get(&label.intern()).copied() self.draw_functions
.iter()
.find(|(inner_label, _)| inner_label == &label.intern())
.map(|(_, shader)| shader)
.cloned()
} }
pub fn add_draw_function( pub fn add_draw_function(
&mut self, &mut self,
label: impl DrawFunctionLabel, label: impl DrawFunctionLabel,
draw_function: DrawFunctionId, draw_function: DrawFunctionId,
) -> Option<DrawFunctionId> { ) {
self.draw_functions.insert(label.intern(), draw_function) self.draw_functions.push((label.intern(), draw_function));
} }
} }
@ -1472,19 +1479,19 @@ where
_ => None, _ => None,
}; };
let mut draw_functions = HashMap::new(); let mut draw_functions = SmallVec::new();
draw_functions.insert(MaterialDrawFunction.intern(), draw_function_id); draw_functions.push((MaterialDrawFunction.intern(), draw_function_id));
if let Some(prepass_draw_function_id) = prepass_draw_function_id { if let Some(prepass_draw_function_id) = prepass_draw_function_id {
draw_functions.insert(PrepassDrawFunction.intern(), prepass_draw_function_id); draw_functions.push((PrepassDrawFunction.intern(), prepass_draw_function_id));
} }
if let Some(deferred_draw_function_id) = deferred_draw_function_id { if let Some(deferred_draw_function_id) = deferred_draw_function_id {
draw_functions.insert(DeferredDrawFunction.intern(), deferred_draw_function_id); draw_functions.push((DeferredDrawFunction.intern(), deferred_draw_function_id));
} }
if let Some(shadow_draw_function_id) = shadow_draw_function_id { if let Some(shadow_draw_function_id) = shadow_draw_function_id {
draw_functions.insert(ShadowsDrawFunction.intern(), shadow_draw_function_id); draw_functions.push((ShadowsDrawFunction.intern(), shadow_draw_function_id));
} }
let mut shaders = HashMap::new(); let mut shaders = SmallVec::new();
let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| { let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| {
let mayber_shader = match shader_ref { let mayber_shader = match shader_ref {
ShaderRef::Default => None, ShaderRef::Default => None,
@ -1492,7 +1499,7 @@ where
ShaderRef::Path(path) => Some(asset_server.load(path)), ShaderRef::Path(path) => Some(asset_server.load(path)),
}; };
if let Some(shader) = mayber_shader { if let Some(shader) = mayber_shader {
shaders.insert(label, shader); shaders.push((label, shader));
} }
}; };
add_shader(MaterialVertexShader.intern(), M::vertex_shader()); add_shader(MaterialVertexShader.intern(), M::vertex_shader());

View File

@ -553,27 +553,18 @@ impl PrepassPipelineInternal {
|| emulate_unclipped_depth || emulate_unclipped_depth
|| (mesh_key.contains(MeshPipelineKey::MAY_DISCARD) || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
&& material_properties && material_properties
.shaders .get_shader(PrepassFragmentShader)
.get(&PrepassFragmentShader.intern())
.is_some()); .is_some());
let fragment = fragment_required.then(|| { let fragment = fragment_required.then(|| {
// Use the fragment shader from the material // Use the fragment shader from the material
let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
match material_properties match material_properties.get_shader(DeferredFragmentShader) {
.shaders
.get(&DeferredFragmentShader.intern())
.cloned()
{
Some(frag_shader_handle) => frag_shader_handle, Some(frag_shader_handle) => frag_shader_handle,
None => self.default_prepass_shader.clone(), None => self.default_prepass_shader.clone(),
} }
} else { } else {
match material_properties match material_properties.get_shader(PrepassFragmentShader) {
.shaders
.get(&PrepassFragmentShader.intern())
.cloned()
{
Some(frag_shader_handle) => frag_shader_handle, Some(frag_shader_handle) => frag_shader_handle,
None => self.default_prepass_shader.clone(), None => self.default_prepass_shader.clone(),
} }