blob: 6d5932f90e392f01127407552e93590dcb358928 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{
fs::File,
io::Write,
time::{Duration, Instant},
};
use forma::{
buffer::{
layout::{Layout, LinearLayout},
BufferBuilder, BufferLayerCache,
},
Color, Composition, CpuRenderer, GpuRenderer, BGR1,
};
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use winit::{
dpi::PhysicalSize,
event::VirtualKeyCode,
event_loop::EventLoop,
window::{Window, WindowBuilder},
};
use crate::{App, Keyboard, Runner};
fn statistics(durations: &mut Vec<f64>) -> (f64, f64, f64) {
let min = durations.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).copied().unwrap();
let max = durations.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).copied().unwrap();
let count = durations.len() as f64;
(durations.drain(..).sum::<f64>() / count, min, max)
}
fn measure<F: FnOnce()>(f: F) -> Duration {
let start = Instant::now();
f();
start.elapsed()
}
#[derive(Debug)]
pub struct CpuRunner {
composition: Composition,
renderer: CpuRenderer,
layer_cache: BufferLayerCache,
window: Window,
layout: LinearLayout,
pixels: Pixels,
compose_durations: Vec<f64>,
render_durations: Vec<f64>,
}
impl CpuRunner {
pub fn new(event_loop: &EventLoop<()>, width: u32, height: u32) -> Self {
let composition = Composition::new();
let mut renderer = CpuRenderer::new();
let layer_cache = renderer.create_buffer_layer_cache().unwrap();
let window = WindowBuilder::new()
.with_title("demo | compose: ???ms, render: ???ms")
.with_inner_size(PhysicalSize::new(width, height))
.build(event_loop)
.unwrap();
let layout = LinearLayout::new(width as usize, width as usize * 4, height as usize);
let surface_texture = SurfaceTexture::new(width as u32, height as u32, &window);
let pixels = PixelsBuilder::new(width as u32, height as u32, surface_texture)
.texture_format(wgpu::TextureFormat::Bgra8UnormSrgb)
.blend_state(wgpu::BlendState::REPLACE)
.build()
.unwrap();
Self {
composition,
renderer,
layer_cache,
window,
layout,
pixels,
compose_durations: Vec::new(),
render_durations: Vec::new(),
}
}
}
impl Runner for CpuRunner {
fn resize(&mut self, width: u32, height: u32) {
self.pixels.resize_surface(width, height);
self.pixels.resize_buffer(width, height);
self.layout = LinearLayout::new(width as usize, width as usize * 4, height as usize);
}
fn render(&mut self, app: &mut dyn App, elapsed: Duration, keyboard: &Keyboard) {
if self.compose_durations.len() == 50 {
let (compose_avg, compose_min, compose_max) = statistics(&mut self.compose_durations);
let (render_avg, render_min, render_max) = statistics(&mut self.render_durations);
self.window.set_title(&format!(
"demo | compose: {:.2}ms ({:.2}/{:.2}), render: {:.2}ms ({:.2}/{:.2})",
compose_avg, compose_min, compose_max, render_avg, render_min, render_max,
));
}
let compose_duration = measure(|| {
app.compose(&mut self.composition, elapsed, keyboard);
});
let render_duration = measure(|| {
self.renderer.render(
&mut self.composition,
&mut BufferBuilder::new(self.pixels.get_frame(), &mut self.layout)
.layer_cache(self.layer_cache.clone())
.build(),
BGR1,
Color { r: 1.0, g: 1.0, b: 1.0, a: 0.0 },
None,
);
});
self.compose_durations.push(compose_duration.as_secs_f64() * 1000.0);
self.render_durations.push(render_duration.as_secs_f64() * 1000.0);
self.pixels.render().unwrap();
if keyboard.is_key_down(VirtualKeyCode::S) {
let mut bytes = Vec::with_capacity(self.layout.width() * self.layout.height() * 3);
for pixel in self.pixels.get_frame().chunks(4) {
if let &[b, g, r, _] = pixel {
bytes.push(r);
bytes.push(g);
bytes.push(b);
}
}
let new_path = "capture.ppm";
let mut output = File::options().write(true).create(true).open(new_path).unwrap();
output
.write_all(
format!("P6\n{} {}\n255\n", self.layout.width(), self.layout.height())
.as_bytes(),
)
.unwrap();
output.write_all(&bytes).unwrap();
}
}
}
pub struct GpuRunner {
composition: Composition,
renderer: GpuRenderer,
window: Window,
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface,
config: wgpu::SurfaceConfiguration,
compose_durations: Vec<f64>,
sort_durations: Vec<f64>,
paint_durations: Vec<f64>,
render_durations: Vec<f64>,
}
impl GpuRunner {
pub fn new(
event_loop: &EventLoop<()>,
width: u32,
height: u32,
power_preference: wgpu::PowerPreference,
) -> Self {
let composition = Composition::new();
let window = WindowBuilder::new()
.with_title("demo | compose: ???ms, sort: ???ms, paint: ???ms, render: ???ms")
.with_inner_size(PhysicalSize::new(width, height))
.build(event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
let surface = unsafe { instance.create_surface(&window) };
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
..Default::default()
}))
.expect("failed to find an appropriate adapter");
let adapter_features = adapter.features();
let has_timestamp_query = adapter_features.contains(wgpu::Features::TIMESTAMP_QUERY);
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::TIMESTAMP_QUERY & adapter_features,
limits: wgpu::Limits {
max_texture_dimension_2d: 4096,
max_storage_buffer_binding_size: 1 << 30,
..wgpu::Limits::downlevel_defaults()
},
},
None,
))
.expect("failed to get device");
let swap_chain_format = surface.get_preferred_format(&adapter).unwrap();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swap_chain_format,
width,
height,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&device, &config);
let renderer = GpuRenderer::new(&device, swap_chain_format, has_timestamp_query);
Self {
composition,
renderer,
window,
device,
queue,
surface,
config,
compose_durations: Vec::new(),
sort_durations: Vec::new(),
paint_durations: Vec::new(),
render_durations: Vec::new(),
}
}
}
impl Runner for GpuRunner {
fn resize(&mut self, width: u32, height: u32) {
self.config.width = width;
self.config.height = height;
self.surface.configure(&self.device, &self.config);
}
fn render(&mut self, app: &mut dyn App, elapsed: Duration, keyboard: &Keyboard) {
if self.compose_durations.len() == 50 {
let (compose_avg, compose_min, compose_max) = statistics(&mut self.compose_durations);
let (sort_avg, sort_min, sort_max) = statistics(&mut self.sort_durations);
let (paint_avg, paint_min, paint_max) = statistics(&mut self.paint_durations);
let (render_avg, render_min, render_max) = statistics(&mut self.render_durations);
self.window.set_title(&format!(
"demo | compose: {:.2}ms ({:.2}/{:.2}), sort: {:.2}ms ({:.2}/{:.2}), paint: {:.2}ms ({:.2}/{:.2}), render: {:.2}ms ({:.2}/{:.2})",
compose_avg, compose_min, compose_max, sort_avg, sort_min, sort_max, paint_avg, paint_min, paint_max, render_avg, render_min, render_max,
));
}
let compose_duration = measure(|| {
app.compose(&mut self.composition, elapsed, keyboard);
});
let timings = self.renderer.render(
&mut self.composition,
&self.device,
&self.queue,
&self.surface,
self.config.width,
self.config.height,
Color { r: 1.0, g: 1.0, b: 1.0, a: 0.0 },
);
if let Some(timings) = timings {
self.compose_durations.push(compose_duration.as_secs_f64() * 1000.0);
self.sort_durations.push(timings.sort.as_secs_f64() * 1000.0);
self.paint_durations.push(timings.paint.as_secs_f64() * 1000.0);
self.render_durations.push(timings.render.as_secs_f64() * 1000.0);
}
}
}