summaryrefslogblamecommitdiffstats
path: root/wgpu/src/lib.rs
blob: ccad08d5384d3af88fa938442197abf0d7fe16cf (plain) (tree)
1
2
3
4
5
6
7
8
9
                                   
   
                                                                                                                                                           
   




                                                                             
                                                
                                                                             
                                                                              

                                                            
   
                                           

                                                  
                                                    
       
                                                                                                                           
  
                                           
                       
              
                  
                 
               
 

                            
 
           
          
           
         
         
             
 







                                                    

                   

                                  
 

             
                       
                     
                             
                           
 









                                                                            



                                               
                                             





                                       


                                        











                                                              


                                                       


















                                                              
                                             

                                                                           


                                     








                                           
                            
       

                                                          
                                             












































                                                                                










                                           
                            








































                                                                           
                                                          



                                                                          
                                                        
 





                                                                     
 
                                                      
 






                                            
 

                                
 
























                                                                          
                                  



























                                                           




                                                      











































                                                                          



                                                  
                                      








































































































































                                                                        


                                                    
             
                                        






                                                        







                                                        
//! A [`wgpu`] renderer for [Iced].
//!
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and
//! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the
//! incoming [WebGPU API].
//!
//! Currently, `iced_wgpu` supports the following primitives:
//! - Text, which is rendered using [`glyphon`].
//! - Quads or rectangles, with rounded borders and a solid background color.
//! - Clip areas, useful to implement scrollables or hide overflowing content.
//! - Images and SVG, loaded from memory or the file system.
//! - Meshes of triangles, useful to draw geometry freely.
//!
//! [Iced]: https://github.com/iced-rs/iced
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`glyphon`]: https://github.com/grovesNL/glyphon
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(missing_docs)]
pub mod layer;
pub mod primitive;
pub mod settings;
pub mod window;

#[cfg(feature = "geometry")]
pub mod geometry;

mod buffer;
mod color;
mod engine;
mod quad;
mod text;
mod triangle;

#[cfg(any(feature = "image", feature = "svg"))]
#[path = "image/mod.rs"]
mod image;

#[cfg(not(any(feature = "image", feature = "svg")))]
#[path = "image/null.rs"]
mod image;

use buffer::Buffer;

pub use iced_graphics as graphics;
pub use iced_graphics::core;

pub use wgpu;

pub use engine::Engine;
pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings;

#[cfg(feature = "geometry")]
pub use geometry::Geometry;

