feat: wip plotters-gtk4

This commit is contained in:
Ilya Zlobintsev 2025-02-08 21:29:04 +02:00
parent 0894f9ae9d
commit 4f1074526e
6 changed files with 102 additions and 39 deletions

38
Cargo.lock generated
View File

@ -1283,6 +1283,7 @@ version = "0.7.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"divan", "divan",
"gtk4",
"lact-cli", "lact-cli",
"lact-daemon", "lact-daemon",
"lact-gui", "lact-gui",
@ -1366,7 +1367,7 @@ dependencies = [
"lact-schema", "lact-schema",
"libadwaita", "libadwaita",
"plotters", "plotters",
"plotters-cairo", "plotters-gtk4",
"pretty_assertions", "pretty_assertions",
"relm4", "relm4",
"relm4-components", "relm4-components",
@ -1776,6 +1777,32 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "pangocairo"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4690509a2fea2a6552a0ef8aa3e5f790c1365365ee0712afa1aedb39af3997b6"
dependencies = [
"cairo-rs",
"glib",
"libc",
"pango",
"pangocairo-sys",
]
[[package]]
name = "pangocairo-sys"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be6ac24147911a6a46783922fc288cf02f67570bc0d360e563b5b26aead6767"
dependencies = [
"cairo-sys-rs",
"glib-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@ -1857,12 +1884,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]] [[package]]
name = "plotters-cairo" name = "plotters-gtk4"
version = "0.7.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e7a3a2567b691ed2f0670ea3cc39988add29c968228b474ec5fe8261b1ff2a5" checksum = "2f18ee173b0c8e6d8cb01c1e32a9b41bf41d74b277a69f22277bab05870e13d7"
dependencies = [ dependencies = [
"cairo-rs", "gtk4",
"pangocairo",
"plotters-backend", "plotters-backend",
] ]

View File

@ -32,13 +32,14 @@ plotters = { version = "0.3.5", default-features = false, features = [
"line_series", "line_series",
"full_palette", "full_palette",
] } ] }
plotters-cairo = "0.7.0" # plotters-cairo = "0.7.0"
cairo-rs = { version = "0.20", default-features = false } cairo-rs = { version = "0.20", default-features = false }
itertools = "0.13.0" itertools = "0.13.0"
thread-priority = "1.1.0" thread-priority = "1.1.0"
divan = { workspace = true, optional = true } divan = { workspace = true, optional = true }
plotters-gtk4 = "0.5.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"

View File

