Skip to content

Commit 74f81db

Browse files
committed
WIP benchmarking and testing for renderers
This shows `iced_glow` outperforming `iced_wgpu`. Probably not accurate, 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.
1 parent 8221794 commit 74f81db

8 files changed

Lines changed: 612 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ maintenance = { status = "actively-developed" }
4848

4949
[workspace]
5050
members = [
51+
"bench",
5152
"core",
5253
"futures",
5354
"graphics",

bench/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "iced_bench"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
iced = { path = "..", features = ["image"] }
8+
iced_glow = { path = "../glow" }
9+
iced_glutin = { path = "../glutin" }
10+
iced_graphics = { path = "../graphics" }
11+
iced_native = { path = "../native" }
12+
iced_wgpu = { path = "../wgpu" }
13+
14+
[dependencies.image_rs]
15+
version = "0.23"
16+
package = "image"
17+
features = ["png"]
18+
default-features = false
19+
20+
[dev-dependencies]
21+
criterion = "0.4.0"
22+
rand = "0.8.5"
23+
24+
[[bench]]
25+
name = "renderer_bench"
26+
harness = false

bench/benches/renderer_bench.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
3+
use iced_native::{widget, Renderer};
4+
5+
use iced_bench::{glow::GlowBench, render_widget, wgpu::WgpuBench, Bench};
6+
7+
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.";
8+
9+
fn image(size: u32) -> iced_native::image::Handle {
10+
let mut bytes = Vec::with_capacity(size as usize * size as usize * 4);
11+
for _y in 0..size {
12+
for _x in 0..size {
13+
let r = rand::random();
14+
let g = rand::random();
15+
let b = rand::random();
16+
bytes.extend_from_slice(&[r, g, b, 255]);
17+
}
18+
}
19+
iced_native::image::Handle::from_pixels(size, size, bytes)
20+
}
21+
22+
fn text_primitive<
23+
R: iced_native::Renderer<Theme = iced::Theme> + iced_native::text::Renderer,
24+
>(
25+
renderer: &R,
26+
) -> iced_graphics::Primitive {
27+
iced_graphics::Primitive::Text {
28+
content: LOREM_IPSUM.to_string(),
29+
bounds: iced::Rectangle::with_size(iced::Size::new(1024.0, 1024.0)),
30+
color: iced_native::Color::BLACK,
31+
size: f32::from(renderer.default_size()),
32+
font: Default::default(),
33+
horizontal_alignment: iced_native::alignment::Horizontal::Left,
34+
vertical_alignment: iced_native::alignment::Vertical::Top,
35+
}
36+
}
37+
38+
fn text_widget<
39+
R: iced_native::Renderer<Theme = iced::Theme> + iced_native::text::Renderer,
40+
>() -> widget::Text<'static, R> {
41+
widget::helpers::text(LOREM_IPSUM)
42+
}
43+
44+
fn iter_render<
45+
B: Bench,
46+
F: FnMut(&mut iced_graphics::Renderer<B::Backend, iced::Theme>),
47+
>(
48+
b: &mut criterion::Bencher,
49+
bench: &mut B,
50+
mut draw_cb: F,
51+
) {
52+
b.iter(|| {
53+
bench.renderer().clear();
54+
let state = bench.clear();
55+
draw_cb(bench.renderer());
56+
bench.present(state);
57+
})
58+
}
59+
60+
fn bench_function<
61+
B: Bench,
62+
F: FnMut(&mut iced_graphics::Renderer<B::Backend, iced::Theme>),
63+
>(
64+
c: &mut Criterion,
65+
bench: &mut B,
66+
id: &str,
67+
mut draw_cb: F,
68+
) {
69+
c.bench_function(&format!("{} {}", B::BACKEND_NAME, id), |b| {
70+
iter_render(b, bench, |backend| draw_cb(backend));
71+
});
72+
73+
// Write output to file, so there's a way to see that generated
74+
// image is correct.
75+
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR"))
76+
.join(format!("bench-renderers/{}", B::BACKEND_NAME));
77+
std::fs::create_dir_all(&dir).unwrap();
78+
bench
79+
.read_pixels()
80+
.save(&dir.join(&format!("{}.png", id)))
81+
.unwrap();
82+
}
83+
84+
fn generic_benchmark<B: Bench>(c: &mut Criterion, bench: &mut B)
85+
where
86+
B::Backend: iced_graphics::backend::Text,
87+
{
88+
bench_function(c, bench, "draw no primitive", |_renderer| {});
89+
bench_function(c, bench, "draw quad primitive", |renderer| {
90+
renderer.draw_primitive(black_box(iced_graphics::Primitive::Quad {
91+
bounds: iced::Rectangle::with_size(iced::Size::new(256.0, 256.0)),
92+
background: iced_native::Background::Color(
93+
iced_native::Color::BLACK,
94+
),
95+
border_radius: 0.,
96+
border_width: 0.,
97+
border_color: Default::default(),
98+
}));
99+
});
100+
bench_function(c, bench, "draw text primitive", |renderer| {
101+
renderer.draw_primitive(black_box(text_primitive(renderer)));
102+
});
103+
let widget = text_widget();
104+
bench_function(c, bench, "render text", |renderer| {
105+
render_widget(&widget, renderer);
106+
});
107+
let handle = image(1024);
108+
let bounds = iced::Rectangle::with_size(iced::Size::new(1024.0, 1024.0));
109+
bench_function(c, bench, "draw image primitive", |renderer| {
110+
renderer.draw_primitive(iced_graphics::Primitive::Image {
111+
handle: handle.clone(),
112+
bounds,
113+
});
114+
});
115+
}
116+
117+
fn glow_benchmark(c: &mut Criterion) {
118+
let mut bench = GlowBench::new(1024, 1024);
119+
120+
generic_benchmark(c, &mut bench);
121+
}
122+
123+
fn wgpu_benchmark(c: &mut Criterion) {
124+
let mut bench = WgpuBench::new(1024, 1024);
125+
126+
generic_benchmark(c, &mut bench);
127+
128+
let widget = widget::helpers::image(image(1024));
129+
bench_function(c, &mut bench, "render image", |renderer| {
130+
render_widget(&widget, renderer);
131+
});
132+
}
133+
134+
criterion_group!(benches, glow_benchmark, wgpu_benchmark);
135+
criterion_main!(benches);

