perf(render): skip redundant draw-order rebuilds
Docs Deploy / Build and Deploy MkDocs (push) Successful in 40s

This commit is contained in:
Valentin Popov committed 2026-10-19 23:05:50 +04:00
1 parent bf1f330379
commit e2b67a066e
1 file changed
+189 -4
+189 -4
View File
@@ -762,7 +762,9 @@ impl VulkanSmokeRenderer {
apply_geometry_range_updates( apply_geometry_range_updates(
&mut self.draw_ranges, &mut self.draw_ranges,
&mut self.draw_range_capacities, &mut self.draw_range_capacities,
&self.draw_range_world_queue,
geometry_ranges, geometry_ranges,
&mut self.draw_order_dirty,
); );
Ok(()) Ok(())
} }
@@ -882,6 +884,19 @@ impl VulkanSmokeRenderer {
context: "dynamic draw range index is out of bounds", context: "dynamic draw range index is out of bounds",
}, },
)?; )?;
let world_queue = self
.draw_range_world_queue
.get(range_index)
.copied()
.ok_or(VulkanSmokeRendererError::InvalidStaticMesh {
context: "dynamic draw range index is out of bounds",
})?;
mark_draw_order_dirty_on_activity_change(
world_queue,
range.index_count,
index_count,
&mut self.draw_order_dirty,
);
range.index_count = index_count; range.index_count = index_count;
Ok(()) Ok(())
} }
@@ -1017,8 +1032,11 @@ impl VulkanSmokeRenderer {
// Calling this setter opts the range into the world queue. Fixed // Calling this setter opts the range into the world queue. Fixed
// screen/sky/weather ranges remain `None` because their pass order is // screen/sky/weather ranges remain `None` because their pass order is
// intentionally owned by the caller. // intentionally owned by the caller.
self.draw_range_world_queue[range_index] = Some(transparent); set_and_mark_dirty_if_changed(
self.draw_order_dirty = true; &mut self.draw_range_world_queue[range_index],
Some(transparent),
&mut self.draw_order_dirty,
);
if pipeline_changed { if pipeline_changed {
self.rebuild_pipeline_resources()?; self.rebuild_pipeline_resources()?;
} }
@@ -1047,8 +1065,7 @@ impl VulkanSmokeRenderer {
context: "dynamic draw range index is out of bounds", context: "dynamic draw range index is out of bounds",
}, },
)?; )?;
*key = Some(sort_key); set_and_mark_dirty_if_changed(key, Some(sort_key), &mut self.draw_order_dirty);
self.draw_order_dirty = true;
Ok(()) Ok(())
} }
@@ -2300,13 +2317,21 @@ fn validate_geometry_upload(
fn apply_geometry_range_updates( fn apply_geometry_range_updates(
ranges: &mut [super::VulkanStaticDrawRange], ranges: &mut [super::VulkanStaticDrawRange],
capacities: &mut [u32], capacities: &mut [u32],
world_queues: &[Option<bool>],
geometry_ranges: &[super::VulkanDynamicDrawRange], geometry_ranges: &[super::VulkanDynamicDrawRange],
draw_order_dirty: &mut bool,
) { ) {
for geometry in geometry_ranges { for geometry in geometry_ranges {
// `validate_geometry_upload` has already checked every handle. Keep // `validate_geometry_upload` has already checked every handle. Keep
// this helper infallible so state is changed only after the Vulkan // this helper infallible so state is changed only after the Vulkan
// upload succeeds. // upload succeeds.
let range = &mut ranges[geometry.range_index]; let range = &mut ranges[geometry.range_index];
mark_draw_order_dirty_on_activity_change(
world_queues[geometry.range_index],
range.index_count,
geometry.index_count,
draw_order_dirty,
);
range.first_index = geometry.first_index; range.first_index = geometry.first_index;
range.index_count = geometry.index_count; range.index_count = geometry.index_count;
// A moved segment no longer owns the old tail. Keeping the previous // A moved segment no longer owns the old tail. Keeping the previous
@@ -2469,11 +2494,36 @@ fn sorted_world_draw_order(
next_without_transparency next_without_transparency
} }
fn set_and_mark_dirty_if_changed<T: PartialEq>(
current: &mut T,
next: T,
draw_order_dirty: &mut bool,
) {
if *current == next {
return;
}
*current = next;
*draw_order_dirty = true;
}
fn mark_draw_order_dirty_on_activity_change(
world_queue: Option<bool>,
current_index_count: u32,
next_index_count: u32,
draw_order_dirty: &mut bool,
) {
let active_changed = (current_index_count == 0) != (next_index_count == 0);
if world_queue.is_some() && active_changed {
*draw_order_dirty = true;
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::{VulkanDynamicDrawRange, VulkanStaticDrawRange, VulkanStaticMesh}; use super::super::{VulkanDynamicDrawRange, VulkanStaticDrawRange, VulkanStaticMesh};
use super::{ use super::{
apply_geometry_range_updates, apply_world_transparency, completed_readback_buffer_index, apply_geometry_range_updates, apply_world_transparency, completed_readback_buffer_index,
mark_draw_order_dirty_on_activity_change, set_and_mark_dirty_if_changed,
sorted_world_draw_order, take_runtime_children_with_validation_snapshot, sorted_world_draw_order, take_runtime_children_with_validation_snapshot,
take_runtime_owners_in_dependency_order, validate_geometry_upload, take_runtime_owners_in_dependency_order, validate_geometry_upload,
world_material_pipeline_states, RollbackOnDrop, world_material_pipeline_states, RollbackOnDrop,
@@ -2598,14 +2648,17 @@ mod tests {
]; ];
let original = ranges[0]; let original = ranges[0];
let mut capacities = vec![3, 3]; let mut capacities = vec![3, 3];
let mut draw_order_dirty = false;
apply_geometry_range_updates( apply_geometry_range_updates(
&mut ranges, &mut ranges,
&mut capacities, &mut capacities,
&[None, None],
&[VulkanDynamicDrawRange { &[VulkanDynamicDrawRange {
range_index: 1, range_index: 1,
first_index: 3, first_index: 3,
index_count: 9, index_count: 9,
}], }],
&mut draw_order_dirty,
); );
assert_eq!(ranges[0], original); assert_eq!(ranges[0], original);
assert_eq!(capacities, vec![3, 9]); assert_eq!(capacities, vec![3, 9]);
@@ -2615,11 +2668,13 @@ mod tests {
apply_geometry_range_updates( apply_geometry_range_updates(
&mut ranges, &mut ranges,
&mut capacities, &mut capacities,
&[None, None],
&[VulkanDynamicDrawRange { &[VulkanDynamicDrawRange {
range_index: 1, range_index: 1,
first_index: 3, first_index: 3,
index_count: 3, index_count: 3,
}], }],
&mut draw_order_dirty,
); );
assert_eq!(ranges[0], original); assert_eq!(ranges[0], original);
assert_eq!(capacities, vec![3, 3]); assert_eq!(capacities, vec![3, 3]);
@@ -2647,6 +2702,72 @@ mod tests {
); );
} }
#[test]
fn geometry_updates_dirty_world_order_on_activation_and_deactivation() {
let mut ranges = [VulkanStaticDrawRange {
first_index: 0,
index_count: 0,
material_index: 0,
lightmap_index: u8::MAX,
batch_flags: 0,
pipeline_state: LegacyPipelineState::default(),
terrain_base_only: false,
alpha_test_reference: 0,
}];
let mut capacities = [6];
let world_queues = [Some(true)];
let mut draw_order_dirty = false;
apply_geometry_range_updates(
&mut ranges,
&mut capacities,
&world_queues,
&[VulkanDynamicDrawRange {
range_index: 0,
first_index: 0,
index_count: 3,
}],
&mut draw_order_dirty,
);
assert!(
draw_order_dirty,
"activating the queued range changes order"
);
draw_order_dirty = false;
apply_geometry_range_updates(
&mut ranges,
&mut capacities,
&world_queues,
&[VulkanDynamicDrawRange {
range_index: 0,
first_index: 0,
index_count: 6,
}],
&mut draw_order_dirty,
);
assert!(
!draw_order_dirty,
"changing an active count does not change order"
);
apply_geometry_range_updates(
&mut ranges,
&mut capacities,
&world_queues,
&[VulkanDynamicDrawRange {
range_index: 0,
first_index: 0,
index_count: 0,
}],
&mut draw_order_dirty,
);
assert!(
draw_order_dirty,
"deactivating the queued range changes order"
);
}
#[test] #[test]
fn runtime_owners_drop_remaining_children_after_partial_init_failures() { fn runtime_owners_drop_remaining_children_after_partial_init_failures() {
let cases = [ let cases = [
@@ -2852,6 +2973,70 @@ mod tests {
assert_eq!(order, vec![2, 3, 1, 0]); assert_eq!(order, vec![2, 3, 1, 0]);
} }
#[test]
fn unchanged_sort_keys_and_transparency_do_not_dirty_draw_order() {
let mut sort_key = None;
let mut draw_order_dirty = false;
set_and_mark_dirty_if_changed(&mut sort_key, Some(3.0), &mut draw_order_dirty);
assert_eq!(sort_key, Some(3.0));
assert!(draw_order_dirty);
draw_order_dirty = false;
set_and_mark_dirty_if_changed(&mut sort_key, Some(3.0), &mut draw_order_dirty);
assert_eq!(sort_key, Some(3.0));
assert!(!draw_order_dirty);
set_and_mark_dirty_if_changed(&mut sort_key, Some(4.0), &mut draw_order_dirty);
assert_eq!(sort_key, Some(4.0));
assert!(draw_order_dirty);
let mut transparent = None;
draw_order_dirty = false;
set_and_mark_dirty_if_changed(&mut transparent, Some(true), &mut draw_order_dirty);
assert_eq!(transparent, Some(true));
assert!(draw_order_dirty);
draw_order_dirty = false;
set_and_mark_dirty_if_changed(&mut transparent, Some(true), &mut draw_order_dirty);
assert_eq!(transparent, Some(true));
assert!(!draw_order_dirty);
set_and_mark_dirty_if_changed(&mut transparent, Some(false), &mut draw_order_dirty);
assert_eq!(transparent, Some(false));
assert!(draw_order_dirty);
}
#[test]
fn world_draw_order_dirties_on_activation_and_deactivation() {
let mut draw_order_dirty = false;
mark_draw_order_dirty_on_activity_change(None, 0, 6, &mut draw_order_dirty);
assert!(
!draw_order_dirty,
"fixed pass slots keep their original order"
);
mark_draw_order_dirty_on_activity_change(Some(true), 0, 6, &mut draw_order_dirty);
assert!(
draw_order_dirty,
"activating a queued range changes its order"
);
draw_order_dirty = false;
mark_draw_order_dirty_on_activity_change(Some(false), 6, 12, &mut draw_order_dirty);
assert!(
!draw_order_dirty,
"changing an active count does not change order"
);
mark_draw_order_dirty_on_activity_change(Some(true), 6, 0, &mut draw_order_dirty);
assert!(
draw_order_dirty,
"deactivating a queued range changes its order"
);
}
#[test] #[test]
fn tree_canopy_queue_follows_sky_and_precedes_foreground_effects() { fn tree_canopy_queue_follows_sky_and_precedes_foreground_effects() {
const SCREEN_GRADIENT: usize = 0; const SCREEN_GRADIENT: usize = 0;