@ -2,6 +2,7 @@ use chrono::NaiveDateTime;
use glib::Properties; use glib::Properties;
use gtk::{glib, prelude::*, subclass::prelude::*}; use gtk::{glib, prelude::*, subclass::prelude::*};
use plotters_gtk4::SnapshotBackend;
use std::cell::Cell; use std::cell::Cell;
use std::cell::RefCell; use std::cell::RefCell;
@ -24,7 +25,7 @@ pub struct Plot {
secondary_y_label_area_relative_size: Cell<f64>, secondary_y_label_area_relative_size: Cell<f64>,
pub(super) data: RefCell<PlotData>, pub(super) data: RefCell<PlotData>,
pub(super) dirty: Cell<bool>, pub(super) dirty: Cell<bool>,
render_thread: RenderThread, // render_thread: RenderThread,
#[property(get, set)] #[property(get, set)]
time_period_seconds: Cell<i64>, time_period_seconds: Cell<i64>,
} }
@ -50,42 +51,58 @@ impl ObjectImpl for Plot {
impl WidgetImpl for Plot { impl WidgetImpl for Plot {
fn snapshot(&self, snapshot: &gtk::Snapshot) { fn snapshot(&self, snapshot: &gtk::Snapshot) {
snapshot.scale(0.25, 0.25);
let width = self.obj().width() as u32; let width = self.obj().width() as u32;
let height = self.obj().height() as u32; let height = self.obj().height() as u32;
if width == 0 || height == 0 { if width == 0 || height == 0 {
return; return;
} }
let request = RenderRequest {
data: self.data.borrow().clone(),
width,
height,
title: self.title.borrow().clone(),
value_suffix: self.value_suffix.borrow().clone(),
secondary_value_suffix: self.secondary_value_suffix.borrow().clone(),
y_label_area_relative_size: self.y_label_area_relative_size.get(),
secondary_y_label_relative_area_size: self.secondary_y_label_area_relative_size.get(),
supersample_factor: 4,
time_period_seconds: self.time_period_seconds.get(),
};
let backend = SnapshotBackend::new(snapshot, (width * 4, height * 4));
request.draw(backend).unwrap();
let last_texture = self.render_thread.get_last_texture(); // let last_texture = self.render_thread.get_last_texture();
let size_changed = last_texture // let size_changed = last_texture
.as_ref() // .as_ref()
.map(|texture| (texture.width() as u32, texture.height() as u32) != (width, height)) // .map(|texture| (texture.width() as u32, texture.height() as u32) != (width, height))
.unwrap_or(true); // .unwrap_or(true);
if self.dirty.replace(false) || size_changed { // if self.dirty.replace(false) || size_changed {
self.render_thread.replace_render_request(RenderRequest { // self.render_thread.replace_render_request(RenderRequest {
data: self.data.borrow().clone(), // data: self.data.borrow().clone(),
width, // width,
height, // height,
title: self.title.borrow().clone(), // title: self.title.borrow().clone(),
value_suffix: self.value_suffix.borrow().clone(), // value_suffix: self.value_suffix.borrow().clone(),
secondary_value_suffix: self.secondary_value_suffix.borrow().clone(), // secondary_value_suffix: self.secondary_value_suffix.borrow().clone(),
y_label_area_relative_size: self.y_label_area_relative_size.get(), // y_label_area_relative_size: self.y_label_area_relative_size.get(),
secondary_y_label_relative_area_size: self // secondary_y_label_relative_area_size: self
.secondary_y_label_area_relative_size // .secondary_y_label_area_relative_size
.get(), // .get(),
supersample_factor: 4, // supersample_factor: 4,
time_period_seconds: self.time_period_seconds.get(), // time_period_seconds: self.time_period_seconds.get(),
}); // });
} // }
// Rendering is always behind by at least one frame, but it's not an issue // // Rendering is always behind by at least one frame, but it's not an issue
if let Some(texture) = last_texture { // if let Some(texture) = last_texture {
let bounds = gtk::graphene::Rect::new(0.0, 0.0, width as f32, height as f32); // let bounds = gtk::graphene::Rect::new(0.0, 0.0, width as f32, height as f32);
// Uses by default Trillinear texture filtering, which is quite good at 4x supersampling // // Uses by default Trillinear texture filtering, which is quite good at 4x supersampling
snapshot.append_texture(&texture, &bounds); // snapshot.append_texture(&texture, &bounds);
} // }
} }
} }

View File

@ -9,7 +9,6 @@ use itertools::Itertools;
use plotters::prelude::*; use plotters::prelude::*;
use plotters::style::colors::full_palette::DEEPORANGE_100; use plotters::style::colors::full_palette::DEEPORANGE_100;
use plotters::style::RelativeSize; use plotters::style::RelativeSize;
use plotters_cairo::CairoBackend;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -131,7 +130,7 @@ impl RenderThread {
} }
fn process_request(render_request: RenderRequest, last_texture: &Mutex<Option<MemoryTexture>>) { fn process_request(render_request: RenderRequest, last_texture: &Mutex<Option<MemoryTexture>>) {
// Create a new ImageSurface for Cairo rendering. /*// Create a new ImageSurface for Cairo rendering.
let mut surface = ImageSurface::create( let mut surface = ImageSurface::create(
cairo::Format::ARgb32, cairo::Format::ARgb32,
(render_request.width * render_request.supersample_factor) as i32, (render_request.width * render_request.supersample_factor) as i32,
@ -178,7 +177,7 @@ fn process_request(render_request: RenderRequest, last_texture: &Mutex<Option<Me
(result, last_texture) => { (result, last_texture) => {
*last_texture = result; *last_texture = result;
} }
}; };*/
} }
// Implement the default constructor for RenderThread using the `new` method. // Implement the default constructor for RenderThread using the `new` method.
@ -400,11 +399,13 @@ mod benches {
use crate::app::graphs_window::plot::PlotData; use crate::app::graphs_window::plot::PlotData;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use divan::{counter::ItemsCount, Bencher}; use divan::{counter::ItemsCount, Bencher};
use gtk::prelude::SnapshotExt;
use plotters_gtk4::SnapshotBackend;
use std::sync::Mutex; use std::sync::Mutex;
#[divan::bench] #[divan::bench]
fn render_plot(bencher: Bencher) { fn render_plot(bencher: Bencher) {
let last_texture = &Mutex::new(None); // let last_texture = &Mutex::new(None);
bencher bencher
.with_inputs(sample_plot_data) .with_inputs(sample_plot_data)
@ -423,7 +424,20 @@ mod benches {
time_period_seconds: 60, time_period_seconds: 60,
}; };
process_request(request, last_texture) let snapshot = gtk::Snapshot::new();
snapshot.scale(
1.0 / request.supersample_factor as f32,
1.0 / request.supersample_factor as f32,
);
let backend = SnapshotBackend::new(
&snapshot,
(
request.width * request.supersample_factor,
request.height * request.supersample_factor,
),
);
request.draw(backend).unwrap();
// process_request(request, last_texture)
}); });
} }

View File

@ -18,6 +18,7 @@ anyhow = { workspace = true }
divan = { workspace = true } divan = { workspace = true }
lact-daemon = { path = "../lact-daemon", features = ["bench"] } lact-daemon = { path = "../lact-daemon", features = ["bench"] }
lact-gui = { path = "../lact-gui", features = ["bench"] } lact-gui = { path = "../lact-gui", features = ["bench"] }
gtk = { version = "0.9", package = "gtk4" }
[[bench]] [[bench]]
name = "bench" name = "bench"

View File

@ -3,5 +3,7 @@ fn main() {
let _ = lact_daemon::run; let _ = lact_daemon::run;
let _ = lact_gui::run; let _ = lact_gui::run;
let _ = gtk::init();
divan::main(); divan::main();
} }