1use std::collections::VecDeque;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6
7use alpha2_gpu::{Capabilities, CapabilityTier};
8
9use crate::{
10 PipelineCacheStats, ReadbackBufferPoolStats, TexturePoolStats, UniformBufferPoolStats,
11 UploadTexturePoolStats,
12};
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16pub enum TimingSource {
17 GpuTimestamp,
19 CpuEncoding,
21}
22
23#[derive(Copy, Clone, Debug, Eq, PartialEq)]
27pub struct PassTiming {
28 pub label: &'static str,
30 pub nanoseconds: u64,
32 pub source: TimingSource,
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct AdapterDiagnostics {
39 pub name: String,
41 pub backend: String,
43 pub driver: String,
45 pub driver_info: String,
47 pub tier: CapabilityTier,
49 pub timestamp_queries: bool,
51 pub shader_compiler: String,
53}
54
55#[derive(Clone, Debug, PartialEq)]
57pub struct RenderDiagnostics {
58 pub adapter: AdapterDiagnostics,
60 pub passes: Vec<PassTiming>,
62 pub textures: TexturePoolStats,
64 pub upload_textures: UploadTexturePoolStats,
66 pub readback_buffers: ReadbackBufferPoolStats,
68 pub uniform_buffers: UniformBufferPoolStats,
70 pub pipelines: PipelineCacheStats,
72 pub pipeline_creation: PipelineCreationStats,
74 pub result_cache: crate::ResultCacheStats,
76 pub presented_frames: u64,
78 pub dropped_frames: u64,
80 pub repeated_frames: u64,
82 pub decoder_wait: Duration,
84 pub max_filter_samples: u32,
86 pub motion_blur_subframes: u32,
88 pub degradations: Vec<crate::CapabilityDiagnostic>,
90 pub graph_nodes: u64,
92 pub graph_passes: u64,
94 pub graph_submissions: u64,
96 pub source_submissions: u64,
98 pub source_conversions: u64,
100 pub yuv_repacks: u64,
102 pub readback_submissions: u64,
104 pub frame_submissions: u64,
106 pub render_passes: u64,
108 pub copy_operations: u64,
110 pub copy_bytes: u64,
112}
113
114#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
116pub struct PipelineCreationStats {
117 pub created: u64,
119 pub create_micros_total: u64,
121 pub create_micros_max: u64,
123}
124
125#[derive(Copy, Clone, Debug, Default, PartialEq)]
128pub struct GpuSubmissionStats {
129 pub frames: u64,
131 pub split_frames: u64,
133 pub max_per_frame: u32,
135 pub max_predicted_ms: Option<f64>,
137}
138
139#[derive(Clone, Debug, PartialEq)]
141pub struct PipelineCreation {
142 pub label: String,
144 pub started: web_time::Instant,
146 pub finished: web_time::Instant,
148}
149
150#[derive(Clone, Debug, PartialEq)]
152pub struct BenchmarkMetadata {
153 pub project: String,
155 pub alpha2_version: &'static str,
157 pub adapter: AdapterDiagnostics,
159 pub dimensions: (u32, u32),
161 pub resolution_scale: f32,
163 pub max_filter_samples: u32,
165 pub motion_blur_subframes: u32,
167 pub texture_budget_bytes: u64,
169}
170
171const PASS_HISTORY: usize = 256;
173
174const PIPELINE_HISTORY: usize = 256;
176
177#[derive(Debug, Default)]
178pub(crate) struct DiagnosticsState {
179 passes: VecDeque<PassTiming>,
180 timing_epoch: u64,
182 presented_frames: u64,
183 dropped_frames: u64,
184 repeated_frames: u64,
185 decoder_wait: Duration,
186 graph_nodes: u64,
187 graph_passes: u64,
188 graph_submissions: u64,
189 source_submissions: u64,
190 source_conversions: u64,
191 yuv_repacks: u64,
192 readback_submissions: u64,
193 frame_submissions: u64,
194 render_passes: u64,
195 copy_operations: u64,
196 copy_bytes: u64,
197 pipeline_creation: PipelineCreationStats,
198 pipeline_creations: VecDeque<PipelineCreation>,
199 gpu_submissions: GpuSubmissionStats,
200}
201
202pub(crate) type SharedDiagnostics = Arc<Mutex<DiagnosticsState>>;
203
204#[derive(Copy, Clone)]
205pub(crate) struct ResourceDiagnostics {
206 pub(crate) textures: TexturePoolStats,
207 pub(crate) upload_textures: UploadTexturePoolStats,
208 pub(crate) readback_buffers: ReadbackBufferPoolStats,
209 pub(crate) uniform_buffers: UniformBufferPoolStats,
210 pub(crate) pipelines: PipelineCacheStats,
211 pub(crate) result_cache: crate::ResultCacheStats,
212}
213
214pub(crate) fn timing_epoch(state: &SharedDiagnostics) -> u64 {
216 state
217 .lock()
218 .unwrap_or_else(std::sync::PoisonError::into_inner)
219 .timing_epoch
220}
221
222pub(crate) fn record_pass(
224 state: &SharedDiagnostics,
225 epoch: u64,
226 label: &'static str,
227 duration: Duration,
228 source: TimingSource,
229) {
230 let mut state = state
231 .lock()
232 .unwrap_or_else(std::sync::PoisonError::into_inner);
233 if state.timing_epoch != epoch {
234 return;
235 }
236 if state.passes.len() == PASS_HISTORY {
237 state.passes.pop_front();
238 }
239 state.passes.push_back(PassTiming {
240 label,
241 nanoseconds: u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX),
242 source,
243 });
244}
245
246pub(crate) fn record_graph_work(
247 state: &SharedDiagnostics,
248 nodes: u64,
249 render_passes: u64,
250 standalone_submission: bool,
251) {
252 let mut state = state
253 .lock()
254 .unwrap_or_else(std::sync::PoisonError::into_inner);
255 state.graph_nodes = state.graph_nodes.saturating_add(nodes);
256 state.graph_passes = state.graph_passes.saturating_add(render_passes);
257 if standalone_submission {
258 state.graph_submissions = state.graph_submissions.saturating_add(1);
259 }
260 state.render_passes = state.render_passes.saturating_add(render_passes);
261}
262
263pub(crate) fn record_source_work(
264 state: &SharedDiagnostics,
265 render_passes: u64,
266 standalone_submission: bool,
267 conversion: bool,
268) {
269 let mut state = state
270 .lock()
271 .unwrap_or_else(std::sync::PoisonError::into_inner);
272 state.source_conversions = state
273 .source_conversions
274 .saturating_add(u64::from(conversion));
275 if standalone_submission {
276 state.source_submissions = state.source_submissions.saturating_add(1);
277 }
278 state.render_passes = state.render_passes.saturating_add(render_passes);
279}
280
281pub(crate) fn record_readback_work(
282 state: &SharedDiagnostics,
283 render_passes: u64,
284 copy_operations: u64,
285 copy_bytes: u64,
286 standalone_submission: bool,
287) {
288 let mut state = state
289 .lock()
290 .unwrap_or_else(std::sync::PoisonError::into_inner);
291 if standalone_submission {
292 state.readback_submissions = state.readback_submissions.saturating_add(1);
293 }
294 state.render_passes = state.render_passes.saturating_add(render_passes);
295 state.copy_operations = state.copy_operations.saturating_add(copy_operations);
296 state.copy_bytes = state.copy_bytes.saturating_add(copy_bytes);
297}
298
299pub(crate) fn record_frame_submission(state: &SharedDiagnostics) {
300 let mut state = state
301 .lock()
302 .unwrap_or_else(std::sync::PoisonError::into_inner);
303 state.frame_submissions = state.frame_submissions.saturating_add(1);
304}
305
306pub(crate) fn end_timing_run(state: &SharedDiagnostics) {
308 let mut state = state
309 .lock()
310 .unwrap_or_else(std::sync::PoisonError::into_inner);
311 state.passes.clear();
312 state.timing_epoch = state.timing_epoch.wrapping_add(1);
313}
314
315pub(crate) fn snapshot(
316 state: &SharedDiagnostics,
317 capabilities: &Capabilities,
318 resources: &ResourceDiagnostics,
319 max_filter_samples: u32,
320 motion_blur_subframes: u32,
321 degradations: Vec<crate::CapabilityDiagnostic>,
322) -> RenderDiagnostics {
323 let state = state
324 .lock()
325 .unwrap_or_else(std::sync::PoisonError::into_inner);
326 RenderDiagnostics {
327 adapter: AdapterDiagnostics {
328 name: capabilities.adapter_name.clone(),
329 backend: capabilities.backend.clone(),
330 driver: capabilities.driver.clone(),
331 driver_info: capabilities.driver_info.clone(),
332 tier: capabilities.tier,
333 timestamp_queries: capabilities.timestamp_queries,
334 shader_compiler: capabilities.shader_compiler.clone(),
335 },
336 passes: state.passes.iter().copied().collect(),
337 textures: resources.textures,
338 upload_textures: resources.upload_textures,
339 readback_buffers: resources.readback_buffers,
340 uniform_buffers: resources.uniform_buffers,
341 pipelines: resources.pipelines,
342 pipeline_creation: state.pipeline_creation,
343 result_cache: resources.result_cache,
344 presented_frames: state.presented_frames,
345 dropped_frames: state.dropped_frames,
346 repeated_frames: state.repeated_frames,
347 decoder_wait: state.decoder_wait,
348 max_filter_samples,
349 motion_blur_subframes,
350 degradations,
351 graph_nodes: state.graph_nodes,
352 graph_passes: state.graph_passes,
353 graph_submissions: state.graph_submissions,
354 source_submissions: state.source_submissions,
355 source_conversions: state.source_conversions,
356 yuv_repacks: state.yuv_repacks,
357 readback_submissions: state.readback_submissions,
358 frame_submissions: state.frame_submissions,
359 render_passes: state.render_passes,
360 copy_operations: state.copy_operations,
361 copy_bytes: state.copy_bytes,
362 }
363}
364
365pub(crate) fn timed_pipeline_creation<T>(
367 state: &SharedDiagnostics,
368 label: impl FnOnce() -> String,
369 create: impl FnOnce() -> T,
370) -> T {
371 let started = web_time::Instant::now();
372 let created = create();
373 let finished = web_time::Instant::now();
374 let micros = u64::try_from(finished.duration_since(started).as_micros()).unwrap_or(u64::MAX);
375 let mut state = state
376 .lock()
377 .unwrap_or_else(std::sync::PoisonError::into_inner);
378 let totals = &mut state.pipeline_creation;
379 totals.created = totals.created.saturating_add(1);
380 totals.create_micros_total = totals.create_micros_total.saturating_add(micros);
381 totals.create_micros_max = totals.create_micros_max.max(micros);
382 if state.pipeline_creations.len() == PIPELINE_HISTORY {
383 state.pipeline_creations.pop_front();
384 }
385 state.pipeline_creations.push_back(PipelineCreation {
386 label: label(),
387 started,
388 finished,
389 });
390 created
391}
392
393pub(crate) fn take_pipeline_creations(state: &SharedDiagnostics) -> Vec<PipelineCreation> {
394 state
395 .lock()
396 .unwrap_or_else(std::sync::PoisonError::into_inner)
397 .pipeline_creations
398 .drain(..)
399 .collect()
400}
401
402pub(crate) fn record_frame_plan(
404 state: &SharedDiagnostics,
405 frame: &crate::FrameSubmissions,
406 max_predicted_ms: Option<f64>,
407) {
408 let mut state = state
409 .lock()
410 .unwrap_or_else(std::sync::PoisonError::into_inner);
411 let totals = &mut state.gpu_submissions;
412 totals.frames = totals.frames.saturating_add(1);
413 totals.split_frames = totals
414 .split_frames
415 .saturating_add(u64::from(frame.submissions > 1));
416 totals.max_per_frame = totals.max_per_frame.max(frame.submissions);
417 if let Some(ms) = max_predicted_ms {
418 totals.max_predicted_ms = Some(totals.max_predicted_ms.map_or(ms, |most| most.max(ms)));
419 }
420}
421
422pub(crate) fn take_gpu_submission_stats(state: &SharedDiagnostics) -> GpuSubmissionStats {
424 std::mem::take(
425 &mut state
426 .lock()
427 .unwrap_or_else(std::sync::PoisonError::into_inner)
428 .gpu_submissions,
429 )
430}
431
432pub(crate) fn record_yuv_repack(state: &SharedDiagnostics) {
433 let mut state = state
434 .lock()
435 .unwrap_or_else(std::sync::PoisonError::into_inner);
436 state.yuv_repacks = state.yuv_repacks.saturating_add(1);
437}
438
439pub(crate) fn record_frame(state: &SharedDiagnostics, presented: bool, repeated: bool) {
440 let mut state = state
441 .lock()
442 .unwrap_or_else(std::sync::PoisonError::into_inner);
443 state.presented_frames += u64::from(presented);
444 state.dropped_frames += u64::from(!presented);
445 state.repeated_frames += u64::from(repeated);
446}
447
448pub(crate) fn record_decoder_wait(state: &SharedDiagnostics, duration: Duration) {
449 let mut state = state
450 .lock()
451 .unwrap_or_else(std::sync::PoisonError::into_inner);
452 state.decoder_wait = state.decoder_wait.saturating_add(duration);
453}