Skip to content

Commit

Permalink
WIP benchmarking and testing for renderers
Browse files Browse the repository at this point in the history
This shows `iced_glow` outperforming `iced_wgpu`. Probably not accurate,
some something may be wrong in the rendering and timing here? It should
also test with more primitivies.

Tests pass when combined with iced-rs#1485
and iced-rs#1491.
  • Loading branch information
ids1024 committed Oct 26, 2022
1 parent 8221794 commit 6809f64
Show file tree
Hide file tree
Showing 8 changed files with 611 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ maintenance = { status = "actively-developed" }

[workspace]
members = [
"bench",
"core",
"futures",
"graphics",
Expand Down
26 changes: 26 additions & 0 deletions bench/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "iced_bench"
version = "0.1.0"
edition = "2021"

[dependencies]
iced = { path = "..", features = ["image"] }
iced_glow = { path = "../glow" }
iced_glutin = { path = "../glutin" }
iced_graphics = { path = "../graphics" }
iced_native = { path = "../native" }
iced_wgpu = { path = "../wgpu" }

[dependencies.image_rs]
version = "0.23"
package = "image"
features = ["png"]
default-features = false

[dev-dependencies]
criterion = "0.4.0"
rand = "0.8.5"

[[bench]]
name = "renderer_bench"
harness = false
135 changes: 135 additions & 0 deletions bench/benches/renderer_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use iced_native::{widget, Renderer};

use iced_bench::{glow::GlowBench, render_widget, wgpu::WgpuBench, Bench};

static LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

fn image(size: u32) -> iced_native::image::Handle {
let mut bytes = Vec::with_capacity(size as usize * size as usize * 4);
for _y in 0..size {
for _x in 0..size {
let r = rand::random();
let g = rand::random();
let b = rand::random();
bytes.extend_from_slice(&[r, g, b, 255]);
}
}
iced_native::image::Handle::from_pixels(size, size, bytes)
}

fn text_primitive<
R: iced_native::Renderer<Theme = iced::Theme> + iced_native::text::Renderer,
>(
renderer: &R,
) -> iced_graphics::Primitive {
iced_graphics::Primitive::Text {
content: LOREM_IPSUM.to_string(),
bounds: iced::Rectangle::with_size(iced::Size::new(1024.0, 1024.0)),
color: iced_native::Color::BLACK,
size: f32::from(renderer.default_size()),
font: Default::default(),
horizontal_alignment: iced_native::alignment::Horizontal::Left,
vertical_alignment: iced_native::alignment::Vertical::Top,
}
}

fn text_widget<
R: iced_native::Renderer<Theme = iced::Theme> + iced_native::text::Renderer,
>() -> widget::Text<'static, R> {
widget::helpers::text(LOREM_IPSUM)
}

fn iter_render<
B: Bench,
F: FnMut(&mut iced_graphics::Renderer<B::Backend, iced::Theme>),
>(
b: &mut criterion::Bencher,
bench: &mut B,
mut draw_cb: F,
) {
b.iter(|| {
bench.renderer().clear();
let state = bench.clear();
draw_cb(bench.renderer());
bench.present(state);
})
}

fn bench_function<
B: Bench,
F: FnMut(&mut iced_graphics::Renderer<B::Backend, iced::Theme>),
>(
c: &mut Criterion,
bench: &mut B,
id: &str,
mut draw_cb: F,
) {
c.bench_function(&format!("{} {}", B::BACKEND_NAME, id), |b| {
iter_render(b, bench, |backend| draw_cb(backend));
});

// Write output to file, so there's a way to see that generated
// image is correct.
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR"))
.join(format!("bench-renderers/{}", B::BACKEND_NAME));
std::fs::create_dir_all(&dir).unwrap();
bench
.read_pixels()
.save(&dir.join(&format!("{}.png", id)))
.unwrap();
}

fn generic_benchmark<B: Bench>(c: &mut Criterion, bench: &mut B)
where
B::Backend: iced_graphics::backend::Text,
{
bench_function(c, bench, "draw no primitive", |_renderer| {});
bench_function(c, bench, "draw quad primitive", |renderer| {
renderer.draw_primitive(black_box(iced_graphics::Primitive::Quad {
bounds: iced::Rectangle::with_size(iced::Size::new(256.0, 256.0)),
background: iced_native::Background::Color(
iced_native::Color::BLACK,
),
border_radius: 0.,
border_width: 0.,
border_color: Default::default(),
}));
});
bench_function(c, bench, "draw text primitive", |renderer| {
renderer.draw_primitive(black_box(text_primitive(renderer)));
});
let widget = text_widget();
bench_function(c, bench, "render text", |renderer| {
render_widget(&widget, renderer);
});
let handle = image(1024);
let bounds = iced::Rectangle::with_size(iced::Size::new(1024.0, 1024.0));
bench_function(c, bench, "draw image primitive", |renderer| {
renderer.draw_primitive(iced_graphics::Primitive::Image {
handle: handle.clone(),
bounds,
});
});
}

fn glow_benchmark(c: &mut Criterion) {
let mut bench = GlowBench::new(1024, 1024);

generic_benchmark(c, &mut bench);
}

