2019-03-05 17:46:18 -05:00
|
|
|
// pathfinder/renderer/src/gpu/debug.rs
|
2019-01-30 17:42:06 -05:00
|
|
|
//
|
|
|
|
// Copyright © 2019 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2019-01-30 22:31:29 -05:00
|
|
|
//! A debug overlay.
|
2019-01-30 17:42:06 -05:00
|
|
|
//!
|
|
|
|
//! We don't render the demo UI text using Pathfinder itself so that we can use the debug UI to
|
|
|
|
//! debug Pathfinder if it's totally busted.
|
|
|
|
//!
|
|
|
|
//! The debug font atlas was generated using: https://evanw.github.io/font-texture-generator/
|
|
|
|
|
2019-03-05 17:46:18 -05:00
|
|
|
use crate::gpu_data::Stats;
|
2019-02-05 13:55:01 -05:00
|
|
|
use pathfinder_geometry::basic::point::Point2DI32;
|
|
|
|
use pathfinder_geometry::basic::rect::RectI32;
|
2019-03-08 19:52:47 -05:00
|
|
|
use pathfinder_gpu::Device;
|
|
|
|
use pathfinder_gpu::resources::ResourceLoader;
|
2019-03-06 13:47:52 -05:00
|
|
|
use pathfinder_ui::{FONT_ASCENT, LINE_HEIGHT, PADDING, UI, WINDOW_COLOR};
|
2019-03-05 18:13:55 -05:00
|
|
|
use std::collections::VecDeque;
|
2019-02-25 19:12:47 -05:00
|
|
|
use std::ops::{Add, Div};
|
2019-01-30 17:42:06 -05:00
|
|
|
use std::time::Duration;
|
|
|
|
|
2019-02-07 18:35:21 -05:00
|
|
|
const SAMPLE_BUFFER_SIZE: usize = 60;
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
const PERF_WINDOW_WIDTH: i32 = 375;
|
|
|
|
const PERF_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 6 + PADDING + 2;
|
2019-01-30 17:42:06 -05:00
|
|
|
|
2019-03-04 17:55:32 -05:00
|
|
|
pub struct DebugUI<D> where D: Device {
|
2019-03-05 18:13:55 -05:00
|
|
|
pub ui: UI<D>,
|
2019-02-07 18:35:21 -05:00
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
cpu_samples: SampleBuffer<CPUSample>,
|
|
|
|
gpu_samples: SampleBuffer<GPUSample>,
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
|
|
|
|
2019-03-04 17:55:32 -05:00
|
|
|
impl<D> DebugUI<D> where D: Device {
|
2019-03-08 19:52:47 -05:00
|
|
|
pub fn new(device: &D, resources: &dyn ResourceLoader, framebuffer_size: Point2DI32)
|
|
|
|
-> DebugUI<D> {
|
2019-03-05 18:13:55 -05:00
|
|
|
let ui = UI::new(device, resources, framebuffer_size);
|
|
|
|
DebugUI { ui, cpu_samples: SampleBuffer::new(), gpu_samples: SampleBuffer::new() }
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
pub fn add_sample(&mut self,
|
|
|
|
stats: Stats,
|
|
|
|
tile_time: Duration,
|
|
|
|
rendering_time: Option<Duration>) {
|
|
|
|
self.cpu_samples.push(CPUSample { stats, elapsed: tile_time });
|
2019-02-07 18:35:21 -05:00
|
|
|
if let Some(rendering_time) = rendering_time {
|
2019-02-25 19:12:47 -05:00
|
|
|
self.gpu_samples.push(GPUSample { elapsed: rendering_time });
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 17:55:32 -05:00
|
|
|
pub fn draw(&self, device: &D) {
|
2019-02-06 23:59:29 -05:00
|
|
|
// Draw performance window.
|
2019-03-05 18:13:55 -05:00
|
|
|
let framebuffer_size = self.ui.framebuffer_size();
|
|
|
|
let bottom = framebuffer_size.y() - PADDING;
|
2019-02-05 13:55:01 -05:00
|
|
|
let window_rect = RectI32::new(
|
2019-03-05 18:13:55 -05:00
|
|
|
Point2DI32::new(framebuffer_size.x() - PADDING - PERF_WINDOW_WIDTH,
|
2019-02-07 17:07:05 -05:00
|
|
|
bottom - PERF_WINDOW_HEIGHT),
|
|
|
|
Point2DI32::new(PERF_WINDOW_WIDTH, PERF_WINDOW_HEIGHT));
|
2019-03-05 18:13:55 -05:00
|
|
|
self.ui.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
|
2019-02-25 19:12:47 -05:00
|
|
|
let origin = window_rect.origin() + Point2DI32::new(PADDING, PADDING + FONT_ASCENT);
|
|
|
|
|
|
|
|
let mean_cpu_sample = self.cpu_samples.mean();
|
2019-03-05 18:13:55 -05:00
|
|
|
self.ui.draw_text(device,
|
2019-03-04 17:55:32 -05:00
|
|
|
&format!("Objects: {}", mean_cpu_sample.stats.object_count),
|
|
|
|
origin,
|
|
|
|
false);
|
2019-03-05 18:13:55 -05:00
|
|
|
self.ui.draw_text(device,
|
2019-03-04 17:55:32 -05:00
|
|
|
&format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count),
|
2019-02-25 19:12:47 -05:00
|
|
|
origin + Point2DI32::new(0, LINE_HEIGHT * 1),
|
|
|
|
false);
|
2019-03-05 18:13:55 -05:00
|
|
|
self.ui.draw_text(device,
|
|
|
|
&format!("Mask Tiles: {}", mean_cpu_sample.stats.mask_tile_count),
|
|
|
|
origin + Point2DI32::new(0, LINE_HEIGHT * 2),
|
|
|
|
false);
|
|
|
|
self.ui.draw_text(device,
|
|
|
|
&format!("Fills: {}", mean_cpu_sample.stats.fill_count),
|
|
|
|
origin + Point2DI32::new(0, LINE_HEIGHT * 3),
|
|
|
|
false);
|
|
|
|
|
|
|
|
self.ui.draw_text(device,
|
|
|
|
&format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)),
|
|
|
|
origin + Point2DI32::new(0, LINE_HEIGHT * 4),
|
|
|
|
false);
|
2019-02-25 19:12:47 -05:00
|
|
|
|
|
|
|
let mean_gpu_sample = self.gpu_samples.mean();
|
2019-03-05 18:13:55 -05:00
|
|
|
self.ui.draw_text(device,
|
|
|
|
&format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)),
|
|
|
|
origin + Point2DI32::new(0, LINE_HEIGHT * 5),
|
|
|
|
false);
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
struct SampleBuffer<S> where S: Add<S, Output=S> + Div<u32, Output=S> + Clone + Default {
|
|
|
|
samples: VecDeque<S>,
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
impl<S> SampleBuffer<S> where S: Add<S, Output=S> + Div<u32, Output=S> + Clone + Default {
|
|
|
|
fn new() -> SampleBuffer<S> {
|
2019-02-07 18:35:21 -05:00
|
|
|
SampleBuffer { samples: VecDeque::with_capacity(SAMPLE_BUFFER_SIZE) }
|
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
fn push(&mut self, time: S) {
|
2019-02-07 18:35:21 -05:00
|
|
|
self.samples.push_back(time);
|
|
|
|
while self.samples.len() > SAMPLE_BUFFER_SIZE {
|
|
|
|
self.samples.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
fn mean(&self) -> S {
|
|
|
|
let mut mean = Default::default();
|
2019-02-07 18:35:21 -05:00
|
|
|
if self.samples.is_empty() {
|
2019-02-25 19:12:47 -05:00
|
|
|
return mean;
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for time in &self.samples {
|
2019-02-25 19:12:47 -05:00
|
|
|
mean = mean + (*time).clone();
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
2019-02-25 19:12:47 -05:00
|
|
|
|
|
|
|
mean / self.samples.len() as u32
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
#[derive(Clone, Default)]
|
|
|
|
struct CPUSample {
|
|
|
|
elapsed: Duration,
|
|
|
|
stats: Stats,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Add<CPUSample> for CPUSample {
|
|
|
|
type Output = CPUSample;
|
|
|
|
fn add(self, other: CPUSample) -> CPUSample {
|
|
|
|
CPUSample {
|
|
|
|
elapsed: self.elapsed + other.elapsed,
|
|
|
|
stats: Stats {
|
|
|
|
object_count: self.stats.object_count + other.stats.object_count,
|
|
|
|
solid_tile_count: self.stats.solid_tile_count + other.stats.solid_tile_count,
|
|
|
|
mask_tile_count: self.stats.mask_tile_count + other.stats.mask_tile_count,
|
|
|
|
fill_count: self.stats.fill_count + other.stats.fill_count,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Div<u32> for CPUSample {
|
|
|
|
type Output = CPUSample;
|
|
|
|
fn div(self, divisor: u32) -> CPUSample {
|
|
|
|
CPUSample {
|
|
|
|
elapsed: self.elapsed / divisor,
|
|
|
|
stats: Stats {
|
|
|
|
object_count: self.stats.object_count / divisor,
|
|
|
|
solid_tile_count: self.stats.solid_tile_count / divisor,
|
|
|
|
mask_tile_count: self.stats.mask_tile_count / divisor,
|
|
|
|
fill_count: self.stats.fill_count / divisor,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
struct GPUSample {
|
|
|
|
elapsed: Duration,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Add<GPUSample> for GPUSample {
|
|
|
|
type Output = GPUSample;
|
|
|
|
fn add(self, other: GPUSample) -> GPUSample {
|
|
|
|
GPUSample { elapsed: self.elapsed + other.elapsed }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Div<u32> for GPUSample {
|
|
|
|
type Output = GPUSample;
|
|
|
|
fn div(self, divisor: u32) -> GPUSample {
|
|
|
|
GPUSample { elapsed: self.elapsed / divisor }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn duration_to_ms(time: Duration) -> f64 {
|
|
|
|
time.as_secs() as f64 * 1000.0 + time.subsec_nanos() as f64 / 1000000.0
|
|
|
|
}
|