opengl monitor

This commit is contained in:
sleptworld 2023-07-06 08:44:20 +08:00
parent 7e2ef5558a
commit 145e09ef53
16 changed files with 914 additions and 742778 deletions

714
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,11 @@ quadtree_rs = "0.1.2"
proj-sys = "0.23.1"
glib-macros = "0.17.10"
svg = "0.13.1"
libloading = "0.8.0"
glue = "0.8.7"
epoxy = "0.1.0"
femtovg = "0.7.1"
glow = "0.12.2"
[build-dependencies]

View File

@ -2,6 +2,6 @@ fn main() {
glib_build_tools::compile_resources(
&["src/resources"],
"src/resources/resources.gresource.xml",
"window.gresource",
"monitor.gresource",
);
}
}

View File

@ -193,20 +193,6 @@ where
});
result
}
pub fn dpi(&self, axis: Axis) {
match self.coord_type {
CoordType::LatLon => {
if axis == Axis::Asix0 {
let mut new_dim1 = Array::zeros(self.dim1.raw_dim());
let a = self.dim1.slice(s![0..3, ..]);
} else if axis == Axis::Asix1 {
} else {
}
}
_ => {}
}
}
}
#[inline]

View File

@ -1,6 +1,4 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use std::ptr;
use glib::{timeout_add, timeout_add_local};
use glib_macros::clone;
@ -8,30 +6,48 @@ use gtk::gdk::builders::RGBABuilder;
use gtk::gdk::Display;
use gtk::{
gio, glib, style_context_add_provider_for_display, Application, ApplicationWindow, CssProvider,
GestureDrag,
FontButton, GestureDrag,
};
use gtk::{prelude::*, DrawingArea};
mod data;
mod monitor;
mod painter;
mod render;
mod tree;
mod window;
use data::{Npz, Radar2d, RadarData2d};
use monitor::Monitor;
use ndarray::parallel::prelude::{IntoParallelIterator, ParallelIterator};
use num_traits::{FromPrimitive, Num};
use painter::wgs84::{LatLonCoord, Mercator, ProjectionS, Range};
use painter::{Coord, Painter};
use svg::node::element::Rectangle;
use render::Render;
use render::{BackgroundConfig, BackgroundWidget};
const APP_ID: &str = "org.gtk_rs.HelloWorld2";
fn main() -> glib::ExitCode {
// gio::resources_register_include!("window.gresource").expect("Failed to register resources");
// Load GL pointers from epoxy (GL context management library used by GTK).
{
#[cfg(target_os = "macos")]
let library = unsafe { libloading::os::unix::Library::new("libepoxy.0.dylib") }.unwrap();
#[cfg(all(unix, not(target_os = "macos")))]
let library = unsafe { libloading::os::unix::Library::new("libepoxy.so.0") }.unwrap();
#[cfg(windows)]
let library = libloading::os::windows::Library::open_already_loaded("libepoxy-0.dll")
.or_else(|_| libloading::os::windows::Library::open_already_loaded("epoxy-0.dll"))
.unwrap();
epoxy::load_with(|name| {
unsafe { library.get::<_>(name.as_bytes()) }
.map(|symbol| *symbol)
.unwrap_or(ptr::null())
});
}
// gio::resources_register_include!("monitor.gresource").expect("Failed to register resources");
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_activate(build_ui);
// Run the application
app.run()
}
@ -43,19 +59,27 @@ fn build_ui(app: &Application) {
.title("My GTK App")
.build();
let drawing_area = DrawingArea::new();
let path = "/Users/ruomu/test2.npz";
let mut background_config = BackgroundConfig::new();
let data = Radar2d::<i8>::load(path, Npz).unwrap();
let background_widget = BackgroundWidget::new(background_config);
let projection = Mercator::new();
let aa = Range::from((*data.dim1.first().unwrap(), *data.dim1.last().unwrap()));
let ab = Range::from((*data.dim2.first().unwrap(), *data.dim2.last().unwrap()));
let coord = LatLonCoord::new(Some(aa), Some(ab), ((0, 1000), (0, 1000)), projection);
let drawing_area = Render::new();
let result = data.mapped(&coord);
// let path = "/Users/ruomu/test2.npz";
window.set_child(Some(&drawing_area));
// let data = Radar2d::<i8>::load(path, Npz).unwrap();
// let projection = Mercator::new();
// let aa = Range::from((*data.dim1.first().unwrap(), *data.dim1.last().unwrap()));
// let ab = Range::from((*data.dim2.first().unwrap(), *data.dim2.last().unwrap()));
// let coord = LatLonCoord::new(Some(aa), Some(ab), ((0, 1000), (0, 1000)), projection);
// let result = data.mapped(&coord);
let monitor = Monitor::new();
window.set_child(Some(&monitor));
window.set_default_width(1000);
window.set_default_height(800);

34
src/monitor/imp.rs Normal file
View File

@ -0,0 +1,34 @@
use crate::render::Render;
use glib::subclass::InitializingObject;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
use gtk::{glib, prelude::WidgetExtManual};
use std::cell::{Cell, RefCell};
#[derive(Default, CompositeTemplate)]
#[template(file = "monitor.ui")]
pub struct Monitor {
#[template_child(id = "render")]
pub(super) label: TemplateChild<Render>,
}
#[glib::object_subclass]
impl ObjectSubclass for Monitor {
const NAME: &'static str = "Monitor";
type Type = super::Monitor;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for Monitor {}
impl WidgetImpl for Monitor {}
impl BoxImpl for Monitor {}

16
src/monitor/mod.rs Normal file
View File

@ -0,0 +1,16 @@
mod imp;
use crate::render::Render;
use glib::subclass::prelude::*;
use gtk::traits::WidgetExt;
glib::wrapper! {
pub struct Monitor(ObjectSubclass<imp::Monitor>)
@extends gtk::Box,gtk::Widget;
}
impl Monitor {
pub fn new() -> Self {
let this: Self = glib::Object::new();
this
}
}

10
src/monitor/monitor.ui Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="Monitor" parent="GtkBox">
<child>
<object class="MyRender" id="render">
<property name="hexpand">True</property>
</object>
</child>
</template>
</interface>

View File

@ -9,6 +9,92 @@ type Lon = f64;
#[derive(Clone, Copy)]
pub struct Range(pub f64, pub f64);
impl Range {
pub fn key_points(&self, max_points: usize) -> Vec<f64> {
if max_points == 0 {
return vec![];
}
let range = (self.0.min(self.1) as f64, self.1.max(self.0) as f64);
assert!(!(range.0.is_nan() || range.1.is_nan()));
if (range.0 - range.1).abs() < std::f64::EPSILON {
return vec![range.0 as f64];
}
let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor());
// The value granularity controls how we round the values.
// To avoid generating key points like 1.00000000001, we round to the nearest multiple of the
// value granularity.
// By default, we make the granularity as the 1/10 of the scale.
let mut value_granularity = scale / 10.0;
fn rem_euclid(a: f64, b: f64) -> f64 {
let ret = if b > 0.0 {
a - (a / b).floor() * b
} else {
a - (a / b).ceil() * b
};
if (ret - b).abs() < std::f64::EPSILON {
0.0
} else {
ret
}
}
// At this point we need to make sure that the loop invariant:
// The scale must yield number of points than requested
if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points {
scale *= 10.0;
value_granularity *= 10.0;
}
'outer: loop {
let old_scale = scale;
for nxt in [2.0, 5.0, 10.0].iter() {
let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt);
if new_left < range.0 {
new_left += old_scale / nxt;
}
let new_right = range.1 - rem_euclid(range.1, old_scale / nxt);
let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt);
if npoints.round() as usize > max_points {
break 'outer;
}
scale = old_scale / nxt;
}
scale = old_scale / 10.0;
value_granularity /= 10.0;
}
let mut ret = vec![];
// In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to
// floating point error.
// In this case, we may loop forever. To avoid this, we need to use two variables to store
// the current left value. So we need keep a left_base and a left_relative.
let left = {
let mut value = range.0 - rem_euclid(range.0, scale);
if value < range.0 {
value += scale;
}
value
};
let left_base = (left / value_granularity).floor() * value_granularity;
let mut left_relative = left - left_base;
let right = range.1 - rem_euclid(range.1, scale);
while (right - left_relative - left_base) >= -std::f64::EPSILON {
let new_left_relative = (left_relative / value_granularity).round() * value_granularity;
if new_left_relative < 0.0 {
left_relative += value_granularity;
}
ret.push((left_relative + left_base) as f64);
left_relative += scale;
}
return ret;
}
}
#[derive(Error, Debug)]
pub enum CoordError {
#[error("")]

View File

@ -0,0 +1,35 @@
use std::cell::RefCell;
use crate::render::{imp, WindowCoord};
use femtovg::Paint;
use geo_macros::Prj;
use gtk::glib;
use gtk::subclass::prelude::*;
#[derive(Prj, Default)]
pub struct BackgroundConfig {
pub show_lat_lines: bool,
pub show_lon_lines: bool,
pub lat_lines: Vec<Vec<WindowCoord>>,
pub lon_lines: Vec<Vec<WindowCoord>>,
pub painter: Paint,
}
impl BackgroundConfig {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Default)]
pub struct BackgroundWidget {
pub(super) config: RefCell<BackgroundConfig>,
}
#[glib::object_subclass]
impl ObjectSubclass for BackgroundWidget {
const NAME: &'static str = "BackgroundWidget";
type Type = super::BackgroundWidget;
}
impl ObjectImpl for BackgroundWidget {}