fn wgpu_benchmark(c: &mut Criterion) {
let mut bench = WgpuBench::new(1024, 1024);

generic_benchmark(c, &mut bench);

let widget = widget::helpers::image(image(1024));
bench_function(c, &mut bench, "render image", |renderer| {
render_widget(&widget, renderer);
});
}

criterion_group!(benches, glow_benchmark, wgpu_benchmark);
criterion_main!(benches);
1 change: 1 addition & 0 deletions bench/examples/render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
132 changes: 132 additions & 0 deletions bench/src/glow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use iced_glow::glow::{self, HasContext};
use iced_glutin::glutin;

fn glutin_context(
width: u32,
height: u32,
) -> glutin::Context<glutin::NotCurrent> {
let el = glutin::event_loop::EventLoop::new();
#[cfg(target_os = "linux")]
use glutin::platform::unix::HeadlessContextExt;
#[cfg(target_os = "linux")]
if let Ok(context) = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)))
.build_surfaceless(&el)
{
return context;
}
glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)))
.build_headless(&el, glutin::dpi::PhysicalSize::new(width, height))
.unwrap()
}

pub struct GlowBench {
_context: glutin::Context<glutin::PossiblyCurrent>,
gl: glow::Context,
renderer: iced_glow::Renderer<iced::Theme>,
viewport: iced_graphics::Viewport,
_framebuffer: glow::NativeFramebuffer,
_renderbuffer: glow::NativeRenderbuffer,
width: u32,
height: u32,
}

impl GlowBench {
pub fn new(width: u32, height: u32) -> Self {
let context =
unsafe { glutin_context(width, height).make_current().unwrap() };
let (gl, framebuffer, renderbuffer);
unsafe {
gl = glow::Context::from_loader_function(|name| {
context.get_proc_address(name)
});
gl.viewport(0, 0, width as i32, height as i32);

renderbuffer = gl.create_renderbuffer().unwrap();
gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
gl.renderbuffer_storage(
glow::RENDERBUFFER,
glow::RGBA8,
width as i32,
height as i32,
);
gl.bind_renderbuffer(glow::RENDERBUFFER, None);

framebuffer = gl.create_framebuffer().unwrap();
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer));
gl.framebuffer_renderbuffer(
glow::FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::RENDERBUFFER,
Some(renderbuffer),
);
assert_eq!(
gl.check_framebuffer_status(glow::FRAMEBUFFER),
glow::FRAMEBUFFER_COMPLETE
);
};
let renderer = iced_glow::Renderer::<iced::Theme>::new(
iced_glow::Backend::new(&gl, Default::default()),
);
let viewport = iced_graphics::Viewport::with_physical_size(
iced::Size::new(width, height),
1.0,
);
Self {
_context: context,
gl,
renderer,
viewport,
_framebuffer: framebuffer,
_renderbuffer: renderbuffer,
width,
height,
}
}
}

impl super::Bench for GlowBench {
type Backend = iced_glow::Backend;
type RenderState = ();
const BACKEND_NAME: &'static str = "glow";

fn clear(&self) {
unsafe {
self.gl.clear_color(1., 1., 1., 1.);
self.gl.clear(glow::COLOR_BUFFER_BIT);
}
}

fn present(&mut self, _state: ()) {
self.renderer.with_primitives(|backend, primitive| {
backend.present::<&str>(&self.gl, primitive, &self.viewport, &[]);
});
unsafe { self.gl.finish() };
}

fn read_pixels(&self) -> image_rs::RgbaImage {
let mut pixels = image_rs::RgbaImage::new(self.width, self.height);
unsafe {
self.gl.read_pixels(
0,
0,
self.width as i32,
self.height as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelPackData::Slice(&mut pixels),
);
}
image_rs::imageops::flip_vertical_in_place(&mut pixels);
pixels
}

fn size(&self) -> (u32, u32) {
(self.width, self.height)
}

fn renderer(&mut self) -> &mut iced_glow::Renderer<iced::Theme> {
&mut self.renderer
}
}
39 changes: 39 additions & 0 deletions bench/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pub mod glow;
pub mod wgpu;

pub enum Msg {}

pub fn render_widget<
R: iced_native::Renderer<Theme = iced::Theme>,
W: iced_native::Widget<Msg, R>,
>(
widget: &W,
renderer: &mut R,
) {
let size = iced::Size::new(1024.0, 1024.0);
let node = iced_native::layout::Node::new(size);
let layout = iced_native::Layout::new(&node);
widget.draw(
&iced_native::widget::Tree::empty(),
renderer,
&iced::Theme::Light,
&Default::default(),
layout,
iced::Point::new(0.0, 0.0),
&iced::Rectangle::with_size(size),
);
}

pub trait Bench {
type Backend: iced_graphics::Backend;
type RenderState;
const BACKEND_NAME: &'static str;

fn clear(&self) -> Self::RenderState;
fn present(&mut self, state: Self::RenderState);
fn read_pixels(&self) -> image_rs::RgbaImage;
fn size(&self) -> (u32, u32);
fn renderer(
&mut self,
) -> &mut iced_graphics::Renderer<Self::Backend, iced::Theme>;
}
Loading

0 comments on commit 6809f64

Please sign in to comment.