-
Notifications
You must be signed in to change notification settings - Fork 770
Performance improvement for the Plotter example #9836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
89c0cc1
e174038
afb159d
5c11ef2
82a467b
f2713bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,6 @@ | ||
| // Copyright © SixtyFPS GmbH <[email protected]> | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| use plotters::prelude::*; | ||
| use slint::SharedPixelBuffer; | ||
|
|
||
| #[cfg(target_arch = "wasm32")] | ||
| use wasm_bindgen::prelude::*; | ||
|
|
||
|
|
@@ -14,57 +11,57 @@ slint::slint! { | |
| export { MainWindow } from "plotter.slint"; | ||
| } | ||
|
|
||
| fn pdf(x: f64, y: f64, a: f64) -> f64 { | ||
| const SDX: f64 = 0.1; | ||
| const SDY: f64 = 0.1; | ||
| let x = x as f64 / 10.0; | ||
| let y = y as f64 / 10.0; | ||
| a * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp() | ||
| } | ||
|
|
||
| fn render_plot(pitch: f32, yaw: f32, amplitude: f32) -> slint::Image { | ||
| let mut pixel_buffer = SharedPixelBuffer::new(640, 480); | ||
| let size = (pixel_buffer.width(), pixel_buffer.height()); | ||
|
|
||
| let backend = BitMapBackend::with_buffer(pixel_buffer.make_mut_bytes(), size); | ||
|
|
||
| // Plotters requires TrueType fonts from the file system to draw axis text - we skip that for | ||
| // WASM for now. | ||
| #[cfg(target_arch = "wasm32")] | ||
| let backend = wasm_backend::BackendWithoutText { backend }; | ||
|
|
||
| let root = backend.into_drawing_area(); | ||
|
|
||
| root.fill(&WHITE).expect("error filling drawing area"); | ||
|
|
||
| let mut chart = ChartBuilder::on(&root) | ||
| .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0) | ||
| .expect("error building coordinate system"); | ||
| chart.with_projection(|mut p| { | ||
| p.pitch = pitch as f64; | ||
| p.yaw = yaw as f64; | ||
| p.scale = 0.7; | ||
| p.into_matrix() // build the projection matrix | ||
| }); | ||
|
|
||
| chart.configure_axes().draw().expect("error drawing"); | ||
|
|
||
| chart | ||
| .draw_series( | ||
| SurfaceSeries::xoz( | ||
| (-15..=15).map(|x| x as f64 / 5.0), | ||
| (-15..=15).map(|x| x as f64 / 5.0), | ||
| |x, y| pdf(x, y, amplitude as f64), | ||
| fn render_plot(pitch: f32, yaw: f32, amplitude: f32, width: u32, height: u32) -> slint::Image { | ||
| fn probability_density_function(x: f64, y: f64, a: f64) -> f64 { | ||
| const SDX: f64 = 0.1; | ||
| const SDY: f64 = 0.1; | ||
| let x = x / 10.0; | ||
| let y = y / 10.0; | ||
| a * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp() | ||
| } | ||
|
|
||
| use plotters::prelude::*; | ||
| let mut pixel_buffer = slint::SharedPixelBuffer::new(width, height); | ||
| { | ||
| let size = (pixel_buffer.width(), pixel_buffer.height()); | ||
| let root = | ||
| BitMapBackend::with_buffer(pixel_buffer.make_mut_bytes(), size).into_drawing_area(); | ||
| root.fill(&plotters::style::RGBColor(28, 28, 28)).expect("error filling drawing area"); | ||
|
|
||
| let mut chart = ChartBuilder::on(&root) | ||
| .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0) | ||
| .expect("error building coordinate system"); | ||
| chart.with_projection(|mut p| { | ||
| p.pitch = pitch as f64; | ||
| p.yaw = yaw as f64; | ||
| p.scale = 0.7; | ||
| p.into_matrix() | ||
| }); | ||
|
|
||
| let gray = &plotters::style::RGBColor(46, 46, 46); | ||
| chart | ||
| .configure_axes() | ||
| .label_style(("sans-serif", 12).into_font().color(&WHITE)) | ||
| .light_grid_style(gray) | ||
| .bold_grid_style(gray) | ||
| .max_light_lines(4) | ||
| .draw() | ||
| .expect("error drawing"); | ||
| let precision = 30; | ||
| chart | ||
| .draw_series( | ||
| SurfaceSeries::xoz( | ||
| (-precision..=precision).map(|x| x as f64 / (precision as f64 / 3.0)), | ||
| (-precision..=precision).map(|x| x as f64 / (precision as f64 / 3.0)), | ||
| |x, y| probability_density_function(x, y, (amplitude as f64 / 1.0) * 6.0), | ||
| ) | ||
| .style_func(&|&v| { | ||
| (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v / 5.0, 1.0, 0.7)).into() | ||
| }), | ||
| ) | ||
| .style_func(&|&v| { | ||
| (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v / 5.0, 1.0, 0.7)).into() | ||
| }), | ||
| ) | ||
| .expect("error drawing series"); | ||
|
|
||
| root.present().expect("error presenting"); | ||
| drop(chart); | ||
| drop(root); | ||
| .expect("error drawing series"); | ||
| root.present().expect("error presenting"); | ||
| } | ||
|
|
||
| slint::Image::from_rgb8(pixel_buffer) | ||
| } | ||
|
|
@@ -76,9 +73,49 @@ pub fn main() { | |
| #[cfg(all(debug_assertions, target_arch = "wasm32"))] | ||
| console_error_panic_hook::set_once(); | ||
|
|
||
| let main_window = MainWindow::new().unwrap(); | ||
|
|
||
| main_window.on_render_plot(render_plot); | ||
|
|
||
| main_window.run().unwrap(); | ||
| let main_window = MainWindow::new().expect("Cannot create main window"); | ||
| let main_window_weak = main_window.as_weak(); | ||
|
|
||
| let mut current_amplitude = -1.0f32; | ||
| let mut current_pitch = -1.0f32; | ||
| let mut current_yaw = -1.0f32; | ||
| let mut current_width = 0u32; | ||
| let mut current_height = 0u32; | ||
|
|
||
| main_window | ||
| .window() | ||
| .set_rendering_notifier(move |state, _graphics_api| { | ||
| if let slint::RenderingState::BeforeRendering = state { | ||
| if let Some(main_window_strong) = main_window_weak.upgrade() { | ||
| let new_pitch = main_window_strong.get_pitch(); | ||
| let new_yaw = main_window_strong.get_yaw(); | ||
| let new_amplitude = main_window_strong.get_amplitude(); | ||
| let new_width = main_window_strong.get_texture_width() as u32; | ||
| let new_height = main_window_strong.get_texture_height() as u32; | ||
| if current_pitch != new_pitch | ||
| || current_yaw != new_yaw | ||
| || current_amplitude != new_amplitude | ||
| || current_width != new_width | ||
| || current_height != new_height | ||
| { | ||
| current_pitch = new_pitch; | ||
| current_yaw = new_yaw; | ||
| current_amplitude = new_amplitude; | ||
| current_width = new_width; | ||
| current_height = new_height; | ||
| main_window_strong.set_texture(render_plot( | ||
| new_pitch, | ||
| new_yaw, | ||
| new_amplitude, | ||
| new_width, | ||
| new_height, | ||
| )); | ||
| } | ||
| main_window_strong.window().request_redraw(); | ||
| } | ||
| } | ||
| }) | ||
| .expect("Unable to set rendering notifier"); | ||
|
|
||
| main_window.run().expect("Failed to run main window"); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,55 +4,57 @@ | |
| import { Slider, GroupBox, HorizontalBox, VerticalBox } from "std-widgets.slint"; | ||
|
|
||
| export component MainWindow inherits Window { | ||
| in-out property <float> pitch: 0.15; | ||
| in-out property <float> yaw: 0.5; | ||
|
|
||
| pure callback render_plot(/* pitch */ float, /* yaw */ float, /* amplitude */ float) -> image; | ||
|
|
||
| in property <image> texture <=> image.source; | ||
| out property <int> texture-width: image.width / 1phx; | ||
| out property <int> texture-height: image.height / 1phx; | ||
| out property <float> amplitude <=> amplitude.value; | ||
| out property <float> pitch: 0.15; | ||
| out property <float> yaw: 0.5; | ||
| title: "Slint Plotter Integration Example"; | ||
| preferred-width: 800px; | ||
| preferred-height: 600px; | ||
|
|
||
| preferred-width: 999px; | ||
| preferred-height: 720px; | ||
| VerticalBox { | ||
| Text { | ||
| font-size: 20px; | ||
| text: "2D Gaussian PDF"; | ||
| text: "2D Gaussian Probability Density Function"; | ||
| horizontal-alignment: center; | ||
| } | ||
|
|
||
| Image { | ||
| source: root.render_plot(root.pitch, root.yaw, amplitude-slider.value / 10); | ||
| touch := TouchArea { | ||
| property <float> pressed-pitch; | ||
| property <float> pressed-yaw; | ||
|
|
||
| pointer-event(event) => { | ||
| if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { | ||
| self.pressed-pitch = root.pitch; | ||
| self.pressed-yaw = root.yaw; | ||
| HorizontalBox { | ||
| image := Image { | ||
| touch := TouchArea { | ||
| property <float> starting-drag-pitch; | ||
| property <float> starting-drag-yaw; | ||
| pointer-event(event) => { | ||
| if (event.kind == PointerEventKind.down && event.button == PointerEventButton.left) { | ||
| self.starting-drag-pitch = root.pitch; | ||
| self.starting-drag-yaw = root.yaw; | ||
| } | ||
| } | ||
| } | ||
| moved => { | ||
| if (self.enabled && self.pressed) { | ||
| root.pitch = self.pressed-pitch + (touch.mouse-y - touch.pressed-y) / self.height * 3.14; | ||
| root.yaw = self.pressed-yaw - (touch.mouse-x - touch.pressed-x) / self.width * 3.14; | ||
| moved => { | ||
| if (self.enabled && self.pressed) { | ||
| root.pitch = self.starting-drag-pitch + (touch.mouse-y - touch.pressed-y) / self.height * 3.14; | ||
| root.yaw = self.starting-drag-yaw - (touch.mouse-x - touch.pressed-x) / self.width * 3.14; | ||
| } | ||
| } | ||
| mouse-cursor: self.pressed ? MouseCursor.grabbing : MouseCursor.grab; | ||
| } | ||
| mouse-cursor: self.pressed ? MouseCursor.grabbing : MouseCursor.grab; | ||
| } | ||
| } | ||
|
|
||
| HorizontalBox { | ||
| Text { | ||
| text: "Amplitude:"; | ||
| font-weight: 600; | ||
| vertical-alignment: center; | ||
| } | ||
| VerticalBox { | ||
| amplitude := Slider { | ||
| orientation: Orientation.vertical; | ||
| transform-rotation: -180deg; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rotation shouldn't be needed. Instead the we should fix the sliderto be in the right direction.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, technically it should be orientation up, down, right or left, or maybe just vertical / horizontal but where |
||
| minimum: 0; | ||
| maximum: 1; | ||
| value: 0.5; | ||
| } | ||
|
|
||
| amplitude-slider := Slider { | ||
| minimum: 0; | ||
| maximum: 100; | ||
| value: 50; | ||
| Text { | ||
| text: "Amplitude"; | ||
| font-weight: 600; | ||
| vertical-alignment: center; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,4 @@ | |
|
|
||
| edition = "2021" | ||
| use_small_heuristics = "Max" | ||
| max_width = 100 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that not the default? why do you change this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the default which you use, but this specification wasn't there so my formatter used the wrong format |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean that you will force a refresh all the time even if nothing changed?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, no choice there is no resize event in slint and using the texture dimensions requires this
Because Slint also uses the request_redraw so we can't only re-render when we need to because its constantly called anyway by for example the slider thumb animation, and other factors
Even if you were to add for example a callback on
widthandheightchange on image it would spam it because the resizing is not instantaneousAs seen in this earlier issue I recorded, the slider spams render window, because the render loop given is tied to the UI rendering, so they both end up inefficient, its a design flaw
Note that on my main project I stopped using Slint for this exact specific issue ironically
On this video we do not request a redraw every frame so the texture is not automatically updated
And as you can see the slider's position change enforce redraw calls multiple times, rather than compute its final position before rendering, this is likely tied with an issue with Rectangle where when an image is inserted inside a rectangle, it affects the parent rectangle's size
rec.mp4