bench/examples/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}

bench/src/glow.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use iced_glow::glow::{self, HasContext};
2+
use iced_glutin::glutin;
3+
4+
fn glutin_context(
5+
width: u32,
6+
height: u32,
7+
) -> glutin::Context<glutin::NotCurrent> {
8+
let el = glutin::event_loop::EventLoop::new();
9+
#[cfg(target_os = "linux")]
10+
use glutin::platform::unix::HeadlessContextExt;
11+
#[cfg(target_os = "linux")]
12+
if let Ok(context) = glutin::ContextBuilder::new()
13+
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)))
14+
.build_surfaceless(&el)
15+
{
16+
return context;
17+
}
18+
glutin::ContextBuilder::new()
19+
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)))
20+
.build_headless(&el, glutin::dpi::PhysicalSize::new(width, height))
21+
.unwrap()
22+
}
23+
24+
pub struct GlowBench {
25+
_context: glutin::Context<glutin::PossiblyCurrent>,
26+
gl: glow::Context,
27+
renderer: iced_glow::Renderer<iced::Theme>,
28+
viewport: iced_graphics::Viewport,
29+
_framebuffer: glow::NativeFramebuffer,
30+
_renderbuffer: glow::NativeRenderbuffer,
31+
width: u32,
32+
height: u32,
33+
}
34+
35+
impl GlowBench {
36+
pub fn new(width: u32, height: u32) -> Self {
37+
let context =
38+
unsafe { glutin_context(width, height).make_current().unwrap() };
39+
let (gl, framebuffer, renderbuffer);
40+
unsafe {
41+
gl = glow::Context::from_loader_function(|name| {
42+
context.get_proc_address(name)
43+
});
44+
gl.viewport(0, 0, width as i32, height as i32);
45+
46+
renderbuffer = gl.create_renderbuffer().unwrap();
47+
gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
48+
gl.renderbuffer_storage(
49+
glow::RENDERBUFFER,
50+
glow::RGBA8,
51+
width as i32,
52+
height as i32,
53+
);
54+
gl.bind_renderbuffer(glow::RENDERBUFFER, None);
55+
56+
framebuffer = gl.create_framebuffer().unwrap();
57+
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer));
58+
gl.framebuffer_renderbuffer(
59+
glow::FRAMEBUFFER,
60+
glow::COLOR_ATTACHMENT0,
61+
glow::RENDERBUFFER,
62+
Some(renderbuffer),
63+
);
64+
assert_eq!(
65+
gl.check_framebuffer_status(glow::FRAMEBUFFER),
66+
glow::FRAMEBUFFER_COMPLETE
67+
);
68+
};
69+
let renderer = iced_glow::Renderer::<iced::Theme>::new(
70+
iced_glow::Backend::new(&gl, Default::default()),
71+
);
72+
let viewport = iced_graphics::Viewport::with_physical_size(
73+
iced::Size::new(width, height),
74+
1.0,
75+
);
76+
Self {
77+
_context: context,
78+
gl,
79+
renderer,
80+
viewport,
81+
_framebuffer: framebuffer,
82+
_renderbuffer: renderbuffer,
83+
width,
84+
height,
85+
}
86+
}
87+
}
88+
89+
impl super::Bench for GlowBench {
90+
type Backend = iced_glow::Backend;
91+
type RenderState = ();
92+
const BACKEND_NAME: &'static str = "glow";
93+
94+
fn clear(&self) {
95+
unsafe {
96+
self.gl.clear_color(1., 1., 1., 1.);
97+
self.gl.clear(glow::COLOR_BUFFER_BIT);
98+
}
99+
}
100+
101+
fn present(&mut self, _state: ()) {
102+
self.renderer.with_primitives(|backend, primitive| {
103+
backend.present::<&str>(&self.gl, primitive, &self.viewport, &[]);
104+
});
105+
unsafe { self.gl.finish() };
106+
}
107+
108+
fn read_pixels(&self) -> image_rs::RgbaImage {
109+
let mut pixels = image_rs::RgbaImage::new(self.width, self.height);
110+
unsafe {
111+
self.gl.read_pixels(
112+
0,
113+
0,
114+
self.width as i32,
115+
self.height as i32,
116+
glow::RGBA,
117+
glow::UNSIGNED_BYTE,
118+
glow::PixelPackData::Slice(&mut pixels),
119+
);
120+
}
121+
image_rs::imageops::flip_vertical_in_place(&mut pixels);
122+
pixels
123+
}
124+
125+
fn size(&self) -> (u32, u32) {
126+
(self.width, self.height)
127+
}
128+
129+
fn renderer(&mut self) -> &mut iced_glow::Renderer<iced::Theme> {
130+
&mut self.renderer
131+
}
132+
}

