-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
shadow performance #11647
base: main
Are you sure you want to change the base?
shadow performance #11647
Conversation
note this also fixes the issues from #11645 but is maybe more contentious |
modified to sort shadows by mesh id, since we only batch entities with the same mesh (we still sort by pipeline first) |
This is saving ~7000 draw calls in my castle stress test view (total went from ~19.8k -> ~12.6k, which is still insane, but less insane). I wonder if sorting the opaque pass by mesh id would net us a similar decrease? |
I guess that would depend on frag shader cost (more overdraw / less drawcalls or vice versa) … might be nice to make it an option somehow. |
@@ -801,6 +801,11 @@ impl Material for StandardMaterial { | |||
PBR_SHADER_HANDLE.into() | |||
} | |||
|
|||
fn shadow_material_key(&self) -> Option<u64> { | |||
// we can batch all pure opaque materials together for shadow rendering |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it just meshes that break our batching for shadows then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for standard materials after this pr, yes. part of this pr is to make all opaque standard materials share a material instance for shadow casting
commands.spawn(DirectionalLightBundle { ..default() }); | ||
commands.spawn(DirectionalLightBundle { | ||
directional_light: DirectionalLight { | ||
shadows_enabled: args.shadows, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this default to shadows on or off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaults to off
/// the same depth-prepass output (such as opaque [`StandardMaterial`]s with different textures), rendering with the first | ||
/// material of the batch for all meshes. | ||
fn shadow_material_key(&self) -> Option<u64> { | ||
None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why is this None
by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is in the Material trait. it's overridden in the StandardMaterial impl
@@ -1635,7 +1636,7 @@ pub fn queue_shadows<M: Material>( | |||
// NOTE: Lights with shadow mapping disabled will have no visible entities | |||
// so no meshes will be queued | |||
for entity in visible_entities.iter().copied() { | |||
let Some(mesh_instance) = render_mesh_instances.get(&entity) else { | |||
let Some(mesh_instance) = render_mesh_instances.get_mut(&entity) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look like it needs to be mut
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's modified @1689 to add the shadow_bind_group_id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, not sure how I missed that.
@@ -1699,7 +1702,7 @@ pub fn queue_shadows<M: Material>( | |||
} | |||
|
|||
pub struct Shadow { | |||
pub distance: f32, | |||
pub asset_id: AssetId<Mesh>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future we probably want to be able to represent AssetID
as a u64 or something so we can go back to using radsort, as I noticed decreases in sort performance in the opaque sorting by pipeline PR (that were more than made up for by the better batching).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely, should be easy to do.
entity: Entity, | ||
) -> Option<(Self::BufferData, Option<Self::CompareData>)> { | ||
let mesh_instance = mesh_instances.get(&entity)?; | ||
let maybe_lightmap = lightmaps.render_lightmaps.get(&entity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be any issues with removing lightmaps in CompareData
for shadow pass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For standard materials, no… in general, possibly I guess? But probably not.
If it did matter for a custom material then the author could add some data into the material and use it to differentiate in the shadow_material_key fn. I’ll remove it from here.
/// Specify a batch key to use for shadows. Returning Some(value) allows batching of distinct materials which will produce | ||
/// the same depth-prepass output (such as opaque [`StandardMaterial`]s with different textures), rendering with the first | ||
/// material of the batch for all meshes. | ||
fn shadow_material_key(&self) -> Option<u64> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels pretty hacky :/. I'd rather we just have a separate set of bindings for shadow passes, or split bindings between vertex/fragment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered how to do this without requiring large user changes, I couldn’t see a way.
Ideally you want this to work for standard materials without extra user input, so
- a separate component (
ShadowMaterial(Handle<M>)
) doesn't really work unless we also add a system to add the component automatically where appropriate, and that system would need to do lookups into the assets resource to make the determination, which sounds expensive and still messy. - have the key function return a handle/asset id, this requires either adding the handle(s) to every material or using some resource to store them and giving access in the key function. This gets at least as awkward, particularly if you have more than one groupable set (I have this case in my work code where some opaque materials can be grouped but not all, based on the “plot” they belong to in the world). Also adds effort around extracting/preparing the new handles, basically seems like a lot of extra complexity for limited reward.
If you can see a clean way to do this I’d be happy to change it, but I don’t see it right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose for now this is the best we can do. I think for 0.14 we might want to look into redoing the Material API so we can support batching, bindless, etc easier.
@@ -180,6 +180,13 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { | |||
) -> Result<(), SpecializedMeshPipelineError> { | |||
Ok(()) | |||
} | |||
|
|||
/// Specify a batch key to use for shadows. Returning Some(value) allows batching of distinct materials which will produce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wording isn't very clear. It took me some time to realize that this would basically force-batch different materials together by grouping them by this arbitrary value, and then using the first material of the batch for all meshes in the batch.
This is nice but not vital: bumping from the milestone. |
Objective
some minor optimisations for shadow rendering performance
Solution
--shadows
option to many_cubes for testingStandardMaterial
s). the material must support the batching viaMaterial::shadow_material_key
the base material's keyno batching by default.i am not sure if this is the right thing to do, it will cause some existingExtendedMaterial
s to render incorrectly (e.g. if they move vertices based on material data). the alternative is to by default disable shadow batching for extended materials (it could still be enabled by authors)perf impacts:
when sharing a material handle there is no impact as expected. when using distinct materials the frame rate increases ~5x.
this is obviously an exaggeration of real world impacts, but on a random real world scene i had
a second test scene (thanks @Elabajaba) showed a smaller change from 56fps to 60fps.