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.
This commit is contained in:
parent
2c7c42ee93
commit
5575e6ea08
8 changed files with 368 additions and 8 deletions
|
|
@ -15,9 +15,9 @@ resolver = "2"
|
|||
[features]
|
||||
default = ["wgpu"]
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_wgpu/image", "image_rs"]
|
||||
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
|
||||
# Enables the `Svg` widget
|
||||
svg = ["iced_wgpu/svg"]
|
||||
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
|
||||
# Enables the `Canvas` widget
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
# Enables the `QRCode` widget
|
||||
|
|
|
|||
|
|
@ -8,12 +8,23 @@ license = "MIT AND OFL-1.1"
|
|||
repository = "https://github.com/iced-rs/iced"
|
||||
|
||||
[features]
|
||||
svg = ["iced_graphics/svg"]
|
||||
image = ["image_rs", "iced_graphics/image", "png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
|
||||
image_rs = ["iced_graphics/image_rs"]
|
||||
png = ["iced_graphics/png"]
|
||||
jpeg = ["iced_graphics/jpeg"]
|
||||
jpeg_rayon = ["iced_graphics/jpeg_rayon"]
|
||||
gif = ["iced_graphics/gif"]
|
||||
webp = ["iced_graphics/webp"]
|
||||
pnm = ["iced_graphics/pnm"]
|
||||
ico = ["iced_graphics/ico"]
|
||||
bmp = ["iced_graphics/bmp"]
|
||||
hdr = ["iced_graphics/hdr"]
|
||||
dds = ["iced_graphics/dds"]
|
||||
farbfeld = ["iced_graphics/farbfeld"]
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
default_system_font = ["iced_graphics/font-source"]
|
||||
# Not supported yet!
|
||||
image = []
|
||||
svg = []
|
||||
|
||||
[dependencies]
|
||||
glow = "0.11.1"
|
||||
|
|
@ -22,6 +33,8 @@ glyph_brush = "0.7"
|
|||
euclid = "0.22"
|
||||
bytemuck = "1.4"
|
||||
log = "0.4"
|
||||
kamadak-exif = "0.5"
|
||||
bitflags = "1.2"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
212
glow/src/image.rs
Normal file
212
glow/src/image.rs
Normal file
|
|
@ -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: <glow::Context as HasContext>::Program,
|
||||
vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
vertex_buffer: <glow::Context as HasContext>::Buffer,
|
||||
transform_location: <glow::Context as HasContext>::UniformLocation,
|
||||
textures: Textures,
|
||||
#[cfg(feature = "image_rs")]
|
||||
raster_cache: RefCell<raster::Cache<Textures>>,
|
||||
#[cfg(feature = "svg")]
|
||||
vector_cache: vector::Cache<Textures>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
82
glow/src/image/textures.rs
Normal file
82
glow/src/image/textures.rs
Normal file
|
|
@ -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<Self::Entry> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@
|
|||
pub use glow;
|
||||
|
||||
mod backend;
|
||||
#[cfg(any(feature = "image_rs", feature = "svg"))]
|
||||
mod image;
|
||||
mod program;
|
||||
mod quad;
|
||||
mod text;
|
||||
|
|
|
|||
22
glow/src/shader/common/image.frag
Normal file
22
glow/src/shader/common/image.frag
Normal file
|
|
@ -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);
|
||||
}
|
||||
9
glow/src/shader/common/image.vert
Normal file
9
glow/src/shader/common/image.vert
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue