From 5575e6ea0897e406674e7e4239808fbf9daa07c3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 24 Oct 2022 17:06:02 -0700 Subject: Add image/svg support to `iced_glow` https://github.com/iced-rs/iced/issues/674 Uses image/svg support in `iced_graphics`. The is not currently using an atlas, and uses one texture/draw per image. This should be good enough for now; supporting images with glow is better than not supporting them, and if something else performs better, that improvement can be made without any change to the public API. --- glow/src/backend.rs | 26 ++++- glow/src/image.rs | 212 ++++++++++++++++++++++++++++++++++++++ glow/src/image/textures.rs | 82 +++++++++++++++ glow/src/lib.rs | 2 + glow/src/shader/common/image.frag | 22 ++++ glow/src/shader/common/image.vert | 9 ++ 6 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 glow/src/image.rs create mode 100644 glow/src/image/textures.rs create mode 100644 glow/src/shader/common/image.frag create mode 100644 glow/src/shader/common/image.vert (limited to 'glow/src') diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 21af2ecf..1ba70a49 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,3 +1,5 @@ +#[cfg(any(feature = "image_rs", feature = "svg"))] +use crate::image; use crate::quad; use crate::text; use crate::{program, triangle}; @@ -15,6 +17,8 @@ use iced_native::{Font, Size}; /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Backend { + #[cfg(any(feature = "image_rs", feature = "svg"))] + image_pipeline: image::Pipeline, quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, @@ -32,10 +36,14 @@ impl Backend { let shader_version = program::Version::new(gl); + #[cfg(any(feature = "image_rs", feature = "svg"))] + let image_pipeline = image::Pipeline::new(gl, &shader_version); let quad_pipeline = quad::Pipeline::new(gl, &shader_version); let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); Self { + #[cfg(any(feature = "image_rs", feature = "svg"))] + image_pipeline, quad_pipeline, text_pipeline, triangle_pipeline, @@ -70,6 +78,9 @@ impl Backend { viewport_size.height, ); } + + #[cfg(any(feature = "image_rs", feature = "svg"))] + self.image_pipeline.trim_cache(gl); } fn flush( @@ -112,6 +123,15 @@ impl Backend { ); } + #[cfg(any(feature = "image_rs", feature = "svg"))] + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline + .draw(gl, scaled, scale_factor, &layer.images); + } + if !layer.text.is_empty() { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text @@ -236,10 +256,10 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { - (50, 50) + fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + self.image_pipeline.dimensions(handle) } } diff --git a/glow/src/image.rs b/glow/src/image.rs new file mode 100644 index 00000000..51e3016e --- /dev/null +++ b/glow/src/image.rs @@ -0,0 +1,212 @@ +use crate::program::{self, Shader}; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +#[cfg(feature = "image_rs")] +use std::cell::RefCell; + +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + +#[cfg(feature = "image_rs")] +use iced_graphics::image::raster; + +#[cfg(feature = "svg")] +use iced_graphics::image::vector; + +mod textures; +use textures::{Entry, Textures}; + +#[derive(Debug)] +pub(crate) struct Pipeline { + program: ::Program, + vertex_array: ::VertexArray, + vertex_buffer: ::Buffer, + transform_location: ::UniformLocation, + textures: Textures, + #[cfg(feature = "image_rs")] + raster_cache: RefCell>, + #[cfg(feature = "svg")] + vector_cache: vector::Cache, +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("shader/common/image.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("shader/common/image.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[(0, "i_Position")], + ) + }; + + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + unsafe { + gl.use_program(Some(program)); + + let transform: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &transform, + ); + + gl.use_program(None); + } + + let vertex_buffer = + unsafe { gl.create_buffer().expect("Create vertex buffer") }; + let vertex_array = + unsafe { gl.create_vertex_array().expect("Create vertex array") }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + + let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; + gl.buffer_data_size( + glow::ARRAY_BUFFER, + vertices.len() as i32, + glow::STATIC_DRAW, + ); + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(vertices), + ); + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32( + 0, + 2, + glow::UNSIGNED_BYTE, + false, + 0, + 0, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + } + + Pipeline { + program, + vertex_array, + vertex_buffer, + transform_location, + textures: Textures::new(), + #[cfg(feature = "image_rs")] + raster_cache: RefCell::new(raster::Cache::default()), + #[cfg(feature = "svg")] + vector_cache: vector::Cache::default(), + } + } + + #[cfg(feature = "image_rs")] + pub fn dimensions( + &self, + handle: &iced_native::image::Handle, + ) -> (u32, u32) { + self.raster_cache.borrow_mut().load(handle).dimensions() + } + + pub fn draw( + &mut self, + mut gl: &glow::Context, + transformation: Transformation, + _scale_factor: f32, + images: &[layer::Image], + ) { + unsafe { + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + } + + #[cfg(feature = "image_rs")] + let mut raster_cache = self.raster_cache.borrow_mut(); + for image in images { + let (entry, bounds) = match &image { + #[cfg(feature = "image_rs")] + layer::Image::Raster { handle, bounds } => ( + raster_cache.upload(handle, &mut gl, &mut self.textures), + bounds, + ), + #[cfg(not(feature = "image_rs"))] + layer::Image::Raster { handle: _, bounds } => (None, bounds), + + #[cfg(feature = "svg")] + layer::Image::Vector { handle, bounds } => { + let size = [bounds.width, bounds.height]; + ( + self.vector_cache.upload( + handle, + size, + _scale_factor, + &mut gl, + &mut self.textures, + ), + bounds, + ) + } + + #[cfg(not(feature = "svg"))] + layer::Image::Vector { handle: _, bounds } => (None, bounds), + }; + + unsafe { + if let Some(Entry { texture, .. }) = entry { + gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) + } else { + continue; + } + + let translate = Transformation::translate(bounds.x, bounds.y); + let scale = Transformation::scale(bounds.width, bounds.height); + let transformation = transformation * translate * scale; + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); + + gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); + + gl.bind_texture(glow::TEXTURE_2D, None); + } + } + + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + gl.use_program(None); + } + } + + pub fn trim_cache(&mut self, mut gl: &glow::Context) { + #[cfg(feature = "image_rs")] + self.raster_cache + .borrow_mut() + .trim(&mut self.textures, &mut gl); + + #[cfg(feature = "svg")] + self.vector_cache.trim(&mut self.textures, &mut gl); + } +} diff --git a/glow/src/image/textures.rs b/glow/src/image/textures.rs new file mode 100644 index 00000000..f43cae1c --- /dev/null +++ b/glow/src/image/textures.rs @@ -0,0 +1,82 @@ +use glow::HasContext; +use iced_graphics::image::{TextureStore, TextureStoreEntry}; + +#[derive(Debug)] +pub struct Textures; + +impl Textures { + pub fn new() -> Self { + Self + } +} + +impl TextureStore for Textures { + type Entry = Entry; + type State<'a> = &'a glow::Context; + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + gl: &mut &glow::Context, + ) -> Option { + unsafe { + let texture = gl.create_texture().expect("create texture"); + gl.bind_texture(glow::TEXTURE_2D, Some(texture)); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::SRGB8_ALPHA8 as i32, + width as i32, + height as i32, + 0, + glow::BGRA, + glow::UNSIGNED_BYTE, + Some(data), + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_S, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_T, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.bind_texture(glow::TEXTURE_2D, None); + + Some(Entry { + size: (width, height), + texture, + }) + } + } + + fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { + unsafe { gl.delete_texture(entry.texture) } + } +} + +#[derive(Debug)] +pub struct Entry { + size: (u32, u32), + pub texture: glow::NativeTexture, +} + +impl TextureStoreEntry for Entry { + fn size(&self) -> (u32, u32) { + self.size + } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs index de9c0002..daeb3e32 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -24,6 +24,8 @@ pub use glow; mod backend; +#[cfg(any(feature = "image_rs", feature = "svg"))] +mod image; mod program; mod quad; mod text; diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag new file mode 100644 index 00000000..5e05abdf --- /dev/null +++ b/glow/src/shader/common/image.frag @@ -0,0 +1,22 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +uniform sampler2D tex; +in vec2 tex_pos; + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif +#ifdef GL_ES +#define texture texture2D +#endif + +void main() { + gl_FragColor = texture(tex, tex_pos); +} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert new file mode 100644 index 00000000..93e541f2 --- /dev/null +++ b/glow/src/shader/common/image.vert @@ -0,0 +1,9 @@ +uniform mat4 u_Transform; + +in vec2 i_Position; +out vec2 tex_pos; + +void main() { + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); + tex_pos = i_Position; +} -- cgit