View File

@ -0,0 +1,46 @@
mod imp;
use femtovg::{renderer::OpenGl, Canvas, Path};
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::{ffi::gtk_widget_get_width, glib, graphene::Rect, prelude::SnapshotExtManual};
use std::ops::Range;
pub use self::imp::BackgroundConfig;
glib::wrapper! {
pub struct BackgroundWidget(ObjectSubclass<imp::BackgroundWidget>);
}
impl Default for BackgroundWidget {
fn default() -> Self {
Self::new(BackgroundConfig::default())
}
}
impl BackgroundWidget {
pub fn new(config: BackgroundConfig) -> Self {
let this: Self = glib::Object::new();
let imp = this.imp();
imp.config.replace(config);
this
}
fn mesh_lines(&self, canvas: &mut Canvas<OpenGl>) {
let imp = self.imp();
let line_painter = &imp.config.borrow().painter;
if imp.config.borrow().show_lat_lines {
for lat_line in imp.config.borrow().lat_lines.iter() {
let mut path = Path::new();
lat_line.iter().for_each(|v| {
let (x, y) = *v;
path.move_to(x, y);
});
canvas.stroke_path(&mut path, line_painter);
}
}
if imp.config.borrow().show_lon_lines {}
}
pub fn draw(&self, canvas: &mut Canvas<OpenGl>) {
let imp = self.imp();
}
}

