diff options
Diffstat (limited to 'glow')
28 files changed, 1503 insertions, 0 deletions
| diff --git a/glow/Cargo.toml b/glow/Cargo.toml new file mode 100644 index 00000000..3f85e52d --- /dev/null +++ b/glow/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "iced_glow" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +description = "A glow renderer for iced" +license = "MIT AND OFL-1.1" +repository = "https://github.com/hecrj/iced" + +[features] +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.6" +glow_glyph = "0.4" +glyph_brush = "0.7" +euclid = "0.22" +bytemuck = "1.4" +log = "0.4" + +[dependencies.iced_native] +version = "0.3" +path = "../native" + +[dependencies.iced_graphics] +version = "0.1" +path = "../graphics" +features = ["font-fallback", "font-icons", "opengl"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true diff --git a/glow/src/backend.rs b/glow/src/backend.rs new file mode 100644 index 00000000..92bb993e --- /dev/null +++ b/glow/src/backend.rs @@ -0,0 +1,226 @@ +use crate::quad; +use crate::text; +use crate::triangle; +use crate::{Settings, Transformation, Viewport}; +use iced_graphics::backend; +use iced_graphics::font; +use iced_graphics::Layer; +use iced_graphics::Primitive; +use iced_native::mouse; +use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; + +/// A [`glow`] graphics backend for [`iced`]. +/// +/// [`glow`]: https://github.com/grovesNL/glow +/// [`iced`]: https://github.com/hecrj/iced +#[derive(Debug)] +pub struct Backend { +    quad_pipeline: quad::Pipeline, +    text_pipeline: text::Pipeline, +    triangle_pipeline: triangle::Pipeline, +    default_text_size: u16, +} + +impl Backend { +    /// Creates a new [`Backend`]. +    pub fn new(gl: &glow::Context, settings: Settings) -> Self { +        let text_pipeline = text::Pipeline::new(gl, settings.default_font); +        let quad_pipeline = quad::Pipeline::new(gl); +        let triangle_pipeline = triangle::Pipeline::new(gl); + +        Self { +            quad_pipeline, +            text_pipeline, +            triangle_pipeline, +            default_text_size: settings.default_text_size, +        } +    } + +    /// Draws the provided primitives in the default framebuffer. +    /// +    /// The text provided as overlay will be rendered on top of the primitives. +    /// This is useful for rendering debug information. +    pub fn draw<T: AsRef<str>>( +        &mut self, +        gl: &glow::Context, +        viewport: &Viewport, +        (primitive, mouse_interaction): &(Primitive, mouse::Interaction), +        overlay_text: &[T], +    ) -> mouse::Interaction { +        let viewport_size = viewport.physical_size(); +        let scale_factor = viewport.scale_factor() as f32; +        let projection = viewport.projection(); + +        let mut layers = Layer::generate(primitive, viewport); +        layers.push(Layer::overlay(overlay_text, viewport)); + +        for layer in layers { +            self.flush( +                gl, +                scale_factor, +                projection, +                &layer, +                viewport_size.height, +            ); +        } + +        *mouse_interaction +    } + +    fn flush( +        &mut self, +        gl: &glow::Context, +        scale_factor: f32, +        transformation: Transformation, +        layer: &Layer<'_>, +        target_height: u32, +    ) { +        let mut bounds = (layer.bounds * scale_factor).snap(); +        bounds.height = bounds.height.min(target_height); + +        if !layer.quads.is_empty() { +            self.quad_pipeline.draw( +                gl, +                target_height, +                &layer.quads, +                transformation, +                scale_factor, +                bounds, +            ); +        } + +        if !layer.meshes.is_empty() { +            let scaled = transformation +                * Transformation::scale(scale_factor, scale_factor); + +            self.triangle_pipeline.draw( +                gl, +                target_height, +                scaled, +                scale_factor, +                &layer.meshes, +            ); +        } + +        if !layer.text.is_empty() { +            for text in layer.text.iter() { +                // Target physical coordinates directly to avoid blurry text +                let text = glow_glyph::Section { +                    // TODO: We `round` here to avoid rerasterizing text when +                    // its position changes slightly. This can make text feel a +                    // bit "jumpy". We may be able to do better once we improve +                    // our text rendering/caching pipeline. +                    screen_position: ( +                        (text.bounds.x * scale_factor).round(), +                        (text.bounds.y * scale_factor).round(), +                    ), +                    // TODO: Fix precision issues with some scale factors. +                    // +                    // The `ceil` here can cause some words to render on the +                    // same line when they should not. +                    // +                    // Ideally, `wgpu_glyph` should be able to compute layout +                    // using logical positions, and then apply the proper +                    // scaling when rendering. This would ensure that both +                    // measuring and rendering follow the same layout rules. +                    bounds: ( +                        (text.bounds.width * scale_factor).ceil(), +                        (text.bounds.height * scale_factor).ceil(), +                    ), +                    text: vec![glow_glyph::Text { +                        text: text.content, +                        scale: glow_glyph::ab_glyph::PxScale { +                            x: text.size * scale_factor, +                            y: text.size * scale_factor, +                        }, +                        font_id: self.text_pipeline.find_font(text.font), +                        extra: glow_glyph::Extra { +                            color: text.color, +                            z: 0.0, +                        }, +                    }], +                    layout: glow_glyph::Layout::default() +                        .h_align(match text.horizontal_alignment { +                            HorizontalAlignment::Left => { +                                glow_glyph::HorizontalAlign::Left +                            } +                            HorizontalAlignment::Center => { +                                glow_glyph::HorizontalAlign::Center +                            } +                            HorizontalAlignment::Right => { +                                glow_glyph::HorizontalAlign::Right +                            } +                        }) +                        .v_align(match text.vertical_alignment { +                            VerticalAlignment::Top => { +                                glow_glyph::VerticalAlign::Top +                            } +                            VerticalAlignment::Center => { +                                glow_glyph::VerticalAlign::Center +                            } +                            VerticalAlignment::Bottom => { +                                glow_glyph::VerticalAlign::Bottom +                            } +                        }), +                    ..Default::default() +                }; + +                self.text_pipeline.queue(text); +            } + +            self.text_pipeline.draw_queued( +                gl, +                transformation, +                glow_glyph::Region { +                    x: bounds.x, +                    y: target_height - (bounds.y + bounds.height), +                    width: bounds.width, +                    height: bounds.height, +                }, +            ); +        } +    } +} + +impl iced_graphics::Backend for Backend { +    fn trim_measurements(&mut self) { +        self.text_pipeline.trim_measurement_cache() +    } +} + +impl backend::Text for Backend { +    const ICON_FONT: Font = font::ICONS; +    const CHECKMARK_ICON: char = font::CHECKMARK_ICON; +    const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; + +    fn default_size(&self) -> u16 { +        self.default_text_size +    } + +    fn measure( +        &self, +        contents: &str, +        size: f32, +        font: Font, +        bounds: Size, +    ) -> (f32, f32) { +        self.text_pipeline.measure(contents, size, font, bounds) +    } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { +    fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { +        (50, 50) +    } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { +    fn viewport_dimensions( +        &self, +        _handle: &iced_native::svg::Handle, +    ) -> (u32, u32) { +        (50, 50) +    } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs new file mode 100644 index 00000000..98faf24c --- /dev/null +++ b/glow/src/lib.rs @@ -0,0 +1,41 @@ +//! A [`glow`] renderer for [`iced_native`]. +//! +//!  +//! +//! [`glow`]: https://github.com/grovesNL/glow +//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![forbid(rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod backend; +mod program; +mod quad; +mod text; +mod triangle; + +pub mod settings; +pub mod widget; +pub mod window; + +pub use backend::Backend; +pub use settings::Settings; + +pub(crate) use iced_graphics::Transformation; + +#[doc(no_inline)] +pub use widget::*; + +pub use iced_graphics::{Error, Viewport}; +pub use iced_native::{ +    Background, Color, Command, HorizontalAlignment, Length, Vector, +    VerticalAlignment, +}; + +/// A [`glow`] graphics renderer for [`iced`]. +/// +/// [`glow`]: https://github.com/grovesNL/glow +/// [`iced`]: https://github.com/hecrj/iced +pub type Renderer = iced_graphics::Renderer<Backend>; diff --git a/glow/src/program.rs b/glow/src/program.rs new file mode 100644 index 00000000..489a194f --- /dev/null +++ b/glow/src/program.rs @@ -0,0 +1,39 @@ +use glow::HasContext; + +pub unsafe fn create( +    gl: &glow::Context, +    shader_sources: &[(u32, &str)], +) -> <glow::Context as HasContext>::Program { +    let program = gl.create_program().expect("Cannot create program"); + +    let mut shaders = Vec::with_capacity(shader_sources.len()); + +    for (shader_type, shader_source) in shader_sources.iter() { +        let shader = gl +            .create_shader(*shader_type) +            .expect("Cannot create shader"); + +        gl.shader_source(shader, shader_source); +        gl.compile_shader(shader); + +        if !gl.get_shader_compile_status(shader) { +            panic!(gl.get_shader_info_log(shader)); +        } + +        gl.attach_shader(program, shader); + +        shaders.push(shader); +    } + +    gl.link_program(program); +    if !gl.get_program_link_status(program) { +        panic!(gl.get_program_info_log(program)); +    } + +    for shader in shaders { +        gl.detach_shader(program, shader); +        gl.delete_shader(shader); +    } + +    program +} diff --git a/glow/src/quad.rs b/glow/src/quad.rs new file mode 100644 index 00000000..a8fbb9e5 --- /dev/null +++ b/glow/src/quad.rs @@ -0,0 +1,235 @@ +use crate::program; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +use iced_native::Rectangle; + +const MAX_INSTANCES: usize = 100_000; + +#[derive(Debug)] +pub struct Pipeline { +    program: <glow::Context as HasContext>::Program, +    vertex_array: <glow::Context as HasContext>::VertexArray, +    instances: <glow::Context as HasContext>::Buffer, +    transform_location: <glow::Context as HasContext>::UniformLocation, +    scale_location: <glow::Context as HasContext>::UniformLocation, +    screen_height_location: <glow::Context as HasContext>::UniformLocation, +    current_transform: Transformation, +    current_scale: f32, +    current_target_height: u32, +} + +impl Pipeline { +    pub fn new(gl: &glow::Context) -> Pipeline { +        let program = unsafe { +            program::create( +                gl, +                &[ +                    (glow::VERTEX_SHADER, include_str!("shader/quad.vert")), +                    (glow::FRAGMENT_SHADER, include_str!("shader/quad.frag")), +                ], +            ) +        }; + +        let transform_location = +            unsafe { gl.get_uniform_location(program, "u_Transform") } +                .expect("Get transform location"); + +        let scale_location = +            unsafe { gl.get_uniform_location(program, "u_Scale") } +                .expect("Get scale location"); + +        let screen_height_location = +            unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } +                .expect("Get target height location"); + +        unsafe { +            gl.use_program(Some(program)); + +            let matrix: [f32; 16] = Transformation::identity().into(); +            gl.uniform_matrix_4_f32_slice( +                Some(&transform_location), +                false, +                &matrix, +            ); + +            gl.uniform_1_f32(Some(&scale_location), 1.0); +            gl.uniform_1_f32(Some(&screen_height_location), 0.0); + +            gl.use_program(None); +        } + +        let (vertex_array, instances) = +            unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; + +        Pipeline { +            program, +            vertex_array, +            instances, +            transform_location, +            scale_location, +            screen_height_location, +            current_transform: Transformation::identity(), +            current_scale: 1.0, +            current_target_height: 0, +        } +    } + +    pub fn draw( +        &mut self, +        gl: &glow::Context, +        target_height: u32, +        instances: &[layer::Quad], +        transformation: Transformation, +        scale: f32, +        bounds: Rectangle<u32>, +    ) { +        unsafe { +            gl.enable(glow::SCISSOR_TEST); +            gl.scissor( +                bounds.x as i32, +                (target_height - (bounds.y + bounds.height)) as i32, +                bounds.width as i32, +                bounds.height as i32, +            ); + +            gl.use_program(Some(self.program)); +            gl.bind_vertex_array(Some(self.vertex_array)); +            gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); +        } + +        if transformation != self.current_transform { +            unsafe { +                let matrix: [f32; 16] = transformation.into(); +                gl.uniform_matrix_4_f32_slice( +                    Some(&self.transform_location), +                    false, +                    &matrix, +                ); + +                self.current_transform = transformation; +            } +        } + +        if scale != self.current_scale { +            unsafe { +                gl.uniform_1_f32(Some(&self.scale_location), scale); +            } + +            self.current_scale = scale; +        } + +        if target_height != self.current_target_height { +            unsafe { +                gl.uniform_1_f32( +                    Some(&self.screen_height_location), +                    target_height as f32, +                ); +            } + +            self.current_target_height = target_height; +        } + +        let mut i = 0; +        let total = instances.len(); + +        while i < total { +            let end = (i + MAX_INSTANCES).min(total); +            let amount = end - i; + +            unsafe { +                gl.buffer_sub_data_u8_slice( +                    glow::ARRAY_BUFFER, +                    0, +                    bytemuck::cast_slice(&instances[i..end]), +                ); + +                gl.draw_arrays_instanced( +                    glow::TRIANGLE_STRIP, +                    0, +                    4, +                    amount as i32, +                ); +            } + +            i += MAX_INSTANCES; +        } + +        unsafe { +            gl.bind_vertex_array(None); +            gl.use_program(None); +            gl.disable(glow::SCISSOR_TEST); +        } +    } +} + +unsafe fn create_instance_buffer( +    gl: &glow::Context, +    size: usize, +) -> ( +    <glow::Context as HasContext>::VertexArray, +    <glow::Context as HasContext>::Buffer, +) { +    let vertex_array = gl.create_vertex_array().expect("Create vertex array"); +    let buffer = gl.create_buffer().expect("Create instance buffer"); + +    gl.bind_vertex_array(Some(vertex_array)); +    gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); +    gl.buffer_data_size( +        glow::ARRAY_BUFFER, +        (size * std::mem::size_of::<layer::Quad>()) as i32, +        glow::DYNAMIC_DRAW, +    ); + +    let stride = std::mem::size_of::<layer::Quad>() as i32; + +    gl.enable_vertex_attrib_array(0); +    gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); +    gl.vertex_attrib_divisor(0, 1); + +    gl.enable_vertex_attrib_array(1); +    gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); +    gl.vertex_attrib_divisor(1, 1); + +    gl.enable_vertex_attrib_array(2); +    gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); +    gl.vertex_attrib_divisor(2, 1); + +    gl.enable_vertex_attrib_array(3); +    gl.vertex_attrib_pointer_f32( +        3, +        4, +        glow::FLOAT, +        false, +        stride, +        4 * (2 + 2 + 4), +    ); +    gl.vertex_attrib_divisor(3, 1); + +    gl.enable_vertex_attrib_array(4); +    gl.vertex_attrib_pointer_f32( +        4, +        1, +        glow::FLOAT, +        false, +        stride, +        4 * (2 + 2 + 4 + 4), +    ); +    gl.vertex_attrib_divisor(4, 1); + +    gl.enable_vertex_attrib_array(5); +    gl.vertex_attrib_pointer_f32( +        5, +        1, +        glow::FLOAT, +        false, +        stride, +        4 * (2 + 2 + 4 + 4 + 1), +    ); +    gl.vertex_attrib_divisor(5, 1); + +    gl.bind_vertex_array(None); +    gl.bind_buffer(glow::ARRAY_BUFFER, None); + +    (vertex_array, buffer) +} diff --git a/glow/src/settings.rs b/glow/src/settings.rs new file mode 100644 index 00000000..524d91a9 --- /dev/null +++ b/glow/src/settings.rs @@ -0,0 +1,31 @@ +//! Configure a renderer. +pub use iced_graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Settings { +    /// The bytes of the font that will be used by default. +    /// +    /// If `None` is provided, a default system font will be chosen. +    pub default_font: Option<&'static [u8]>, + +    /// The default size of text. +    /// +    /// By default, it will be set to 20. +    pub default_text_size: u16, + +    /// The antialiasing strategy that will be used for triangle primitives. +    pub antialiasing: Option<Antialiasing>, +} + +impl Default for Settings { +    fn default() -> Settings { +        Settings { +            default_font: None, +            default_text_size: 20, +            antialiasing: None, +        } +    } +} diff --git a/glow/src/shader/quad.frag b/glow/src/shader/quad.frag new file mode 100644 index 00000000..cea36bdc --- /dev/null +++ b/glow/src/shader/quad.frag @@ -0,0 +1,70 @@ +#version 330 + +uniform float u_ScreenHeight; + +in vec4 v_Color; +in vec4 v_BorderColor; +in vec2 v_Pos; +in vec2 v_Scale; +in float v_BorderRadius; +in float v_BorderWidth; + +out vec4 o_Color; + +float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) +{ +    // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN +    vec2 inner_size = size - vec2(radius, radius) * 2.0; +    vec2 top_left = position + vec2(radius, radius); +    vec2 bottom_right = top_left + inner_size; + +    vec2 top_left_distance = top_left - frag_coord; +    vec2 bottom_right_distance = frag_coord - bottom_right; + +    vec2 distance = vec2( +        max(max(top_left_distance.x, bottom_right_distance.x), 0.0), +        max(max(top_left_distance.y, bottom_right_distance.y), 0.0) +    ); + +    return sqrt(distance.x * distance.x + distance.y * distance.y); +} + +void main() { +    vec4 mixed_color; + +    vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); + +    // TODO: Remove branching (?) +    if(v_BorderWidth > 0) { +        float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); + +        float internal_distance = distance( +            fragCoord, +            v_Pos + vec2(v_BorderWidth), +            v_Scale - vec2(v_BorderWidth * 2.0), +            internal_border +        ); + +        float border_mix = smoothstep( +            max(internal_border - 0.5, 0.0), +            internal_border + 0.5, +            internal_distance +        ); + +        mixed_color = mix(v_Color, v_BorderColor, border_mix); +    } else { +        mixed_color = v_Color; +    } + +    float d = distance( +        fragCoord, +        v_Pos, +        v_Scale, +        v_BorderRadius +    ); + +    float radius_alpha = +        1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); + +    o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert new file mode 100644 index 00000000..82417856 --- /dev/null +++ b/glow/src/shader/quad.vert @@ -0,0 +1,52 @@ +#version 330 + +uniform mat4 u_Transform; +uniform float u_Scale; + +layout(location = 0) in vec2 i_Pos; +layout(location = 1) in vec2 i_Scale; +layout(location = 2) in vec4 i_Color; +layout(location = 3) in vec4 i_BorderColor; +layout(location = 4) in float i_BorderRadius; +layout(location = 5) in float i_BorderWidth; + +out vec4 v_Color; +out vec4 v_BorderColor; +out vec2 v_Pos; +out vec2 v_Scale; +out float v_BorderRadius; +out float v_BorderWidth; + +const vec2 positions[4] = vec2[]( +    vec2(0.0, 0.0), +    vec2(0.0, 1.0), +    vec2(1.0, 0.0), +    vec2(1.0, 1.0) +); + +void main() { +    vec2 q_Pos = positions[gl_VertexID]; +    vec2 p_Pos = i_Pos * u_Scale; +    vec2 p_Scale = i_Scale  * u_Scale; + +    float i_BorderRadius = min( +        i_BorderRadius, +        min(i_Scale.x, i_Scale.y) / 2.0 +    ); + +    mat4 i_Transform = mat4( +        vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), +        vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), +        vec4(0.0, 0.0, 1.0, 0.0), +        vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) +    ); + +    v_Color = i_Color; +    v_BorderColor = i_BorderColor; +    v_Pos = p_Pos; +    v_Scale = p_Scale; +    v_BorderRadius = i_BorderRadius * u_Scale; +    v_BorderWidth = i_BorderWidth * u_Scale; + +    gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); +} diff --git a/glow/src/shader/triangle.frag b/glow/src/shader/triangle.frag new file mode 100644 index 00000000..d186784a --- /dev/null +++ b/glow/src/shader/triangle.frag @@ -0,0 +1,9 @@ +#version 330 + +in vec4 v_Color; + +out vec4 o_Color; + +void main() { +    o_Color = v_Color; +} diff --git a/glow/src/shader/triangle.vert b/glow/src/shader/triangle.vert new file mode 100644 index 00000000..5723436a --- /dev/null +++ b/glow/src/shader/triangle.vert @@ -0,0 +1,13 @@ +#version 330 + +uniform mat4 u_Transform; + +layout(location = 0) in vec2 i_Position; +layout(location = 1) in vec4 i_Color; + +out vec4 v_Color; + +void main() { +    gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); +    v_Color = i_Color; +} diff --git a/glow/src/text.rs b/glow/src/text.rs new file mode 100644 index 00000000..925c7287 --- /dev/null +++ b/glow/src/text.rs @@ -0,0 +1,156 @@ +use crate::Transformation; +use glow_glyph::ab_glyph; +use iced_graphics::font; +use std::{cell::RefCell, collections::HashMap}; + +#[derive(Debug)] +pub struct Pipeline { +    draw_brush: RefCell<glow_glyph::GlyphBrush>, +    draw_font_map: RefCell<HashMap<String, glow_glyph::FontId>>, +    measure_brush: RefCell<glyph_brush::GlyphBrush<()>>, +} + +impl Pipeline { +    pub fn new(gl: &glow::Context, default_font: Option<&[u8]>) -> Self { +        let default_font = default_font.map(|slice| slice.to_vec()); + +        // TODO: Font customization +        #[cfg(feature = "default_system_font")] +        let default_font = { +            default_font.or_else(|| { +                font::Source::new() +                    .load(&[font::Family::SansSerif, font::Family::Serif]) +                    .ok() +            }) +        }; + +        let default_font = +            default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); + +        let font = ab_glyph::FontArc::try_from_vec(default_font) +            .unwrap_or_else(|_| { +                log::warn!( +                    "System font failed to load. Falling back to \ +                    embedded font..." +                ); + +                ab_glyph::FontArc::try_from_slice(font::FALLBACK) +                    .expect("Load fallback font") +            }); + +        let draw_brush = +            glow_glyph::GlyphBrushBuilder::using_font(font.clone()) +                .initial_cache_size((2048, 2048)) +                .draw_cache_multithread(false) // TODO: Expose as a configuration flag +                .build(&gl); + +        let measure_brush = +            glyph_brush::GlyphBrushBuilder::using_font(font).build(); + +        Pipeline { +            draw_brush: RefCell::new(draw_brush), +            draw_font_map: RefCell::new(HashMap::new()), +            measure_brush: RefCell::new(measure_brush), +        } +    } + +    pub fn queue(&mut self, section: glow_glyph::Section<'_>) { +        self.draw_brush.borrow_mut().queue(section); +    } + +    pub fn draw_queued( +        &mut self, +        gl: &glow::Context, +        transformation: Transformation, +        region: glow_glyph::Region, +    ) { +        self.draw_brush +            .borrow_mut() +            .draw_queued_with_transform_and_scissoring( +                gl, +                transformation.into(), +                region, +            ) +            .expect("Draw text"); +    } + +    pub fn measure( +        &self, +        content: &str, +        size: f32, +        font: iced_native::Font, +        bounds: iced_native::Size, +    ) -> (f32, f32) { +        use glow_glyph::GlyphCruncher; + +        let glow_glyph::FontId(font_id) = self.find_font(font); + +        let section = glow_glyph::Section { +            bounds: (bounds.width, bounds.height), +            text: vec![glow_glyph::Text { +                text: content, +                scale: size.into(), +                font_id: glow_glyph::FontId(font_id), +                extra: glow_glyph::Extra::default(), +            }], +            ..Default::default() +        }; + +        if let Some(bounds) = +            self.measure_brush.borrow_mut().glyph_bounds(section) +        { +            (bounds.width().ceil(), bounds.height().ceil()) +        } else { +            (0.0, 0.0) +        } +    } + +    pub fn trim_measurement_cache(&mut self) { +        // TODO: We should probably use a `GlyphCalculator` for this. However, +        // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. +        // This makes stuff quite inconvenient. A manual method for trimming the +        // cache would make our lives easier. +        loop { +            let action = self +                .measure_brush +                .borrow_mut() +                .process_queued(|_, _| {}, |_| {}); + +            match action { +                Ok(_) => break, +                Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { +                    let (width, height) = suggested; + +                    self.measure_brush +                        .borrow_mut() +                        .resize_texture(width, height); +                } +            } +        } +    } + +    pub fn find_font(&self, font: iced_native::Font) -> glow_glyph::FontId { +        match font { +            iced_native::Font::Default => glow_glyph::FontId(0), +            iced_native::Font::External { name, bytes } => { +                if let Some(font_id) = self.draw_font_map.borrow().get(name) { +                    return *font_id; +                } + +                let font = ab_glyph::FontArc::try_from_slice(bytes) +                    .expect("Load font"); + +                let _ = self.measure_brush.borrow_mut().add_font(font.clone()); + +                let font_id = self.draw_brush.borrow_mut().add_font(font); + +                let _ = self +                    .draw_font_map +                    .borrow_mut() +                    .insert(String::from(name), font_id); + +                font_id +            } +        } +    } +} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs new file mode 100644 index 00000000..9202bcb2 --- /dev/null +++ b/glow/src/triangle.rs @@ -0,0 +1,292 @@ +//! Draw meshes of triangles. +use crate::program; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +use std::marker::PhantomData; + +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + +const VERTEX_BUFFER_SIZE: usize = 10_000; +const INDEX_BUFFER_SIZE: usize = 10_000; + +#[derive(Debug)] +pub(crate) struct Pipeline { +    program: <glow::Context as HasContext>::Program, +    vertex_array: <glow::Context as HasContext>::VertexArray, +    vertices: Buffer<Vertex2D>, +    indices: Buffer<u32>, +    transform_location: <glow::Context as HasContext>::UniformLocation, +    current_transform: Transformation, +} + +impl Pipeline { +    pub fn new(gl: &glow::Context) -> Pipeline { +        let program = unsafe { +            program::create( +                gl, +                &[ +                    (glow::VERTEX_SHADER, include_str!("shader/triangle.vert")), +                    ( +                        glow::FRAGMENT_SHADER, +                        include_str!("shader/triangle.frag"), +                    ), +                ], +            ) +        }; + +        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_array = +            unsafe { gl.create_vertex_array().expect("Create vertex array") }; + +        unsafe { +            gl.bind_vertex_array(Some(vertex_array)); +        } + +        let vertices = unsafe { +            Buffer::new( +                gl, +                glow::ARRAY_BUFFER, +                glow::DYNAMIC_DRAW, +                VERTEX_BUFFER_SIZE, +            ) +        }; + +        let indices = unsafe { +            Buffer::new( +                gl, +                glow::ELEMENT_ARRAY_BUFFER, +                glow::DYNAMIC_DRAW, +                INDEX_BUFFER_SIZE, +            ) +        }; + +        unsafe { +            let stride = std::mem::size_of::<Vertex2D>() as i32; + +            gl.enable_vertex_attrib_array(0); +            gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + +            gl.enable_vertex_attrib_array(1); +            gl.vertex_attrib_pointer_f32( +                1, +                4, +                glow::FLOAT, +                false, +                stride, +                4 * 2, +            ); + +            gl.bind_vertex_array(None); +        } + +        Pipeline { +            program, +            vertex_array, +            vertices, +            indices, +            transform_location, +            current_transform: Transformation::identity(), +        } +    } + +    pub fn draw( +        &mut self, +        gl: &glow::Context, +        target_height: u32, +        transformation: Transformation, +        scale_factor: f32, +        meshes: &[layer::Mesh<'_>], +    ) { +        unsafe { +            gl.enable(glow::MULTISAMPLE); +            gl.enable(glow::SCISSOR_TEST); +            gl.use_program(Some(self.program)); +            gl.bind_vertex_array(Some(self.vertex_array)); +        } + +        // This looks a bit crazy, but we are just counting how many vertices +        // and indices we will need to handle. +        // TODO: Improve readability +        let (total_vertices, total_indices) = meshes +            .iter() +            .map(|layer::Mesh { buffers, .. }| { +                (buffers.vertices.len(), buffers.indices.len()) +            }) +            .fold((0, 0), |(total_v, total_i), (v, i)| { +                (total_v + v, total_i + i) +            }); + +        // Then we ensure the current buffers are big enough, resizing if +        // necessary +        unsafe { +            self.vertices.bind(gl, total_vertices); +            self.indices.bind(gl, total_indices); +        } + +        // We upload all the vertices and indices upfront +        let mut last_vertex = 0; +        let mut last_index = 0; + +        for layer::Mesh { buffers, .. } in meshes { +            unsafe { +                gl.buffer_sub_data_u8_slice( +                    glow::ARRAY_BUFFER, +                    (last_vertex * std::mem::size_of::<Vertex2D>()) as i32, +                    bytemuck::cast_slice(&buffers.vertices), +                ); + +                gl.buffer_sub_data_u8_slice( +                    glow::ELEMENT_ARRAY_BUFFER, +                    (last_index * std::mem::size_of::<u32>()) as i32, +                    bytemuck::cast_slice(&buffers.indices), +                ); + +                last_vertex += buffers.vertices.len(); +                last_index += buffers.indices.len(); +            } +        } + +        // Then we draw each mesh using offsets +        let mut last_vertex = 0; +        let mut last_index = 0; + +        for layer::Mesh { +            buffers, +            origin, +            clip_bounds, +        } in meshes +        { +            let transform = +                transformation * Transformation::translate(origin.x, origin.y); + +            let clip_bounds = (*clip_bounds * scale_factor).snap(); + +            unsafe { +                if self.current_transform != transform { +                    let matrix: [f32; 16] = transform.into(); +                    gl.uniform_matrix_4_f32_slice( +                        Some(&self.transform_location), +                        false, +                        &matrix, +                    ); + +                    self.current_transform = transform; +                } + +                gl.scissor( +                    clip_bounds.x as i32, +                    (target_height - (clip_bounds.y + clip_bounds.height)) +                        as i32, +                    clip_bounds.width as i32, +                    clip_bounds.height as i32, +                ); + +                gl.draw_elements_base_vertex( +                    glow::TRIANGLES, +                    buffers.indices.len() as i32, +                    glow::UNSIGNED_INT, +                    (last_index * std::mem::size_of::<u32>()) as i32, +                    last_vertex as i32, +                ); + +                last_vertex += buffers.vertices.len(); +                last_index += buffers.indices.len(); +            } +        } + +        unsafe { +            gl.bind_vertex_array(None); +            gl.use_program(None); +            gl.disable(glow::SCISSOR_TEST); +            gl.disable(glow::MULTISAMPLE); +        } +    } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct Uniforms { +    transform: [f32; 16], +} + +unsafe impl bytemuck::Zeroable for Uniforms {} +unsafe impl bytemuck::Pod for Uniforms {} + +impl Default for Uniforms { +    fn default() -> Self { +        Self { +            transform: *Transformation::identity().as_ref(), +        } +    } +} + +impl From<Transformation> for Uniforms { +    fn from(transformation: Transformation) -> Uniforms { +        Self { +            transform: transformation.into(), +        } +    } +} + +#[derive(Debug)] +struct Buffer<T> { +    raw: <glow::Context as HasContext>::Buffer, +    target: u32, +    usage: u32, +    size: usize, +    phantom: PhantomData<T>, +} + +impl<T> Buffer<T> { +    pub unsafe fn new( +        gl: &glow::Context, +        target: u32, +        usage: u32, +        size: usize, +    ) -> Self { +        let raw = gl.create_buffer().expect("Create buffer"); + +        let mut buffer = Buffer { +            raw, +            target, +            usage, +            size: 0, +            phantom: PhantomData, +        }; + +        buffer.bind(gl, size); + +        buffer +    } + +    pub unsafe fn bind(&mut self, gl: &glow::Context, size: usize) { +        gl.bind_buffer(self.target, Some(self.raw)); + +        if self.size < size { +            gl.buffer_data_size( +                self.target, +                (size * std::mem::size_of::<T>()) as i32, +                self.usage, +            ); + +            self.size = size; +        } +    } +} diff --git a/glow/src/widget.rs b/glow/src/widget.rs new file mode 100644 index 00000000..b5c84c56 --- /dev/null +++ b/glow/src/widget.rs @@ -0,0 +1,72 @@ +//! Use the widgets supported out-of-the-box. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced_glow::{button, Button}; +//! ``` +use crate::Renderer; + +pub mod button; +pub mod checkbox; +pub mod container; +pub mod pane_grid; +pub mod pick_list; +pub mod progress_bar; +pub mod radio; +pub mod rule; +pub mod scrollable; +pub mod slider; +pub mod text_input; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use rule::Rule; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use text_input::TextInput; + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + +pub use iced_native::{Image, Space}; + +/// A container that distributes its contents vertically. +pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; + +/// A container that distributes its contents horizontally. +pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; + +/// A paragraph of text. +pub type Text = iced_native::Text<Renderer>; diff --git a/glow/src/widget/button.rs b/glow/src/widget/button.rs new file mode 100644 index 00000000..fc729cd5 --- /dev/null +++ b/glow/src/widget/button.rs @@ -0,0 +1,12 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +use crate::Renderer; + +pub use iced_graphics::button::{Style, StyleSheet}; +pub use iced_native::button::State; + +/// A widget that produces a message when clicked. +/// +/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. +pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>; diff --git a/glow/src/widget/canvas.rs b/glow/src/widget/canvas.rs new file mode 100644 index 00000000..399dd19c --- /dev/null +++ b/glow/src/widget/canvas.rs @@ -0,0 +1,6 @@ +//! Draw 2D graphics for your users. +//! +//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a +//! [`Frame`]. It can be used for animation, data visualization, game graphics, +//! and more! +pub use iced_graphics::canvas::*; diff --git a/glow/src/widget/checkbox.rs b/glow/src/widget/checkbox.rs new file mode 100644 index 00000000..d27d77cc --- /dev/null +++ b/glow/src/widget/checkbox.rs @@ -0,0 +1,9 @@ +//! Show toggle controls using checkboxes. +use crate::Renderer; + +pub use iced_graphics::checkbox::{Style, StyleSheet}; + +/// A box that can be checked. +/// +/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. +pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>; diff --git a/glow/src/widget/container.rs b/glow/src/widget/container.rs new file mode 100644 index 00000000..bc26cef2 --- /dev/null +++ b/glow/src/widget/container.rs @@ -0,0 +1,10 @@ +//! Decorate content and apply alignment. +use crate::Renderer; + +pub use iced_graphics::container::{Style, StyleSheet}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` container with a default +/// `Renderer`. +pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>; diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs new file mode 100644 index 00000000..c26dde48 --- /dev/null +++ b/glow/src/widget/pane_grid.rs @@ -0,0 +1,31 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [](https://gfycat.com/mixedflatjellyfish) +//! +//! # Example +//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, +//! drag and drop, and hotkey support. +//! +//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid +use crate::Renderer; + +pub use iced_native::pane_grid::{ +    Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, +    State, +}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [](https://gfycat.com/mixedflatjellyfish) +/// +/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. +pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>; + +/// The content of a [`Pane`]. +pub type Content<'a, Message> = +    iced_native::pane_grid::Content<'a, Message, Renderer>; + +/// The title bar of a [`Pane`]. +pub type TitleBar<'a, Message> = +    iced_native::pane_grid::TitleBar<'a, Message, Renderer>; diff --git a/glow/src/widget/pick_list.rs b/glow/src/widget/pick_list.rs new file mode 100644 index 00000000..fccc68c9 --- /dev/null +++ b/glow/src/widget/pick_list.rs @@ -0,0 +1,9 @@ +//! Display a dropdown list of selectable values. +pub use iced_native::pick_list::State; + +pub use iced_graphics::overlay::menu::Style as Menu; +pub use iced_graphics::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message> = +    iced_native::PickList<'a, T, Message, crate::Renderer>; diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs new file mode 100644 index 00000000..45a25d00 --- /dev/null +++ b/glow/src/widget/progress_bar.rs @@ -0,0 +1,13 @@ +//! Allow your users to visually track the progress of a computation. +//! +//! A [`ProgressBar`] has a range of possible values and a current value, +//! as well as a length, height and style. +use crate::Renderer; + +pub use iced_graphics::progress_bar::{Style, StyleSheet}; + +/// A bar that displays progress. +/// +/// This is an alias of an `iced_native` progress bar with an +/// `iced_wgpu::Renderer`. +pub type ProgressBar = iced_native::ProgressBar<Renderer>; diff --git a/glow/src/widget/qr_code.rs b/glow/src/widget/qr_code.rs new file mode 100644 index 00000000..7b1c2408 --- /dev/null +++ b/glow/src/widget/qr_code.rs @@ -0,0 +1,2 @@ +//! Encode and display information in a QR code. +pub use iced_graphics::qr_code::*; diff --git a/glow/src/widget/radio.rs b/glow/src/widget/radio.rs new file mode 100644 index 00000000..0b843d1f --- /dev/null +++ b/glow/src/widget/radio.rs @@ -0,0 +1,10 @@ +//! Create choices using radio buttons. +use crate::Renderer; + +pub use iced_graphics::radio::{Style, StyleSheet}; + +/// A circular button representing a choice. +/// +/// This is an alias of an `iced_native` radio button with an +/// `iced_wgpu::Renderer`. +pub type Radio<Message> = iced_native::Radio<Message, Renderer>; diff --git a/glow/src/widget/rule.rs b/glow/src/widget/rule.rs new file mode 100644 index 00000000..faa2be86 --- /dev/null +++ b/glow/src/widget/rule.rs @@ -0,0 +1,10 @@ +//! Display a horizontal or vertical rule for dividing content. + +use crate::Renderer; + +pub use iced_graphics::rule::{FillMode, Style, StyleSheet}; + +/// Display a horizontal or vertical rule for dividing content. +/// +/// This is an alias of an `iced_native` rule with an `iced_glow::Renderer`. +pub type Rule = iced_native::Rule<Renderer>; diff --git a/glow/src/widget/scrollable.rs b/glow/src/widget/scrollable.rs new file mode 100644 index 00000000..fabb4318 --- /dev/null +++ b/glow/src/widget/scrollable.rs @@ -0,0 +1,13 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::Renderer; + +pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet}; +pub use iced_native::scrollable::State; + +/// A widget that can vertically display an infinite amount of content +/// with a scrollbar. +/// +/// This is an alias of an `iced_native` scrollable with a default +/// `Renderer`. +pub type Scrollable<'a, Message> = +    iced_native::Scrollable<'a, Message, Renderer>; diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs new file mode 100644 index 00000000..9a269858 --- /dev/null +++ b/glow/src/widget/slider.rs @@ -0,0 +1,13 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +use crate::Renderer; + +pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet}; +pub use iced_native::slider::State; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. +pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>; diff --git a/glow/src/widget/text_input.rs b/glow/src/widget/text_input.rs new file mode 100644 index 00000000..db18b1cc --- /dev/null +++ b/glow/src/widget/text_input.rs @@ -0,0 +1,12 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +use crate::Renderer; + +pub use iced_graphics::text_input::{Style, StyleSheet}; +pub use iced_native::text_input::State; + +/// A field that can be filled with text. +/// +/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. +pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>; diff --git a/glow/src/window.rs b/glow/src/window.rs new file mode 100644 index 00000000..aac5fb9e --- /dev/null +++ b/glow/src/window.rs @@ -0,0 +1,4 @@ +//! Display rendering results on windows. +mod compositor; + +pub use compositor::Compositor; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs new file mode 100644 index 00000000..1fb671ad --- /dev/null +++ b/glow/src/window/compositor.rs @@ -0,0 +1,76 @@ +use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; + +use core::ffi::c_void; +use glow::HasContext; +use iced_graphics::{Antialiasing, Size}; +use iced_native::mouse; + +/// A window graphics backend for iced powered by `glow`. +#[allow(missing_debug_implementations)] +pub struct Compositor { +    gl: glow::Context, +} + +impl iced_graphics::window::GLCompositor for Compositor { +    type Settings = Settings; +    type Renderer = Renderer; + +    unsafe fn new( +        settings: Self::Settings, +        loader_function: impl FnMut(&str) -> *const c_void, +    ) -> Result<(Self, Self::Renderer), Error> { +        let gl = glow::Context::from_loader_function(loader_function); + +        // Enable auto-conversion from/to sRGB +        gl.enable(glow::FRAMEBUFFER_SRGB); + +        // Enable alpha blending +        gl.enable(glow::BLEND); +        gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + +        // Disable multisampling by default +        gl.disable(glow::MULTISAMPLE); + +        let renderer = Renderer::new(Backend::new(&gl, settings)); + +        Ok((Self { gl }, renderer)) +    } + +    fn sample_count(settings: &Settings) -> u32 { +        settings +            .antialiasing +            .map(Antialiasing::sample_count) +            .unwrap_or(0) +    } + +    fn resize_viewport(&mut self, physical_size: Size<u32>) { +        unsafe { +            self.gl.viewport( +                0, +                0, +                physical_size.width as i32, +                physical_size.height as i32, +            ); +        } +    } + +    fn draw<T: AsRef<str>>( +        &mut self, +        renderer: &mut Self::Renderer, +        viewport: &Viewport, +        color: Color, +        output: &<Self::Renderer as iced_native::Renderer>::Output, +        overlay: &[T], +    ) -> mouse::Interaction { +        let gl = &self.gl; + +        let [r, g, b, a] = color.into_linear(); + +        unsafe { +            gl.clear_color(r, g, b, a); +            gl.clear(glow::COLOR_BUFFER_BIT); +        } + +        renderer.backend_mut().draw(gl, viewport, output, overlay) +    } +} | 