bench/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
pub mod glow;
2+
pub mod wgpu;
3+
4+
pub enum Msg {}
5+
6+
pub fn render_widget<
7+
R: iced_native::Renderer<Theme = iced::Theme>,
8+
W: iced_native::Widget<Msg, R>,
9+
>(
10+
widget: &W,
11+
renderer: &mut R,
12+
) {
13+
let size = iced::Size::new(1024.0, 1024.0);
14+
let node = iced_native::layout::Node::new(size);
15+
let layout = iced_native::Layout::new(&node);
16+
widget.draw(
17+
&iced_native::widget::Tree::empty(),
18+
renderer,
19+
&iced::Theme::Light,
20+
&Default::default(),
21+
layout,
22+
iced::Point::new(0.0, 0.0),
23+
&iced::Rectangle::with_size(size),
24+
);
25+
}
26+
27+
pub trait Bench {
28+
type Backend: iced_graphics::Backend;
29+
type RenderState;
30+
const BACKEND_NAME: &'static str;
31+
32+
fn clear(&self) -> Self::RenderState;
33+
fn present(&mut self, state: Self::RenderState);
34+
fn read_pixels(&self) -> image_rs::RgbaImage;
35+
fn size(&self) -> (u32, u32);
36+
fn renderer(
37+
&mut self,
38+
) -> &mut iced_graphics::Renderer<Self::Backend, iced::Theme>;
39+
}

0 commit comments

Comments
 (0)