1use std::{
5 any::TypeId,
6 collections::{BinaryHeap, HashMap},
7};
8
9use petgraph::{
10 graph::{DiGraph, NodeIndex},
11 visit::IntoNodeIdentifiers,
12};
13
14use crate::{
15 px::{Px, PxPosition, PxRect, PxSize},
16 renderer::command::{BarrierRequirement, Command},
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub(crate) enum InstructionCategory {
23 ContinuationDraw,
25 BarrierDraw,
27 Compute,
29 StateChange,
31}
32
33pub(crate) struct InstructionInfo {
35 pub(crate) original_index: usize,
36 pub(crate) command: Command,
37 pub(crate) type_id: TypeId,
38 pub(crate) size: PxSize,
39 pub(crate) position: PxPosition,
40 pub(crate) category: InstructionCategory,
41 pub(crate) rect: PxRect,
42}
43
44impl InstructionInfo {
45 pub(crate) fn new(
49 (command, type_id, size, position): (Command, TypeId, PxSize, PxPosition),
50 original_index: usize,
51 ) -> Self {
52 let (category, rect) = match &command {
53 Command::Compute(command) => {
54 let barrier_req = command.barrier();
57 let rect = match barrier_req {
58 BarrierRequirement::Global => PxRect {
59 x: Px(0),
60 y: Px(0),
61 width: Px(i32::MAX),
62 height: Px(i32::MAX),
63 },
64 BarrierRequirement::PaddedLocal(_) => component_dependency_rect(position, size),
65 BarrierRequirement::Absolute(rect) => rect,
66 };
67 (InstructionCategory::Compute, rect)
68 }
69 Command::Draw(draw_command) => {
70 let barrier = draw_command.barrier();
71 let category = if barrier.is_some() {
72 InstructionCategory::BarrierDraw
73 } else {
74 InstructionCategory::ContinuationDraw
75 };
76
77 let rect = match barrier {
78 Some(BarrierRequirement::Global) => PxRect {
79 x: Px(0),
80 y: Px(0),
81 width: Px(i32::MAX),
82 height: Px(i32::MAX),
83 },
84 Some(BarrierRequirement::PaddedLocal(_)) => {
85 component_dependency_rect(position, size)
86 }
87 Some(BarrierRequirement::Absolute(rect)) => rect,
88 None => PxRect {
89 x: position.x,
90 y: position.y,
91 width: size.width,
92 height: size.height,
93 },
94 };
95 (category, rect)
96 }
97 Command::ClipPush(rect) => (InstructionCategory::StateChange, *rect),
98 Command::ClipPop => (
99 InstructionCategory::StateChange,
100 PxRect {
101 x: position.x,
102 y: position.y,
103 width: Px::ZERO,
104 height: Px::ZERO,
105 },
106 ),
107 };
108
109 Self {
110 original_index,
111 command,
112 type_id,
113 size,
114 position,
115 category,
116 rect,
117 }
118 }
119}
120
121fn component_dependency_rect(position: PxPosition, size: PxSize) -> PxRect {
122 PxRect {
123 x: position.x.max(Px::ZERO),
124 y: position.y.max(Px::ZERO),
125 width: size.width,
126 height: size.height,
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132struct PriorityNode {
133 category: InstructionCategory,
134 type_id: TypeId,
135 original_index: usize,
136 node_index: NodeIndex,
137 batch_potential: usize,
138}
139
140impl Ord for PriorityNode {
141 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
142 self.category
149 .cmp(&other.category)
150 .then_with(|| other.batch_potential.cmp(&self.batch_potential))
151 .then_with(|| other.original_index.cmp(&self.original_index))
152 }
153}
154
155impl PartialOrd for PriorityNode {
156 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
157 Some(self.cmp(other))
158 }
159}
160
161pub(crate) fn reorder_instructions(
162 commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
163) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
164 let instructions: Vec<InstructionInfo> = commands
165 .into_iter()
166 .enumerate()
167 .map(|(i, cmd)| InstructionInfo::new(cmd, i))
168 .collect();
169
170 if instructions.is_empty() {
171 return vec![];
172 }
173
174 let mut potentials = HashMap::new();
175 for info in &instructions {
176 *potentials.entry((info.category, info.type_id)).or_insert(0) += 1;
177 }
178
179 let graph = build_dependency_graph(&instructions);
180
181 let sorted_node_indices = priority_topological_sort(&graph, &instructions, &potentials);
182
183 let mut sorted_instructions = Vec::with_capacity(instructions.len());
184 let mut original_infos: Vec<_> = instructions.into_iter().map(Some).collect();
185
186 for node_index in sorted_node_indices {
187 let original_index = node_index.index();
188 if let Some(info) = original_infos[original_index].take() {
189 sorted_instructions.push((info.command, info.type_id, info.size, info.position));
190 }
191 }
192
193 sorted_instructions
194}
195
196fn priority_topological_sort(
197 graph: &DiGraph<(), ()>,
198 instructions: &[InstructionInfo],
199 potentials: &HashMap<(InstructionCategory, TypeId), usize>,
200) -> Vec<NodeIndex> {
201 let mut in_degree = vec![0; graph.node_count()];
202 for edge in graph.raw_edges() {
203 in_degree[edge.target().index()] += 1;
204 }
205
206 let mut ready_queue = BinaryHeap::new();
207 for node_index in graph.node_identifiers() {
208 if in_degree[node_index.index()] == 0 {
209 let info = &instructions[node_index.index()];
210 ready_queue.push(PriorityNode {
211 category: info.category,
212 type_id: info.type_id,
213 original_index: info.original_index,
214 node_index,
215 batch_potential: potentials[&(info.category, info.type_id)],
216 });
217 }
218 }
219
220 let mut sorted_list = Vec::with_capacity(instructions.len());
221 let mut last_type_id: Option<TypeId> = None;
222 while !ready_queue.is_empty() {
223 let highest_category = ready_queue.peek().map(|node| node.category);
224
225 let mut selected: Option<PriorityNode> = None;
226 if let (Some(last_type), Some(high_cat)) = (last_type_id, highest_category) {
227 let mut deferred = Vec::new();
228 while let Some(node) = ready_queue.pop() {
229 if node.category == high_cat && node.type_id == last_type {
230 selected = Some(node);
231 break;
232 }
233 deferred.push(node);
234 }
235 for node in deferred {
236 ready_queue.push(node);
237 }
238 }
239
240 let priority_node = selected.unwrap_or_else(|| {
241 ready_queue
242 .pop()
243 .expect("ready_queue should not be empty while sorting instructions")
244 });
245 let u = priority_node.node_index;
246 sorted_list.push(u);
247 match priority_node.category {
248 InstructionCategory::StateChange => last_type_id = None,
249 _ => last_type_id = Some(priority_node.type_id),
250 }
251
252 for v in graph.neighbors(u) {
253 in_degree[v.index()] -= 1;
254 if in_degree[v.index()] == 0 {
255 let info = &instructions[v.index()];
256 ready_queue.push(PriorityNode {
257 category: info.category,
258 type_id: info.type_id,
259 original_index: info.original_index,
260 node_index: v,
261 batch_potential: potentials[&(info.category, info.type_id)],
262 });
263 }
264 }
265 }
266
267 if sorted_list.len() != instructions.len() {
268 return (0..instructions.len()).map(NodeIndex::new).collect();
272 }
273
274 sorted_list
275}
276
277fn build_dependency_graph(instructions: &[InstructionInfo]) -> DiGraph<(), ()> {
278 let mut graph = DiGraph::new();
279 let node_indices: Vec<NodeIndex> = instructions.iter().map(|_| graph.add_node(())).collect();
280
281 for i in 0..instructions.len() {
282 for j in 0..instructions.len() {
283 if i == j {
284 continue;
285 }
286
287 let inst_i = &instructions[i];
288 let inst_j = &instructions[j];
289
290 if inst_i.original_index < inst_j.original_index
293 && (inst_i.category == InstructionCategory::StateChange
294 || inst_j.category == InstructionCategory::StateChange)
295 {
296 graph.add_edge(node_indices[i], node_indices[j], ());
297 }
298
299 if inst_i.category == InstructionCategory::Compute
303 && inst_j.category == InstructionCategory::BarrierDraw
304 && inst_i.original_index < inst_j.original_index
305 {
306 graph.add_edge(node_indices[i], node_indices[j], ());
307 }
308
309 if (inst_i.category == InstructionCategory::BarrierDraw
314 || inst_i.category == InstructionCategory::ContinuationDraw)
315 && (inst_j.category == InstructionCategory::BarrierDraw
316 || inst_j.category == InstructionCategory::ContinuationDraw)
317 && inst_i.original_index < inst_j.original_index
318 && !inst_i.rect.is_orthogonal(&inst_j.rect)
319 {
320 graph.add_edge(node_indices[i], node_indices[j], ());
321 }
322
323 if (inst_i.category == InstructionCategory::BarrierDraw
327 || inst_i.category == InstructionCategory::ContinuationDraw)
328 && inst_j.category == InstructionCategory::Compute
329 && inst_i.original_index < inst_j.original_index
330 && !inst_i.rect.is_orthogonal(&inst_j.rect)
331 {
332 graph.add_edge(node_indices[i], node_indices[j], ());
333 }
334 }
335 }
336
337 graph
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::{
344 px::{Px, PxPosition, PxRect, PxSize},
345 renderer::{
346 BarrierRequirement, command::Command, compute::ComputeCommand, drawer::DrawCommand,
347 },
348 };
349 use std::any::TypeId;
350 use std::fmt::Debug;
351
352 #[derive(Debug, PartialEq, Clone)]
353 struct MockDrawCommand {
354 barrier_req: Option<BarrierRequirement>,
355 }
356
357 impl DrawCommand for MockDrawCommand {
358 fn barrier(&self) -> Option<BarrierRequirement> {
359 self.barrier_req
360 }
361 }
362
363 #[derive(Debug, PartialEq, Clone)]
364 struct MockDrawCommand2 {
365 barrier_req: Option<BarrierRequirement>,
366 }
367
368 impl DrawCommand for MockDrawCommand2 {
369 fn barrier(&self) -> Option<BarrierRequirement> {
370 self.barrier_req
371 }
372 }
373
374 #[derive(Debug, PartialEq, Clone)]
375 struct MockComputeCommand {
376 barrier_req: BarrierRequirement,
377 }
378
379 impl ComputeCommand for MockComputeCommand {
380 fn barrier(&self) -> BarrierRequirement {
381 self.barrier_req
382 }
383 }
384
385 #[derive(Debug, PartialEq, Clone)]
386 struct MockComputeCommand2 {
387 barrier_req: BarrierRequirement,
388 }
389
390 impl ComputeCommand for MockComputeCommand2 {
391 fn barrier(&self) -> BarrierRequirement {
392 self.barrier_req
393 }
394 }
395
396 fn create_cmd(
399 pos: PxPosition,
400 barrier_req: Option<BarrierRequirement>,
401 is_compute: bool,
402 ) -> (Command, TypeId, PxSize, PxPosition) {
403 let size = PxSize::new(Px(10), Px(10));
404 if is_compute {
405 let cmd = MockComputeCommand {
406 barrier_req: barrier_req.unwrap_or(BarrierRequirement::Global),
407 };
408 (
409 Command::Compute(Box::new(cmd)),
410 TypeId::of::<MockComputeCommand>(),
411 size,
412 pos,
413 )
414 } else {
415 let cmd = MockDrawCommand { barrier_req };
416 (
417 Command::Draw(Box::new(cmd)),
418 TypeId::of::<MockDrawCommand>(),
419 size,
420 pos,
421 )
422 }
423 }
424
425 fn create_cmd2(
426 pos: PxPosition,
427 barrier_req: Option<BarrierRequirement>,
428 is_compute: bool,
429 ) -> (Command, TypeId, PxSize, PxPosition) {
430 let size = PxSize::new(Px(10), Px(10));
431 if is_compute {
432 let cmd = MockComputeCommand2 {
433 barrier_req: barrier_req.unwrap_or(BarrierRequirement::Global),
434 };
435 (
436 Command::Compute(Box::new(cmd)),
437 TypeId::of::<MockComputeCommand2>(),
438 size,
439 pos,
440 )
441 } else {
442 let cmd = MockDrawCommand2 { barrier_req };
443 (
444 Command::Draw(Box::new(cmd)),
445 TypeId::of::<MockDrawCommand2>(),
446 size,
447 pos,
448 )
449 }
450 }
451
452 fn get_positions(commands: &[(Command, TypeId, PxSize, PxPosition)]) -> Vec<PxPosition> {
453 commands.iter().map(|(_, _, _, pos)| *pos).collect()
454 }
455
456 #[test]
457 fn test_empty_instructions() {
458 let commands = vec![];
459 let reordered = reorder_instructions(commands);
460 assert!(reordered.is_empty());
461 }
462
463 #[test]
464 fn test_no_dependencies_preserves_order() {
465 let commands = vec![
466 create_cmd(PxPosition::new(Px(0), Px(0)), None, false), create_cmd(PxPosition::new(Px(20), Px(0)), None, false), ];
469 let original_positions = get_positions(&commands);
470 let reordered = reorder_instructions(commands);
471 let reordered_positions = get_positions(&reordered);
472 assert_eq!(reordered_positions, original_positions);
473 }
474
475 #[test]
476 fn test_compute_before_barrier_preserves_order() {
477 let commands = vec![
478 create_cmd(
479 PxPosition::new(Px(0), Px(0)),
480 Some(BarrierRequirement::Global),
481 true,
482 ), create_cmd(
484 PxPosition::new(Px(20), Px(20)),
485 Some(BarrierRequirement::Global),
486 false,
487 ), ];
489 let original_positions = get_positions(&commands);
490 let reordered = reorder_instructions(commands);
491 let reordered_positions = get_positions(&reordered);
492 assert_eq!(reordered_positions, original_positions);
493 }
494
495 #[test]
496 fn test_opt() {
497 let commands = vec![
499 create_cmd(PxPosition::new(Px(0), Px(0)), None, false), create_cmd2(PxPosition::new(Px(10), Px(10)), None, false), create_cmd(PxPosition::new(Px(20), Px(20)), None, false), ];
503 let reordered = reorder_instructions(commands);
504 let reordered_positions = get_positions(&reordered);
505
506 let expected_positions = vec![
510 PxPosition::new(Px(10), Px(10)), PxPosition::new(Px(0), Px(0)), PxPosition::new(Px(20), Px(20)), ];
514 assert_eq!(reordered_positions, expected_positions);
515
516 let commands = vec![
518 create_cmd(PxPosition::new(Px(0), Px(0)), None, false), create_cmd2(PxPosition::new(Px(10), Px(10)), None, false), create_cmd(PxPosition::new(Px(5), Px(5)), None, false), ];
522 let reordered = reorder_instructions(commands);
523 let reordered_positions = get_positions(&reordered);
524
525 let expected_positions = vec![
531 PxPosition::new(Px(10), Px(10)), PxPosition::new(Px(0), Px(0)), PxPosition::new(Px(5), Px(5)), ];
535 assert_eq!(expected_positions, reordered_positions);
536 }
537
538 #[test]
539 fn compute_draw_pairs_remain_grouped_with_local_barrier() {
540 let padding = BarrierRequirement::uniform_padding_local(Px::new(10));
541
542 let commands = vec![
543 create_cmd(PxPosition::new(Px(0), Px(0)), Some(padding), true),
544 create_cmd(PxPosition::new(Px(0), Px(0)), Some(padding), false),
545 create_cmd(PxPosition::new(Px(200), Px(0)), Some(padding), true),
546 create_cmd(PxPosition::new(Px(200), Px(0)), Some(padding), false),
547 ];
548
549 let reordered = reorder_instructions(commands);
550 let kinds: Vec<&'static str> = reordered
551 .iter()
552 .map(|(cmd, _, _, _)| match cmd {
553 Command::Compute(_) => "C",
554 Command::Draw(_) => "D",
555 _ => panic!("unexpected command variant"),
556 })
557 .collect();
558
559 assert_eq!(kinds, vec!["C", "C", "D", "D"]);
560 }
561
562 #[test]
563 fn test_overlapping_draw_preserves_order() {
564 let commands = vec![
565 create_cmd(PxPosition::new(Px(0), Px(0)), None, false), create_cmd(PxPosition::new(Px(5), Px(5)), None, false), ];
568 let original_positions = get_positions(&commands);
569 let reordered = reorder_instructions(commands);
570 let reordered_positions = get_positions(&reordered);
571 assert_eq!(reordered_positions, original_positions);
572 }
573
574 #[test]
575 fn test_draw_before_overlapping_compute_preserves_order() {
576 let commands = vec![
577 create_cmd(
578 PxPosition::new(Px(0), Px(0)),
579 Some(BarrierRequirement::Global),
580 false,
581 ), create_cmd(
583 PxPosition::new(Px(20), Px(20)),
584 Some(BarrierRequirement::Global),
585 true,
586 ), ];
588 let original_positions = get_positions(&commands);
589 let reordered = reorder_instructions(commands);
590 let reordered_positions = get_positions(&reordered);
591 assert_eq!(reordered_positions, original_positions);
592 }
593
594 #[test]
595 fn test_clip_state_change_acts_as_fence() {
596 let clip_rect = PxRect::new(Px(0), Px(0), Px(50), Px(50));
597 let clip_size = PxSize::new(Px(50), Px(50));
598 let commands = vec![
599 (
600 Command::ClipPush(clip_rect),
601 TypeId::of::<Command>(),
602 clip_size,
603 PxPosition::new(Px(0), Px(0)),
604 ),
605 create_cmd(
606 PxPosition::new(Px(5), Px(5)),
607 Some(BarrierRequirement::Global),
608 false,
609 ),
610 (
611 Command::ClipPop,
612 TypeId::of::<Command>(),
613 clip_size,
614 PxPosition::new(Px(0), Px(0)),
615 ),
616 create_cmd2(
617 PxPosition::new(Px(60), Px(60)),
618 Some(BarrierRequirement::Global),
619 false,
620 ),
621 ];
622 let original_positions = get_positions(&commands);
623 let reordered = reorder_instructions(commands);
624 let reordered_positions = get_positions(&reordered);
625 assert_eq!(reordered_positions, original_positions);
626 }
627
628 #[test]
629 fn test_padded_local_overlap_prevents_reorder() {
630 let commands = vec![
631 create_cmd(
632 PxPosition::new(Px(100), Px(100)),
633 Some(BarrierRequirement::uniform_padding_local(Px(30))),
634 false,
635 ),
636 create_cmd(
637 PxPosition::new(Px(70), Px(70)),
638 Some(BarrierRequirement::Global),
639 false,
640 ),
641 ];
642 let original_positions = get_positions(&commands);
643 let reordered = reorder_instructions(commands);
644 let reordered_positions = get_positions(&reordered);
645 assert_eq!(reordered_positions, original_positions);
646 }
647
648 #[test]
649 fn test_mixed_categories_with_state_fences() {
650 let clip_rect = PxRect::new(Px(0), Px(0), Px(400), Px(400));
651 let clip_size = PxSize::new(Px(400), Px(400));
652 let commands = vec![
653 (
654 Command::ClipPush(clip_rect),
655 TypeId::of::<Command>(),
656 clip_size,
657 PxPosition::ZERO,
658 ),
659 create_cmd(
660 PxPosition::new(Px(10), Px(10)),
661 Some(BarrierRequirement::Absolute(PxRect::new(
662 Px(10),
663 Px(10),
664 Px(20),
665 Px(20),
666 ))),
667 false,
668 ),
669 create_cmd2(PxPosition::new(Px(220), Px(20)), None, false),
670 create_cmd(
671 PxPosition::new(Px(150), Px(150)),
672 Some(BarrierRequirement::Absolute(PxRect::new(
673 Px(150),
674 Px(150),
675 Px(30),
676 Px(30),
677 ))),
678 true,
679 ),
680 create_cmd2(
681 PxPosition::new(Px(155), Px(155)),
682 Some(BarrierRequirement::Absolute(PxRect::new(
683 Px(150),
684 Px(150),
685 Px(30),
686 Px(30),
687 ))),
688 false,
689 ),
690 create_cmd(PxPosition::new(Px(260), Px(30)), None, false),
691 create_cmd2(
692 PxPosition::new(Px(300), Px(60)),
693 Some(BarrierRequirement::Absolute(PxRect::new(
694 Px(300),
695 Px(60),
696 Px(25),
697 Px(25),
698 ))),
699 true,
700 ),
701 (
702 Command::ClipPop,
703 TypeId::of::<Command>(),
704 clip_size,
705 PxPosition::ZERO,
706 ),
707 ];
708
709 let reordered = reorder_instructions(commands.clone());
710 let reordered_positions = get_positions(&reordered);
711 let expected_positions = vec![
712 PxPosition::ZERO,
713 PxPosition::new(Px(150), Px(150)),
714 PxPosition::new(Px(300), Px(60)),
715 PxPosition::new(Px(10), Px(10)),
716 PxPosition::new(Px(155), Px(155)),
717 PxPosition::new(Px(220), Px(20)),
718 PxPosition::new(Px(260), Px(30)),
719 PxPosition::ZERO,
720 ];
721 assert_eq!(reordered_positions, expected_positions);
722 }
723
724 #[test]
725 fn test_randomized_sequences_preserve_dependencies() {
726 struct Lcg(u64);
727 impl Lcg {
728 fn new(seed: u64) -> Self {
729 Self(seed)
730 }
731 fn next_u32(&mut self) -> u32 {
732 self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1);
734 (self.0 >> 32) as u32
735 }
736 fn next_range(&mut self, upper: u32) -> u32 {
737 if upper == 0 {
738 0
739 } else {
740 self.next_u32() % upper
741 }
742 }
743 fn next_bool(&mut self) -> bool {
744 self.next_u32() & 1 == 1
745 }
746 }
747
748 fn random_barrier(rng: &mut Lcg, allow_none: bool) -> Option<BarrierRequirement> {
749 let roll = rng.next_range(if allow_none { 4 } else { 3 });
750 match roll {
751 0 if allow_none => None,
752 0 | 3 => Some(BarrierRequirement::Global),
753 1 => {
754 let padding = Px(rng.next_range(20) as i32);
755 Some(BarrierRequirement::uniform_padding_local(padding))
756 }
757 _ => {
758 let x = Px(rng.next_range(240) as i32);
759 let y = Px(rng.next_range(240) as i32);
760 let width = Px(10 + rng.next_range(80) as i32);
761 let height = Px(10 + rng.next_range(80) as i32);
762 Some(BarrierRequirement::Absolute(PxRect::new(
763 x, y, width, height,
764 )))
765 }
766 }
767 }
768
769 fn random_commands(seed: u64) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
770 let mut rng = Lcg::new(seed);
771 let mut commands = Vec::new();
772 let len = 5 + rng.next_range(6) as usize; let mut clip_depth = 0usize;
774
775 for _ in 0..len {
776 match rng.next_range(6) {
777 0 => {
778 let pos = PxPosition::new(
780 Px(rng.next_range(256) as i32),
781 Px(rng.next_range(256) as i32),
782 );
783 commands.push(create_cmd(pos, None, false));
784 }
785 1 => {
786 let pos = PxPosition::new(
788 Px(rng.next_range(256) as i32),
789 Px(rng.next_range(256) as i32),
790 );
791 commands.push(create_cmd2(pos, None, false));
792 }
793 2 => {
794 let pos = PxPosition::new(
796 Px(rng.next_range(256) as i32),
797 Px(rng.next_range(256) as i32),
798 );
799 let command = if rng.next_bool() {
800 create_cmd(pos, random_barrier(&mut rng, false), false)
801 } else {
802 create_cmd2(pos, random_barrier(&mut rng, false), false)
803 };
804 commands.push(command);
805 }
806 3 => {
807 let pos = PxPosition::new(
809 Px(rng.next_range(256) as i32),
810 Px(rng.next_range(256) as i32),
811 );
812 let command = if rng.next_bool() {
813 create_cmd(pos, random_barrier(&mut rng, false), true)
814 } else {
815 create_cmd2(pos, random_barrier(&mut rng, false), true)
816 };
817 commands.push(command);
818 }
819 4 => {
820 let width = Px(50 + rng.next_range(150) as i32);
822 let height = Px(50 + rng.next_range(150) as i32);
823 let rect = PxRect::new(
824 Px(rng.next_range(200) as i32),
825 Px(rng.next_range(200) as i32),
826 width,
827 height,
828 );
829 let size = PxSize::new(width, height);
830 commands.push((
831 Command::ClipPush(rect),
832 TypeId::of::<Command>(),
833 size,
834 PxPosition::new(rect.x, rect.y),
835 ));
836 clip_depth += 1;
837 }
838 _ => {
839 if clip_depth > 0 {
841 clip_depth -= 1;
842 commands.push((
843 Command::ClipPop,
844 TypeId::of::<Command>(),
845 PxSize::new(Px(0), Px(0)),
846 PxPosition::new(Px(0), Px(0)),
847 ));
848 } else {
849 let pos = PxPosition::new(
850 Px(rng.next_range(256) as i32),
851 Px(rng.next_range(256) as i32),
852 );
853 commands.push(create_cmd(pos, None, false));
854 }
855 }
856 }
857 }
858
859 while clip_depth > 0 {
860 clip_depth -= 1;
861 commands.push((
862 Command::ClipPop,
863 TypeId::of::<Command>(),
864 PxSize::new(Px(0), Px(0)),
865 PxPosition::new(Px(0), Px(0)),
866 ));
867 }
868
869 commands
870 }
871
872 for seed in 0..50 {
873 let commands = random_commands(seed);
874 let reordered = reorder_instructions(commands.clone());
875
876 let instructions: Vec<InstructionInfo> = commands
878 .iter()
879 .cloned()
880 .enumerate()
881 .map(|(i, cmd)| InstructionInfo::new(cmd, i))
882 .collect();
883
884 let mut potentials = HashMap::new();
885 for info in &instructions {
886 *potentials.entry((info.category, info.type_id)).or_insert(0) += 1;
887 }
888
889 let graph = build_dependency_graph(&instructions);
890 let sorted_indices = priority_topological_sort(&graph, &instructions, &potentials);
891 let expected: Vec<_> = sorted_indices
892 .iter()
893 .map(|idx| commands[idx.index()].clone())
894 .collect();
895
896 assert_eq!(reordered.len(), expected.len());
897 for (idx, (expected_item, reordered_item)) in
898 expected.iter().zip(&reordered).enumerate()
899 {
900 assert_eq!(
901 expected_item.1, reordered_item.1,
902 "TypeId mismatch at position {idx} for seed {seed}"
903 );
904 assert_eq!(
905 expected_item.2, reordered_item.2,
906 "Size mismatch at position {idx} for seed {seed}"
907 );
908 assert_eq!(
909 expected_item.3, reordered_item.3,
910 "Position mismatch at position {idx} for seed {seed}"
911 );
912 }
913
914 let mut position_by_original = vec![0usize; instructions.len()];
916 for (order, node_idx) in sorted_indices.iter().enumerate() {
917 position_by_original[node_idx.index()] = order;
918 }
919
920 for edge in graph.raw_edges() {
921 let src = edge.source().index();
922 let dst = edge.target().index();
923 assert!(
924 position_by_original[src] < position_by_original[dst],
925 "edge {:?}->{:?} violated for seed {seed}",
926 src,
927 dst
928 );
929 }
930 }
931 }
932
933 #[test]
934 fn test_batches_cards_across_orthogonal_gap() {
935 let card1 = create_cmd(PxPosition::new(Px(0), Px(0)), None, false);
936 let text1 = create_cmd2(
937 PxPosition::new(Px(0), Px(0)),
938 None, false,
940 );
941 let card2 = create_cmd(PxPosition::new(Px(200), Px(0)), None, false);
942 let text2 = create_cmd2(
943 PxPosition::new(Px(200), Px(0)),
944 None, false,
946 );
947
948 let commands = vec![card1, text1, card2, text2];
949
950 let reordered = reorder_instructions(commands);
951 let reordered_positions = get_positions(&reordered);
952 let expected_positions = vec![
953 PxPosition::new(Px(0), Px(0)),
954 PxPosition::new(Px(200), Px(0)),
955 PxPosition::new(Px(0), Px(0)),
956 PxPosition::new(Px(200), Px(0)),
957 ];
958 assert_eq!(reordered_positions, expected_positions);
959 }
960
961 #[test]
962 fn test_reorder_based_on_priority_with_no_overlap() {
963 let commands = vec![
964 create_cmd(
965 PxPosition::new(Px(0), Px(0)),
966 Some(BarrierRequirement::Absolute(PxRect::new(
967 Px(0),
968 Px(0),
969 Px(10),
970 Px(10),
971 ))), false, ), create_cmd(
975 PxPosition::new(Px(100), Px(100)),
976 Some(BarrierRequirement::Absolute(PxRect::new(
977 Px(100),
978 Px(100),
979 Px(10),
980 Px(10),
981 ))), true, ), create_cmd(PxPosition::new(Px(200), Px(200)), None, false), ];
986 let original_positions = get_positions(&commands);
987 let reordered = reorder_instructions(commands);
990 let reordered_positions = get_positions(&reordered);
991
992 let expected_positions = vec![
993 original_positions[1], original_positions[0], original_positions[2], ];
997 assert_eq!(reordered_positions, expected_positions);
998 }
999
1000 #[test]
1001 fn test_compute_commands_of_same_type_stay_contiguous() {
1002 let commands = vec![
1003 create_cmd(
1004 PxPosition::new(Px(0), Px(0)),
1005 Some(BarrierRequirement::Absolute(PxRect::new(
1006 Px(0),
1007 Px(0),
1008 Px(30),
1009 Px(30),
1010 ))),
1011 true,
1012 ),
1013 create_cmd2(PxPosition::new(Px(150), Px(0)), None, false),
1014 create_cmd2(
1015 PxPosition::new(Px(60), Px(0)),
1016 Some(BarrierRequirement::Absolute(PxRect::new(
1017 Px(60),
1018 Px(0),
1019 Px(30),
1020 Px(30),
1021 ))),
1022 true,
1023 ),
1024 create_cmd(
1025 PxPosition::new(Px(120), Px(0)),
1026 Some(BarrierRequirement::Absolute(PxRect::new(
1027 Px(120),
1028 Px(0),
1029 Px(30),
1030 Px(30),
1031 ))),
1032 true,
1033 ),
1034 ];
1035
1036 let reordered = reorder_instructions(commands);
1037
1038 let mut compute_type_sequence = Vec::new();
1039 let mut saw_non_compute = false;
1040 for (command, type_id, _, _) in &reordered {
1041 match command {
1042 Command::Compute(_) => {
1043 assert!(
1044 !saw_non_compute,
1045 "Compute command appeared after non-compute commands"
1046 );
1047 compute_type_sequence.push(*type_id);
1048 }
1049 _ => saw_non_compute = true,
1050 }
1051 }
1052
1053 assert!(
1054 compute_type_sequence.len() >= 2,
1055 "Expected multiple compute commands to verify grouping"
1056 );
1057
1058 let mut type_positions: std::collections::HashMap<TypeId, Vec<usize>> =
1059 std::collections::HashMap::new();
1060 for (idx, ty) in compute_type_sequence.iter().enumerate() {
1061 type_positions.entry(*ty).or_default().push(idx);
1062 }
1063
1064 for positions in type_positions.values() {
1065 let first = positions[0];
1066 let last = *positions
1067 .last()
1068 .expect("compute positions should not be empty");
1069 assert_eq!(
1070 last - first + 1,
1071 positions.len(),
1072 "Compute type span is not contiguous"
1073 );
1074 }
1075 }
1076
1077 #[test]
1078 fn test_complex_reordering_with_dependencies() {
1079 let commands = vec![
1080 create_cmd(
1082 PxPosition::new(Px(0), Px(0)),
1083 Some(BarrierRequirement::Global),
1084 true,
1085 ),
1086 create_cmd(
1088 PxPosition::new(Px(50), Px(50)),
1089 Some(BarrierRequirement::Absolute(PxRect::new(
1090 Px(50),
1091 Px(50),
1092 Px(10),
1093 Px(10),
1094 ))),
1095 false,
1096 ),
1097 create_cmd(PxPosition::new(Px(200), Px(200)), None, false),
1099 create_cmd(PxPosition::new(Px(205), Px(205)), None, false),
1101 create_cmd(
1103 PxPosition::new(Px(80), Px(80)),
1104 Some(BarrierRequirement::Absolute(PxRect::new(
1105 Px(80),
1106 Px(80),
1107 Px(10),
1108 Px(10),
1109 ))),
1110 false,
1111 ),
1112 ];
1113 let original_positions = get_positions(&commands);
1114
1115 let reordered = reorder_instructions(commands);
1128 let reordered_positions = get_positions(&reordered);
1129 let expected_positions = vec![
1130 original_positions[0],
1131 original_positions[1],
1132 original_positions[4],
1133 original_positions[2],
1134 original_positions[3],
1135 ];
1136 assert_eq!(reordered_positions, expected_positions);
1137 }
1138
1139 #[test]
1140 fn test_blur_batching_with_large_sampling_padding() {
1141 let blur_commands = vec![
1146 create_cmd(
1147 PxPosition::new(Px(0), Px(0)),
1148 Some(BarrierRequirement::uniform_padding_local(Px(75))),
1149 true, ),
1151 create_cmd(
1152 PxPosition::new(Px(200), Px(0)),
1153 Some(BarrierRequirement::uniform_padding_local(Px(75))),
1154 true,
1155 ),
1156 create_cmd(
1157 PxPosition::new(Px(400), Px(0)),
1158 Some(BarrierRequirement::uniform_padding_local(Px(75))),
1159 true,
1160 ),
1161 create_cmd(
1162 PxPosition::new(Px(600), Px(0)),
1163 Some(BarrierRequirement::uniform_padding_local(Px(75))),
1164 true,
1165 ),
1166 create_cmd(
1167 PxPosition::new(Px(800), Px(0)),
1168 Some(BarrierRequirement::uniform_padding_local(Px(75))),
1169 true,
1170 ),
1171 ];
1172
1173 let reordered = reorder_instructions(blur_commands);
1174
1175 let kinds: Vec<&'static str> = reordered
1178 .iter()
1179 .map(|(cmd, _, _, _)| match cmd {
1180 Command::Compute(_) => "C",
1181 Command::Draw(_) => "D",
1182 _ => panic!("unexpected command variant"),
1183 })
1184 .collect();
1185
1186 assert_eq!(kinds, vec!["C", "C", "C", "C", "C"]);
1187
1188 let positions = get_positions(&reordered);
1190 assert_eq!(positions.len(), 5);
1191
1192 }
1197}