View File

@ -1,30 +1,111 @@
use super::background::BackgroundWidget;
use glib::{ObjectExt, ParamSpec, Properties, Value};
use gtk::glib;
use gtk::subclass::prelude::*;
use gtk::traits::{GLAreaExt, WidgetExt};
use gtk::{glib, prelude::WidgetExtManual};
use std::cell::{Cell, RefCell};
use crate::data::Radar2d;
use std::f32::consts::PI;
use std::num::NonZeroU32;
#[derive(Default)]
pub struct Render {
data: RefCell<Option<Radar2d<i8>>>,
// pub(super) background: RefCell<BackgroundWidget>,
canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Render {
const NAME: &'static str = "MyRender";
type Type = super::Render;
type ParentType = gtk::Widget;
type ParentType = gtk::GLArea;
}
// Trait shared by all GObjects
impl ObjectImpl for Render {}
// Trait shared by all widgets
impl WidgetImpl for Render {
fn snapshot(&self, snapshot: &gtk::Snapshot) {
if let Some(data) = self.data.borrow_mut().take() {}
impl ObjectImpl for Render {
fn constructed(&self) {
self.parent_constructed();
let area = self.obj();
area.set_has_stencil_buffer(true);
area.add_tick_callback(|area, _| {
area.queue_render();
glib::Continue(true)
});
}
}
impl DrawingAreaImpl for Render {}
// Trait shared by all widgets
impl WidgetImpl for Render {}
impl GLAreaImpl for Render {
fn resize(&self, width: i32, height: i32) {
self.ensure_canvas();
let mut canvas = self.canvas.borrow_mut();
let canvas = canvas.as_mut().unwrap();
canvas.set_size(
width as u32,
height as u32,
self.obj().scale_factor() as f32,
);
}
fn render(&self, context: &gtk::gdk::GLContext) -> bool {
use femtovg::{Color, Paint, Path};
self.ensure_canvas();
let mut canvas = self.canvas.borrow_mut();
let canvas = canvas.as_mut().unwrap();
let dpi = self.obj().scale_factor();
let w = self.obj().width();
let h = self.obj().width();
canvas.clear_rect(
0,
0,
(w * dpi) as u32,
(h * dpi) as u32,
Color::rgba(0, 0, 0, 255),
);
canvas.fill_text(
0.,
0.,
"hello",
&Paint::color(Color::rgba(255, 255, 255, 0)),
);
canvas.flush();
true
}
}
impl Render {
fn ensure_canvas(&self) {
use femtovg::{renderer, Canvas};
use glow::HasContext;
if self.canvas.borrow().is_some() {
return;
}
let widget = self.obj();
widget.attach_buffers();
static LOAD_FN: fn(&str) -> *const std::ffi::c_void =
|s| epoxy::get_proc_addr(s) as *const _;
// SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg
// knows which framebuffer to bind. This is safe as long as we call attach_buffers
// beforehand. Also unbind it here just in case, since this can be called outside render.
let (mut renderer, fbo) = unsafe {
let renderer =
renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer");
let ctx = glow::Context::from_loader_function(LOAD_FN);
let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32)
.expect("No GTK provided framebuffer binding");
ctx.bind_framebuffer(glow::FRAMEBUFFER, None);
(renderer, glow::NativeFramebuffer(id))
};
renderer.set_screen_target(Some(fbo));
let canvas = Canvas::new(renderer).expect("Cannot create canvas");
self.canvas.replace(Some(canvas));
}
}

View File

@ -1,9 +1,20 @@
mod background;
mod imp;
use glib::Object;
use gtk::glib;
pub use self::background::{BackgroundConfig, BackgroundWidget};
use glib::subclass::prelude::*;
use gtk::{traits::GestureSingleExt, EventControllerScrollFlags};
pub(super) type WindowCoord = (f32, f32);
glib::wrapper! {
pub struct Render(ObjectSubclass<imp::Render>) @extends gtk::Widget;
pub struct Render(ObjectSubclass<imp::Render>)
@extends gtk::GLArea, gtk::Widget;
}
impl Default for Render {
fn default() -> Self {
Self::new()
}
}
impl Render {

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/cinrad_g/">
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
<file compressed="true" preprocess="xml-stripblanks">monitor.ui</file>
</gresource>
</gresources>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="MyGtkAppWindow" parent="GtkApplicationWindow">
<property name="title">My GTK App</property>
<child>
<object class="GtkButton" id="button">
<property name="label">Press me!</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
</object>
</child>
</template>
</interface>

742529
test.svg

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 70 MiB