use crate::core::{
    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;

use std::borrow::Cow;

/// A [`wgpu`] graphics renderer for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
#[allow(missing_debug_implementations)]
pub struct Renderer {
    default_font: Font,
    default_text_size: Pixels,
    layers: layer::Stack,

    triangle_storage: triangle::Storage,
    text_storage: text::Storage,

    // TODO: Centralize all the image feature handling
    #[cfg(any(feature = "svg", feature = "image"))]
    image_cache: image::cache::Shared,
}

impl Renderer {
    pub fn new(settings: Settings, _engine: &Engine) -> Self {
        Self {
            default_font: settings.default_font,
            default_text_size: settings.default_text_size,
            layers: layer::Stack::new(),

            triangle_storage: triangle::Storage::new(),
            text_storage: text::Storage::new(),

            #[cfg(any(feature = "svg", feature = "image"))]
            image_cache: _engine.image_cache().clone(),
        }
    }

    pub fn draw_primitive(&mut self, _primitive: Primitive) {}

    pub fn present<T: AsRef<str>>(
        &mut self,
        engine: &mut Engine,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        encoder: &mut wgpu::CommandEncoder,
        clear_color: Option<Color>,
        format: wgpu::TextureFormat,
        frame: &wgpu::TextureView,
        viewport: &Viewport,
        overlay: &[T],
    ) {
        self.draw_overlay(overlay, viewport);
        self.prepare(engine, device, queue, format, encoder, viewport);
        self.render(engine, device, encoder, frame, clear_color, viewport);

        self.triangle_storage.trim();
        self.text_storage.trim();
    }

    fn prepare(
        &mut self,
        engine: &mut Engine,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        _format: wgpu::TextureFormat,
        encoder: &mut wgpu::CommandEncoder,
        viewport: &Viewport,
    ) {
        let scale_factor = viewport.scale_factor() as f32;

        for layer in self.layers.iter_mut() {
            if !layer.quads.is_empty() {
                engine.quad_pipeline.prepare(
                    device,
                    encoder,
                    &mut engine.staging_belt,
                    &layer.quads,
                    viewport.projection(),
                    scale_factor,
                );
            }

            if !layer.triangles.is_empty() {
                engine.triangle_pipeline.prepare(
                    device,
                    encoder,
                    &mut engine.staging_belt,
                    &mut self.triangle_storage,
                    &layer.triangles,
                    viewport.projection() * Transformation::scale(scale_factor),
                );
            }

            if !layer.text.is_empty() {
                engine.text_pipeline.prepare(
                    device,
                    queue,
                    encoder,
                    &mut self.text_storage,
                    &layer.text,
                    layer.bounds,
                    Transformation::scale(scale_factor),
                    viewport.physical_size(),
                );
            }

            #[cfg(any(feature = "svg", feature = "image"))]
            if !layer.images.is_empty() {
                engine.image_pipeline.prepare(
                    device,
                    encoder,
                    &mut engine.staging_belt,
                    &layer.images,
                    viewport.projection(),
                    scale_factor,
                );
            }
        }
    }

    fn render(
        &mut self,
        engine: &mut Engine,
        device: &wgpu::Device,
        encoder: &mut wgpu::CommandEncoder,
        frame: &wgpu::TextureView,
        clear_color: Option<Color>,
        viewport: &Viewport,
    ) {
        use std::mem::ManuallyDrop;

        let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
            &wgpu::RenderPassDescriptor {
                label: Some("iced_wgpu render pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: frame,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: match clear_color {
                            Some(background_color) => wgpu::LoadOp::Clear({
                                let [r, g, b, a] =
                                    graphics::color::pack(background_color)
                                        .components();

                                wgpu::Color {
                                    r: f64::from(r),
                                    g: f64::from(g),
                                    b: f64::from(b),
                                    a: f64::from(a),
                                }
                            }),
                            None => wgpu::LoadOp::Load,
                        },
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            },
        ));

        let mut quad_layer = 0;
        let mut mesh_layer = 0;
        let mut text_layer = 0;

        #[cfg(any(feature = "svg", feature = "image"))]
        let mut image_layer = 0;

        let scale_factor = viewport.scale_factor() as f32;
        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
            viewport.physical_size(),
        ));

        let scale = Transformation::scale(scale_factor);

        for layer in self.layers.iter() {
            let Some(physical_bounds) =
                physical_bounds.intersection(&(layer.bounds * scale))
            else {
                continue;
            };

            let scissor_rect = physical_bounds.snap();

            if !layer.quads.is_empty() {
                engine.quad_pipeline.render(
                    quad_layer,
                    scissor_rect,
                    &layer.quads,
                    &mut render_pass,
                );

                quad_layer += 1;
            }

            if !layer.triangles.is_empty() {
                let _ = ManuallyDrop::into_inner(render_pass);

                mesh_layer += engine.triangle_pipeline.render(
                    device,
                    encoder,
                    frame,
                    &self.triangle_storage,
                    mesh_layer,
                    &layer.triangles,
                    viewport.physical_size(),
                    physical_bounds,
                    scale,
                );

                render_pass = ManuallyDrop::new(encoder.begin_render_pass(
                    &wgpu::RenderPassDescriptor {
                        label: Some("iced_wgpu render pass"),
                        color_attachments: &[Some(
                            wgpu::RenderPassColorAttachment {
                                view: frame,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Load,
                                    store: wgpu::StoreOp::Store,
                                },
                            },
                        )],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    },
                ));
            }

            if !layer.text.is_empty() {
                text_layer += engine.text_pipeline.render(
                    &self.text_storage,
                    text_layer,
                    &layer.text,
                    scissor_rect,
                    &mut render_pass,
                );
            }

            #[cfg(any(feature = "svg", feature = "image"))]
            if !layer.images.is_empty() {
                engine.image_pipeline.render(
                    image_layer,
                    scissor_rect,
                    &mut render_pass,
                );

                image_layer += 1;
            }
        }

        let _ = ManuallyDrop::into_inner(render_pass);
    }

    fn draw_overlay(
        &mut self,
        overlay: &[impl AsRef<str>],
        viewport: &Viewport,
    ) {
        use crate::core::alignment;
        use crate::core::text::Renderer as _;
        use crate::core::Renderer as _;
        use crate::core::Vector;

        self.with_layer(
            Rectangle::with_size(viewport.logical_size()),
            |renderer| {
                for (i, line) in overlay.iter().enumerate() {
                    let text = crate::core::Text {
                        content: line.as_ref().to_owned(),
                        bounds: viewport.logical_size(),
                        size: Pixels(20.0),
                        line_height: core::text::LineHeight::default(),
                        font: Font::MONOSPACE,
                        horizontal_alignment: alignment::Horizontal::Left,
                        vertical_alignment: alignment::Vertical::Top,
                        shaping: core::text::Shaping::Basic,
                    };

                    renderer.fill_text(
                        text.clone(),
                        Point::new(11.0, 11.0 + 25.0 * i as f32),
                        Color::new(0.9, 0.9, 0.9, 1.0),
                        Rectangle::with_size(Size::INFINITY),
                    );

                    renderer.fill_text(
                        text,
                        Point::new(11.0, 11.0 + 25.0 * i as f32)
                            + Vector::new(-1.0, -1.0),
                        Color::BLACK,
                        Rectangle::with_size(Size::INFINITY),
                    );
                }
            },
        );
    }
}

