diff --git a/Cargo.lock b/Cargo.lock index 8772f5d..f59356b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "anyhow", "async-trait", "cairo-rs", + "crossbeam", "epoxy", "euclid", "femtovg", @@ -429,6 +430,7 @@ dependencies = [ "geo-macros", "geo-types", "geojson 0.24.1", + "gl", "glib", "glib-build-tools", "glib-macros", @@ -631,47 +633,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] -name = "crossbeam-channel" -version = "0.5.8" +name = "crossbeam" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -1358,6 +1373,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator 0.14.0", +] + [[package]] name = "gl_generator" version = "0.9.0" @@ -3846,7 +3870,7 @@ checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" dependencies = [ "proc-macro2 1.0.76", "quote 1.0.35", - "xml-rs 0.7.0", + "xml-rs 0.8.19", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ba3da21..bc86c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,8 @@ once_cell = "1.19.0" relm4-icons = {version="0.6.0",features=["add-filled","delete-filled","chevron-up-filled","chevron-down-filled"]} surfman = "0.8.1" euclid = "0.22.9" +gl = "0.14.0" +crossbeam = "0.8.4" # plotters-cairo = "0.5.0" diff --git a/back.txt b/back.txt index cbf3b09..562849b 100644 --- a/back.txt +++ b/back.txt @@ -343,3 +343,53 @@ pub enum CoordType { // .blue(250 as f32 / 255.0) // .build(), // ]; + + + + // let texture = ctx.create_texture().expect("Cannot create texture"); + + // ctx.bind_texture(glow::TEXTURE_2D, Some(texture)); + // ctx.tex_image_2d( + // glow::TEXTURE_2D, + // 0, + // glow::RGBA as i32, + // 3000, + // 3000, + // 0, + // glow::RGBA, + // glow::UNSIGNED_BYTE, + // None, + // ); + // ctx.tex_parameter_i32( + // glow::TEXTURE_2D, + // glow::TEXTURE_MIN_FILTER, + // glow::LINEAR as i32, + // ); + // ctx.tex_parameter_i32( + // glow::TEXTURE_2D, + // glow::TEXTURE_MAG_FILTER, + // glow::LINEAR as i32, + // ); + // ctx.framebuffer_texture_2d( + // glow::FRAMEBUFFER, + // glow::COLOR_ATTACHMENT0, + // glow::TEXTURE_2D, + // Some(texture), + // 0, + // ); + + // let depth_buffer = ctx + // .create_renderbuffer() + // .expect("Cannot create renderbuffer"); + // ctx.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_buffer)); + // ctx.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_COMPONENT16, 3000, 3000); + // ctx.framebuffer_renderbuffer( + // glow::FRAMEBUFFER, + // glow::DEPTH_ATTACHMENT, + // glow::RENDERBUFFER, + // Some(depth_buffer), + // ); + // // let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) + // // .expect("No GTK provided framebuffer binding"); + + \ No newline at end of file diff --git a/src/components/render_panel/monitor/monitor.rs b/src/components/render_panel/monitor/monitor.rs index 8b1f1e3..08db61e 100644 --- a/src/components/render_panel/monitor/monitor.rs +++ b/src/components/render_panel/monitor/monitor.rs @@ -1,5 +1,6 @@ use crate::pipeline::offscreen_renderer::OffscreenRenderer; use crate::render::{Target, CMS}; +// use crate::OFFSCREEN; use crate::{ components::render_panel::messages::{MonitorInputMsg, MonitorOutputMsg}, coords::{proj::Mercator, Mapper}, @@ -82,8 +83,11 @@ impl Component for MonitorModel { fn update(&mut self, message: Self::Input, sender: ComponentSender, root: &Self::Root) { match message { MonitorInputMsg::AddLayer(layer) => { + // let mut canvas = OFFSCREEN.lock().unwrap(); sender.oneshot_command(async move { - let new_canvas = OffscreenRenderer::new().unwrap(); + let mut back = OffscreenRenderer::new(3000, 3000).unwrap(); + let canvas = back.create_canvas(); + // let new_canvas = OffscreenRenderer::new().unwrap(); let f = { let p = layer.get_prepare(); let mut _p = p.lock().unwrap(); @@ -94,9 +98,9 @@ impl Component for MonitorModel { let map: Mapper = Mercator::default().into(); let cms = CMS::new(map, (3000.0, 3000.0)); - let canvas = new_canvas.create_canvas(); let canvas = Arc::new(Mutex::new(canvas)); let c = f(imp, canvas, cms).await; + Some(c) // None } else { diff --git a/src/main.rs b/src/main.rs index d17d5f5..120cf99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,8 @@ mod utils; -use gtk::prelude::*; use gtk::{gio, glib, Application, ApplicationWindow}; use pipeline::offscreen_renderer::OffscreenRenderer; -use relm4::menu; -use relm4::RelmApp; use std::ptr; use tokio::runtime::Runtime; -use utils::creator; mod chart; mod components; mod coords; @@ -19,13 +15,16 @@ mod render; mod window; use components::app::{AppMode, AppModel}; use once_cell::sync::Lazy; +use std::sync::Mutex; const APP_ID: &str = "org.gtk_rs.HelloWorld2"; static RUNTIME: Lazy = Lazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed.")); -// static OFFSCREEN: Lazy = Lazy::new(|| OffscreenRenderer::new().expect("Can't create offscreen renderer.")); +// static OFFSCREEN: Lazy> = Lazy::new(|| { +// Mutex::new(OffscreenRenderer::new(3000, 3000).expect("Can't create offscreen renderer.")) +// }); fn main() { // Load GL pointers from epoxy (GL context management library used by GTK). diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 11de261..a63b625 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -6,6 +6,7 @@ use ndarray::parallel::prelude::*; use ndarray::{Array2, ArrayView2}; use num_traits::{Num, AsPrimitive, FromPrimitive}; pub mod offscreen_renderer; +mod utils; use crate::{ coords::Mapper, diff --git a/src/pipeline/offscreen_renderer.rs b/src/pipeline/offscreen_renderer.rs index 96239a7..d5fe06e 100644 --- a/src/pipeline/offscreen_renderer.rs +++ b/src/pipeline/offscreen_renderer.rs @@ -1,12 +1,13 @@ +use super::utils::*; use euclid::Size2D; use femtovg::{renderer::OpenGl, Canvas}; -use lazy_static::__Deref; -use std::borrow::BorrowMut; +use gl; +use gl::types::{GLchar, GLenum, GLint, GLuint, GLvoid}; +use glow::HasContext; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::{Mutex, RwLock}; use std::{cell::RefCell, sync::Arc}; -use surfman::platform::system::connection; use surfman::{ device, Adapter, Connection, Context, ContextAttributeFlags, Device, Error, GLApi, NativeConnection, NativeDevice, @@ -16,66 +17,129 @@ pub struct OffscreenRenderer { context: Arc>, device: Device, fbo: NonZeroU32, - // canvas: Arc>, + glow_ctx: Option, + size: (i32, i32), // canvas: Arc>, } impl OffscreenRenderer { - pub fn new() -> Result { + pub fn new(width: i32, height: i32) -> Result { let connection = Connection::new()?; - let adapter = connection.create_hardware_adapter()?; + let adapter = connection.create_adapter()?; let mut device = connection.create_device(&adapter)?; - let api = connection.gl_api(); let descriptor = device.create_context_descriptor(&surfman::ContextAttributes { - version: surfman::GLVersion::new(3, 3), - flags: ContextAttributeFlags::DEPTH.union(ContextAttributeFlags::STENCIL), + version: surfman::GLVersion::new(4, 1), + flags: ContextAttributeFlags::empty(), })?; let mut context = device.create_context(&descriptor, None)?; + let surface = device.create_surface( &context, - surfman::SurfaceAccess::GPUCPU, + surfman::SurfaceAccess::GPUOnly, surfman::SurfaceType::Generic { - size: euclid::Size2D::new(3000, 3000), + size: euclid::Size2D::new(width, height), }, )?; - let surface_info = device.surface_info(&surface); device .bind_surface_to_context(&mut context, surface) .expect("Failed to bind surface to context"); device.make_context_current(&context).unwrap(); + let surface_info = device.context_surface_info(&context).unwrap().unwrap(); + gl::load_with(|symbol_name| device.get_proc_address(&context, symbol_name)); + + unsafe { + debug_assert_eq!(gl::GetError(), gl::NO_ERROR); + gl::BindFramebuffer(gl::FRAMEBUFFER, surface_info.framebuffer_object); + debug_assert_eq!(gl::GetError(), gl::NO_ERROR); + } + Ok(Self { context: Arc::new(RwLock::new(context)), device, fbo: NonZeroU32::new(surface_info.framebuffer_object).unwrap(), + glow_ctx: None, + size: (width, height), }) } - pub fn create_canvas(&self) -> CanvasWrapper { - use glow::HasContext; - static LOAD_FN: fn(&str) -> *const std::ffi::c_void = - |s| epoxy::get_proc_addr(s) as *const _; + pub fn create_canvas(&mut self) -> CanvasWrapper { + let (w, h) = self.size; + let mut pixels: Vec = vec![0; w as usize * h as usize * 4]; - let (mut renderer, fbo) = unsafe { - let renderer = OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer"); - let ctx = glow::Context::from_loader_function(LOAD_FN); + let (mut renderer, fbo, ctx) = unsafe { + let renderer = OpenGl::new_from_function(|s| { + self.device + .get_proc_address(&self.context.read().unwrap(), s) as *const _ + }) + .expect("Cannot create renderer"); - let fbo = ctx.create_framebuffer().expect("can't create framebuffer"); - let cbo = ctx.create_buffer().expect("can't create color buffer"); + let ctx = glow::Context::from_loader_function(|s| { + self.device + .get_proc_address(&self.context.read().unwrap(), s) as *const _ + }); - // let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) - // .expect("No GTK provided framebuffer binding"); + let fbo = glow::NativeFramebuffer(self.fbo); ctx.bind_framebuffer(glow::FRAMEBUFFER, None); - // let fbo = glow::NativeFramebuffer(id); - (renderer, fbo) + (renderer, fbo, ctx) }; + renderer.set_screen_target(Some(fbo)); let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); canvas.set_size(3000, 3000, 1.0); + let img = canvas + .create_image_empty( + 3000, + 3000, + femtovg::PixelFormat::Rgba8, + femtovg::ImageFlags::empty(), + ) + .unwrap(); + + canvas.set_render_target(femtovg::RenderTarget::Image(img)); + let mut path = femtovg::Path::new(); + path.rect(0.0, 0.0, 300.0, 300.0); + canvas.fill_path( + &mut path, + &femtovg::Paint::color(femtovg::Color::rgb(255, 0, 0)), + ); + + unsafe { + ctx.get_tex_image( + glow::TEXTURE_2D, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + glow::PixelPackData::Slice(&mut pixels), + ); + } + + // unsafe { + // let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER); + // println!("status: {}", status); + // gl::ReadPixels( + // 0, + // 0, + // w, + // h, + // gl::RGBA, + // gl::UNSIGNED_BYTE, + // pixels.as_mut_ptr() as *mut GLvoid, + // ); + + // debug_assert_eq!(gl::GetError(), gl::NO_ERROR); + // } + + if let Some(max) = pixels.iter().max() { + println!("The maximum value is {}", max); + } else { + println!("The vector is empty"); + } + CanvasWrapper::new(canvas) } } diff --git a/src/pipeline/utils.rs b/src/pipeline/utils.rs new file mode 100644 index 0000000..210025b --- /dev/null +++ b/src/pipeline/utils.rs @@ -0,0 +1,171 @@ +// examples/common/mod.rs +// +// OpenGL convenience wrappers used in the examples. + +use gl; +use gl::types::{GLchar, GLenum, GLint, GLuint}; +use std::fs::File; +use std::io::Read; +use std::os::raw::c_void; +use std::ptr; +use surfman::GLApi; + +pub struct Program { + pub object: GLuint, + #[allow(dead_code)] + vertex_shader: Shader, + #[allow(dead_code)] + fragment_shader: Shader, +} + +impl Program { + pub fn new(vertex_shader: Shader, fragment_shader: Shader) -> Program { + unsafe { + let program = gl::CreateProgram(); + ck(); + gl::AttachShader(program, vertex_shader.object); + ck(); + gl::AttachShader(program, fragment_shader.object); + ck(); + gl::LinkProgram(program); + ck(); + Program { + object: program, + vertex_shader, + fragment_shader, + } + } + } +} + +pub struct Shader { + object: GLuint, +} + +impl Shader { + pub fn new( + name: &str, + kind: ShaderKind, + gl_api: GLApi, + gl_texture_target: GLenum, + resource_loader: &dyn ResourceLoader, + ) -> Shader { + let mut source = vec![]; + match gl_api { + GLApi::GL => source.extend_from_slice(b"#version 330\n"), + GLApi::GLES => source.extend_from_slice(b"#version 300 es\n"), + } + match gl_texture_target { + gl::TEXTURE_2D => {} + gl::TEXTURE_RECTANGLE => source.extend_from_slice(b"#define SAMPLER_RECT\n"), + _ => {} + } + resource_loader.slurp(&mut source, &format!("{}.{}.glsl", name, kind.extension())); + + unsafe { + let shader = gl::CreateShader(kind.to_gl()); + ck(); + gl::ShaderSource( + shader, + 1, + &(source.as_ptr() as *const GLchar), + &(source.len() as GLint), + ); + ck(); + gl::CompileShader(shader); + ck(); + + let mut compile_status = 0; + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut compile_status); + ck(); + if compile_status != gl::TRUE as GLint { + let mut info_log_length = 0; + gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut info_log_length); + let mut info_log = vec![0; info_log_length as usize + 1]; + gl::GetShaderInfoLog( + shader, + info_log_length, + ptr::null_mut(), + info_log.as_mut_ptr() as *mut _, + ); + eprintln!( + "Failed to compile shader:\n{}", + String::from_utf8_lossy(&info_log) + ); + panic!("Shader compilation failed!"); + } + debug_assert_eq!(compile_status, gl::TRUE as GLint); + + Shader { object: shader } + } + } +} + +pub struct Buffer { + pub object: GLuint, +} + +impl Buffer { + pub fn from_data(data: &[u8]) -> Buffer { + unsafe { + let mut buffer = 0; + gl::GenBuffers(1, &mut buffer); + ck(); + gl::BindBuffer(gl::ARRAY_BUFFER, buffer); + ck(); + gl::BufferData( + gl::ARRAY_BUFFER, + data.len() as isize, + data.as_ptr() as *const c_void, + gl::STATIC_DRAW, + ); + ck(); + Buffer { object: buffer } + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ShaderKind { + Vertex, + Fragment, +} + +impl ShaderKind { + fn extension(self) -> &'static str { + match self { + ShaderKind::Vertex => "vs", + ShaderKind::Fragment => "fs", + } + } + + fn to_gl(self) -> GLenum { + match self { + ShaderKind::Vertex => gl::VERTEX_SHADER, + ShaderKind::Fragment => gl::FRAGMENT_SHADER, + } + } +} + +pub trait ResourceLoader { + fn slurp(&self, dest: &mut Vec, filename: &str); +} + +#[allow(dead_code)] +pub struct FilesystemResourceLoader; + +impl ResourceLoader for FilesystemResourceLoader { + fn slurp(&self, dest: &mut Vec, filename: &str) { + let path = format!("resources/examples/{}", filename); + File::open(&path) + .expect("Failed to open file!") + .read_to_end(dest) + .unwrap(); + } +} + +pub fn ck() { + unsafe { + debug_assert_eq!(gl::GetError(), gl::NO_ERROR); + } +} \ No newline at end of file