cvkg_core/
frame_manifest.rs1use crate::frame_phase::FramePhase;
9use crate::identity::KvasirId;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13pub struct TimeBudgetRequest {
14 pub phase: FramePhase,
16 pub time_slice_us: u32,
18 pub skippable: bool,
20 pub name: &'static str,
22}
23
24#[derive(Debug, Clone)]
26pub struct PassNodeDescriptor {
27 pub id: &'static str,
29 pub label: &'static str,
31 pub inputs: &'static [&'static str],
33 pub outputs: &'static [&'static str],
35 pub after: &'static [&'static str],
38 pub constructor: fn() -> Box<dyn PassNode>,
41}
42
43impl PartialEq for PassNodeDescriptor {
46 fn eq(&self, other: &Self) -> bool {
47 self.id == other.id
48 && self.label == other.label
49 && self.inputs == other.inputs
50 && self.outputs == other.outputs
51 && self.after == other.after
52 }
53}
54impl Eq for PassNodeDescriptor {}
55
56pub trait PassNode: Send + Sync + 'static {
59 fn id(&self) -> &'static str {
61 std::any::type_name::<Self>()
62 }
63
64 fn label(&self) -> &'static str {
66 self.id()
67 }
68
69 fn inputs(&self) -> &[&'static str] {
71 &[]
72 }
73
74 fn outputs(&self) -> &[&'static str] {
76 &[]
77 }
78
79 fn pass_id(&self) -> KvasirId {
81 KvasirId::new()
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct FrameManifest {
88 pub phase_contributions: &'static [FramePhase],
90 pub pass_nodes: &'static [PassNodeDescriptor],
92 pub time_budget_requests: &'static [TimeBudgetRequest],
94}
95
96impl FrameManifest {
97 pub const fn empty() -> Self {
99 Self {
100 phase_contributions: &[],
101 pass_nodes: &[],
102 time_budget_requests: &[],
103 }
104 }
105
106 pub const fn merge(manifests: &[&Self]) -> Self {
108 let mut _total_phases = 0usize;
110 let mut _total_passes = 0usize;
111 let mut _total_budgets = 0usize;
112 let mut i = 0;
113 while i < manifests.len() {
114 _total_phases += manifests[i].phase_contributions.len();
115 _total_passes += manifests[i].pass_nodes.len();
116 _total_budgets += manifests[i].time_budget_requests.len();
117 i += 1;
118 }
119
120 Self::empty()
125 }
126}
127
128#[macro_export]
130macro_rules! merge_manifests {
131 ($($manifest:expr),+ $(,)?) => {
132 const _VERIFY_MANIFESTS: () = {
134 $crate::validate_manifests(&[ $( & $manifest ),+ ]);
135
136 let _check_merge = $crate::FrameManifest::merge(&[ $( & $manifest ),+ ]);
138 let _ = _check_merge;
139 };
140
141 pub static MERGED_MANIFEST: $crate::FrameManifest = {
143 $crate::FrameManifest::merge(&[ $( & $manifest ),+ ])
146 };
147
148 pub static KVASIR_PASSES: &[&'static $crate::PassNodeDescriptor] = {
150 &[]
153 };
154
155 #[allow(dead_code)]
157 pub static MERGED_BUDGET_REQUESTS: &'static [$crate::TimeBudgetRequest] = {
158 &[]
160 };
161 };
162}
163
164pub const fn validate_manifests(manifests: &[&FrameManifest]) {
166 let mut i = 0;
167 while i < manifests.len() {
168 validate_phase_order(manifests[i].phase_contributions);
169 validate_pass_node(manifests[i].pass_nodes);
170 validate_budgets(manifests[i].time_budget_requests);
171 i += 1;
172 }
173
174 let mut all_pass_ids: [&'static str; 64] = [""; 64];
176 let mut pass_count = 0;
177
178 let mut m = 0;
179 while m < manifests.len() {
180 let passes = manifests[m].pass_nodes;
181 let mut p = 0;
182 while p < passes.len() && pass_count < 64 {
183 all_pass_ids[pass_count] = passes[p].id;
184 pass_count += 1;
185 p += 1;
186 }
187 m += 1;
188 }
189
190 let mut i = 0;
192 while i < pass_count {
193 let mut j = i + 1;
194 while j < pass_count {
195 let id_i = all_pass_ids[i].as_bytes();
196 let id_j = all_pass_ids[j].as_bytes();
197 let mut k = 0;
198 let mut equal = true;
199 while k < id_i.len() && k < id_j.len() {
200 if id_i[k] != id_j[k] {
201 equal = false;
202 }
203 k += 1;
204 }
205 if equal && id_i.len() == id_j.len() {
206 panic!("Duplicate pass ID across manifests");
207 }
208 j += 1;
209 }
210 i += 1;
211 }
212}
213
214const fn validate_phase_order(phases: &[FramePhase]) {
215 let mut i = 0;
216 while i < phases.len() {
217 if i > 0 {
218 let prev = phases[i - 1] as u8;
219 let curr = phases[i] as u8;
220 if curr < prev {
221 panic!("Phase contributions must be in ascending FramePhase order");
222 }
223 }
224 i += 1;
225 }
226}
227
228const fn validate_pass_node(passes: &[PassNodeDescriptor]) {
229 let mut i = 0;
230 while i < passes.len() {
231 let mut j = i + 1;
232 while j < passes.len() {
233 let id_i = passes[i].id.as_bytes();
234 let id_j = passes[j].id.as_bytes();
235 let mut k = 0;
236 let mut equal = true;
237 while k < id_i.len() && k < id_j.len() {
238 if id_i[k] != id_j[k] {
239 equal = false;
240 }
241 k += 1;
242 }
243 if equal && id_i.len() == id_j.len() {
244 panic!("Duplicate pass ID");
245 }
246 j += 1;
247 }
248 i += 1;
249 }
250}
251
252const fn validate_budgets(budgets: &[TimeBudgetRequest]) {
253 let mut total_by_phase = [0u32; 8];
254 let mut i = 0;
255 while i < budgets.len() {
256 let phase_idx = budgets[i].phase as u8 as usize;
257 total_by_phase[phase_idx] = total_by_phase[phase_idx].saturating_add(budgets[i].time_slice_us);
258 i += 1;
259 }
260
261 const MAX_BUDGET_US: u32 = 16667;
262 let mut phase = 0;
263 while phase < 8 {
264 if total_by_phase[phase] > MAX_BUDGET_US {
265 panic!("Phase budget exceeds limit");
266 }
267 phase += 1;
268 }
269}