impl core::Renderer for Renderer {
    fn start_layer(&mut self, bounds: Rectangle) {
        self.layers.push_clip(bounds);
    }

    fn end_layer(&mut self, _bounds: Rectangle) {
        self.layers.pop_clip();
    }

    fn start_transformation(&mut self, transformation: Transformation) {
        self.layers.push_transformation(transformation);
    }

    fn end_transformation(&mut self, _transformation: Transformation) {
        self.layers.pop_transformation();
    }

    fn fill_quad(
        &mut self,
        quad: core::renderer::Quad,
        background: impl Into<Background>,
    ) {
        self.layers.draw_quad(quad, background.into());
    }

    fn clear(&mut self) {
        self.layers.clear();
    }
}

impl core::text::Renderer for Renderer {
    type Font = Font;
    type Paragraph = Paragraph;
    type Editor = Editor;

    const ICON_FONT: Font = Font::with_name("Iced-Icons");
    const CHECKMARK_ICON: char = '\u{f00c}';
    const ARROW_DOWN_ICON: char = '\u{e800}';

    fn default_font(&self) -> Self::Font {
        self.default_font
    }

    fn default_size(&self) -> Pixels {
        self.default_text_size
    }

    fn load_font(&mut self, font: Cow<'static, [u8]>) {
        graphics::text::font_system()
            .write()
            .expect("Write font system")
            .load_font(font);

        // TODO: Invalidate buffer cache
    }

    fn fill_paragraph(
        &mut self,
        text: &Self::Paragraph,
        position: Point,
        color: Color,
        clip_bounds: Rectangle,
    ) {
        self.layers
            .draw_paragraph(text, position, color, clip_bounds);
    }

    fn fill_editor(
        &mut self,
        editor: &Self::Editor,
        position: Point,
        color: Color,
        clip_bounds: Rectangle,
    ) {
        self.layers
            .draw_editor(editor, position, color, clip_bounds);
    }

    fn fill_text(
        &mut self,
        text: core::Text,
        position: Point,
        color: Color,
        clip_bounds: Rectangle,
    ) {
        self.layers.draw_text(text, position, color, clip_bounds);
    }
}

#[cfg(feature = "image")]
impl core::image::Renderer for Renderer {
    type Handle = core::image::Handle;

    fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
        self.image_cache.lock().measure_image(handle)
    }

    fn draw_image(
        &mut self,
        handle: Self::Handle,
        filter_method: core::image::FilterMethod,
        bounds: Rectangle,
    ) {
        self.layers.draw_image(handle, filter_method, bounds);
    }
}

#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
    fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
        self.image_cache.lock().measure_svg(handle)
    }

    fn draw_svg(
        &mut self,
        handle: core::svg::Handle,
        color_filter: Option<Color>,
        bounds: Rectangle,
    ) {
        self.layers.draw_svg(handle, color_filter, bounds);
    }
}

impl graphics::mesh::Renderer for Renderer {
    fn draw_mesh(&mut self, mesh: graphics::Mesh) {
        self.layers.draw_mesh(mesh);
    }
}

#[cfg(feature = "geometry")]
impl graphics::geometry::Renderer for Renderer {
    type Geometry = Geometry;
    type Frame = geometry::Frame;

    fn new_frame(&self, size: Size) -> Self::Frame {
        geometry::Frame::new(size)
    }

    fn draw_geometry(&mut self, geometry: Self::Geometry) {
        match geometry {
            Geometry::Live { meshes, text } => {
                self.layers.draw_mesh_group(meshes);
                self.layers.draw_text_group(text);
            }
            Geometry::Cached(cache) => {
                if let Some(meshes) = cache.meshes {
                    self.layers.draw_mesh_cache(meshes);
                }

                if let Some(text) = cache.text {
                    self.layers.draw_text_cache(text);
                }
            }
        }
    }
}

impl graphics::compositor::Default for crate::Renderer {
    type Compositor = window::Compositor;
}