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-05-07 14:22:24 -04:00
|
|
|
use crate::gpu::renderer::{RenderStats, RenderTime};
|
2019-06-21 13:06:19 -04:00
|
|
|
use pathfinder_geometry::vector::Vector2I;
|
|
|
|
use pathfinder_geometry::rect::RectI;
|
2019-04-29 19:45:29 -04:00
|
|
|
use pathfinder_gpu::Device;
|
2020-02-28 20:10:53 -05:00
|
|
|
use pathfinder_resources::ResourceLoader;
|
2019-05-06 18:36:39 -04:00
|
|
|
use pathfinder_ui::{FONT_ASCENT, LINE_HEIGHT, PADDING, UIPresenter, 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-05-07 14:22:24 -04:00
|
|
|
const STATS_WINDOW_WIDTH: i32 = 325;
|
|
|
|
const STATS_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 4 + PADDING + 2;
|
|
|
|
|
|
|
|
const PERFORMANCE_WINDOW_WIDTH: i32 = 400;
|
|
|
|
const PERFORMANCE_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 4 + PADDING + 2;
|
2019-01-30 17:42:06 -05:00
|
|
|
|
2019-05-06 18:36:39 -04:00
|
|
|
pub struct DebugUIPresenter<D>
|
2019-04-29 19:45:29 -04:00
|
|
|
where
|
|
|
|
D: Device,
|
|
|
|
{
|
2019-05-06 18:36:39 -04:00
|
|
|
pub ui_presenter: UIPresenter<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-05-06 18:36:39 -04:00
|
|
|
impl<D> DebugUIPresenter<D>
|
2019-04-29 19:45:29 -04:00
|
|
|
where
|
|
|
|
D: Device,
|
|
|
|
{
|
|
|
|
pub fn new(
|
|
|
|
device: &D,
|
|
|
|
resources: &dyn ResourceLoader,
|
2019-06-03 15:39:29 -04:00
|
|
|
framebuffer_size: Vector2I,
|
2019-05-06 18:36:39 -04:00
|
|
|
) -> DebugUIPresenter<D> {
|
|
|
|
let ui_presenter = UIPresenter::new(device, resources, framebuffer_size);
|
|
|
|
DebugUIPresenter {
|
|
|
|
ui_presenter,
|
2019-04-29 19:45:29 -04:00
|
|
|
cpu_samples: SampleBuffer::new(),
|
|
|
|
gpu_samples: SampleBuffer::new(),
|
|
|
|
}
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
|
|
|
|
2019-04-29 19:45:29 -04:00
|
|
|
pub fn add_sample(
|
|
|
|
&mut self,
|
|
|
|
stats: RenderStats,
|
|
|
|
tile_time: Duration,
|
2019-05-07 14:22:24 -04:00
|
|
|
rendering_time: Option<RenderTime>,
|
2019-04-29 19:45:29 -04:00
|
|
|
) {
|
|
|
|
self.cpu_samples.push(CPUSample {
|
|
|
|
stats,
|
|
|
|
elapsed: tile_time,
|
|
|
|
});
|
2019-05-07 14:22:24 -04:00
|
|
|
if let Some(time) = rendering_time {
|
|
|
|
self.gpu_samples.push(GPUSample { time })
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 17:55:32 -05:00
|
|
|
pub fn draw(&self, device: &D) {
|
2019-05-07 14:22:24 -04:00
|
|
|
let mean_cpu_sample = self.cpu_samples.mean();
|
|
|
|
self.draw_stats_window(device, &mean_cpu_sample);
|
|
|
|
self.draw_performance_window(device, &mean_cpu_sample);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_stats_window(&self, device: &D, mean_cpu_sample: &CPUSample) {
|
2019-05-06 18:36:39 -04:00
|
|
|
let framebuffer_size = self.ui_presenter.framebuffer_size();
|
2019-03-05 18:13:55 -05:00
|
|
|
let bottom = framebuffer_size.y() - PADDING;
|
2019-05-29 22:13:42 -04:00
|
|
|
let window_rect = RectI::new(
|
2019-06-03 15:39:29 -04:00
|
|
|
Vector2I::new(
|
2019-05-07 14:22:24 -04:00
|
|
|
framebuffer_size.x() - PADDING - STATS_WINDOW_WIDTH,
|
|
|
|
bottom - PERFORMANCE_WINDOW_HEIGHT - PADDING - STATS_WINDOW_HEIGHT,
|
2019-04-29 19:45:29 -04:00
|
|
|
),
|
2019-06-03 15:39:29 -04:00
|
|
|
Vector2I::new(STATS_WINDOW_WIDTH, STATS_WINDOW_HEIGHT),
|
2019-04-29 19:45:29 -04:00
|
|
|
);
|
2019-05-07 14:22:24 -04:00
|
|
|
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
|
2019-02-25 19:12:47 -05:00
|
|
|
|
2019-06-03 15:39:29 -04:00
|
|
|
let origin = window_rect.origin() + Vector2I::new(PADDING, PADDING + FONT_ASCENT);
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
2019-05-10 15:03:38 -04:00
|
|
|
&format!("Paths: {}", mean_cpu_sample.stats.path_count),
|
2019-04-29 19:45:29 -04:00
|
|
|
origin,
|
|
|
|
false,
|
|
|
|
);
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
|
|
|
&format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 1),
|
2019-04-29 19:45:29 -04:00
|
|
|
false,
|
|
|
|
);
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
|
|
|
&format!("Alpha Tiles: {}", mean_cpu_sample.stats.alpha_tile_count),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 2),
|
2019-04-29 19:45:29 -04:00
|
|
|
false,
|
|
|
|
);
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
|
|
|
&format!("Fills: {}", mean_cpu_sample.stats.fill_count),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 3),
|
2019-04-29 19:45:29 -04:00
|
|
|
false,
|
|
|
|
);
|
2019-05-07 14:22:24 -04:00
|
|
|
}
|
2019-04-29 19:45:29 -04:00
|
|
|
|
2019-05-07 14:22:24 -04:00
|
|
|
fn draw_performance_window(&self, device: &D, mean_cpu_sample: &CPUSample) {
|
|
|
|
let framebuffer_size = self.ui_presenter.framebuffer_size();
|
|
|
|
let bottom = framebuffer_size.y() - PADDING;
|
2019-05-29 22:13:42 -04:00
|
|
|
let window_rect = RectI::new(
|
2019-06-03 15:39:29 -04:00
|
|
|
Vector2I::new(
|
2019-05-07 14:22:24 -04:00
|
|
|
framebuffer_size.x() - PADDING - PERFORMANCE_WINDOW_WIDTH,
|
|
|
|
bottom - PERFORMANCE_WINDOW_HEIGHT,
|
|
|
|
),
|
2019-06-03 15:39:29 -04:00
|
|
|
Vector2I::new(PERFORMANCE_WINDOW_WIDTH, PERFORMANCE_WINDOW_HEIGHT),
|
2019-05-07 14:22:24 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
self.ui_presenter.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
|
|
|
|
|
2019-06-03 15:39:29 -04:00
|
|
|
let origin = window_rect.origin() + Vector2I::new(PADDING, PADDING + FONT_ASCENT);
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
|
|
|
&format!(
|
2019-05-07 14:22:24 -04:00
|
|
|
"Stage 0 CPU: {:.3} ms",
|
2019-04-29 19:45:29 -04:00
|
|
|
duration_to_ms(mean_cpu_sample.elapsed)
|
|
|
|
),
|
2019-05-07 14:22:24 -04:00
|
|
|
origin,
|
2019-04-29 19:45:29 -04:00
|
|
|
false,
|
|
|
|
);
|
2019-02-25 19:12:47 -05:00
|
|
|
|
|
|
|
let mean_gpu_sample = self.gpu_samples.mean();
|
2019-05-06 18:36:39 -04:00
|
|
|
self.ui_presenter.draw_text(
|
2019-04-29 19:45:29 -04:00
|
|
|
device,
|
|
|
|
&format!(
|
2019-05-07 14:22:24 -04:00
|
|
|
"Stage 0 GPU: {:.3} ms",
|
|
|
|
duration_to_ms(mean_gpu_sample.time.stage_0)
|
|
|
|
),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 1),
|
2019-05-07 14:22:24 -04:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
self.ui_presenter.draw_text(
|
|
|
|
device,
|
|
|
|
&format!(
|
|
|
|
"Stage 1 GPU: {:.3} ms",
|
|
|
|
duration_to_ms(mean_gpu_sample.time.stage_1)
|
2019-04-29 19:45:29 -04:00
|
|
|
),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 2),
|
2019-05-07 14:22:24 -04:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
|
|
|
let wallclock_time = f64::max(duration_to_ms(mean_gpu_sample.time.stage_0),
|
|
|
|
duration_to_ms(mean_cpu_sample.elapsed)) +
|
|
|
|
duration_to_ms(mean_gpu_sample.time.stage_1);
|
|
|
|
self.ui_presenter.draw_text(
|
|
|
|
device,
|
|
|
|
&format!("Wallclock: {:.3} ms", wallclock_time),
|
2019-06-03 15:39:29 -04:00
|
|
|
origin + Vector2I::new(0, LINE_HEIGHT * 3),
|
2019-04-29 19:45:29 -04:00
|
|
|
false,
|
|
|
|
);
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
2019-05-07 14:22:24 -04:00
|
|
|
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
|
|
|
|
2019-04-29 19:45:29 -04:00
|
|
|
struct SampleBuffer<S>
|
|
|
|
where
|
|
|
|
S: Add<S, Output = S> + Div<usize, Output = S> + Clone + Default,
|
|
|
|
{
|
2019-02-25 19:12:47 -05:00
|
|
|
samples: VecDeque<S>,
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
|
2019-04-29 19:45:29 -04:00
|
|
|
impl<S> SampleBuffer<S>
|
|
|
|
where
|
|
|
|
S: Add<S, Output = S> + Div<usize, Output = S> + Clone + Default,
|
|
|
|
{
|
2019-02-25 19:12:47 -05:00
|
|
|
fn new() -> SampleBuffer<S> {
|
2019-04-29 19:45:29 -04:00
|
|
|
SampleBuffer {
|
|
|
|
samples: VecDeque::with_capacity(SAMPLE_BUFFER_SIZE),
|
|
|
|
}
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2019-04-18 18:09:37 -04:00
|
|
|
mean / self.samples.len()
|
2019-02-07 18:35:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 19:12:47 -05:00
|
|
|
#[derive(Clone, Default)]
|
|
|
|
struct CPUSample {
|
|
|
|
elapsed: Duration,
|
2019-04-18 18:09:37 -04:00
|
|
|
stats: RenderStats,
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Add<CPUSample> for CPUSample {
|
|
|
|
type Output = CPUSample;
|
|
|
|
fn add(self, other: CPUSample) -> CPUSample {
|
2019-04-29 19:45:29 -04:00
|
|
|
CPUSample {
|
|
|
|
elapsed: self.elapsed + other.elapsed,
|
|
|
|
stats: self.stats + other.stats,
|
|
|
|
}
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 18:09:37 -04:00
|
|
|
impl Div<usize> for CPUSample {
|
2019-02-25 19:12:47 -05:00
|
|
|
type Output = CPUSample;
|
2019-04-18 18:09:37 -04:00
|
|
|
fn div(self, divisor: usize) -> CPUSample {
|
2019-04-29 19:45:29 -04:00
|
|
|
CPUSample {
|
|
|
|
elapsed: self.elapsed / (divisor as u32),
|
|
|
|
stats: self.stats / divisor,
|
|
|
|
}
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
struct GPUSample {
|
2019-05-07 14:22:24 -04:00
|
|
|
time: RenderTime,
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Add<GPUSample> for GPUSample {
|
|
|
|
type Output = GPUSample;
|
|
|
|
fn add(self, other: GPUSample) -> GPUSample {
|
2019-04-29 19:45:29 -04:00
|
|
|
GPUSample {
|
2019-05-07 14:22:24 -04:00
|
|
|
time: self.time + other.time,
|
2019-04-29 19:45:29 -04:00
|
|
|
}
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 18:09:37 -04:00
|
|
|
impl Div<usize> for GPUSample {
|
2019-02-25 19:12:47 -05:00
|
|
|
type Output = GPUSample;
|
2019-04-18 18:09:37 -04:00
|
|
|
fn div(self, divisor: usize) -> GPUSample {
|
2019-04-29 19:45:29 -04:00
|
|
|
GPUSample {
|
2019-05-07 14:22:24 -04:00
|
|
|
time: RenderTime {
|
|
|
|
stage_0: self.time.stage_0 / (divisor as u32),
|
|
|
|
stage_1: self.time.stage_1 / (divisor as u32),
|
|
|
|
}
|
2019-04-29 19:45:29 -04:00
|
|
|
}
|
2019-02-25 19:12:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn duration_to_ms(time: Duration) -> f64 {
|
|
|
|
time.as_secs() as f64 * 1000.0 + time.subsec_nanos() as f64 / 1000000.0
|
|
|
|
}
|