diff options
37 files changed, 2139 insertions, 6 deletions
@@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" }  [features]  default = ["wgpu"]  # Enable the `wgpu` GPU-accelerated renderer backend -wgpu = ["iced_renderer/wgpu"] +wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]  # Enables the `Image` widget  image = ["iced_widget/image", "dep:image"]  # Enables the `Svg` widget diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index c1c2eeac..d5437d51 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -183,6 +183,17 @@ impl From<Rectangle<u32>> for Rectangle<f32> {      }  } +impl From<Rectangle<f32>> for Rectangle<u32> { +    fn from(rectangle: Rectangle<f32>) -> Self { +        Rectangle { +            x: rectangle.x as u32, +            y: rectangle.y as u32, +            width: rectangle.width as u32, +            height: rectangle.height as u32, +        } +    } +} +  impl<T> std::ops::Add<Vector<T>> for Rectangle<T>  where      T: std::ops::Add<Output = T>, diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml new file mode 100644 index 00000000..7a927811 --- /dev/null +++ b/examples/custom_shader/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "custom_shader" +version = "0.1.0" +authors = ["Bingus <shankern@protonmail.com>"] +edition = "2021" + +[dependencies] +iced = { path = "../..", features = ["debug", "advanced"]} +image = { version = "0.24.6"} +wgpu = "0.17" +bytemuck = { version = "1.13.1" } +glam = { version = "0.24.0", features = ["bytemuck"] } +rand = "0.8.5" diff --git a/examples/custom_shader/src/camera.rs b/examples/custom_shader/src/camera.rs new file mode 100644 index 00000000..2a49c102 --- /dev/null +++ b/examples/custom_shader/src/camera.rs @@ -0,0 +1,53 @@ +use glam::{mat4, vec3, vec4}; +use iced::Rectangle; + +#[derive(Copy, Clone)] +pub struct Camera { +    eye: glam::Vec3, +    target: glam::Vec3, +    up: glam::Vec3, +    fov_y: f32, +    near: f32, +    far: f32, +} + +impl Default for Camera { +    fn default() -> Self { +        Self { +            eye: vec3(0.0, 2.0, 3.0), +            target: glam::Vec3::ZERO, +            up: glam::Vec3::Y, +            fov_y: 45.0, +            near: 0.1, +            far: 100.0, +        } +    } +} + +pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4( +    vec4(1.0, 0.0, 0.0, 0.0), +    vec4(0.0, 1.0, 0.0, 0.0), +    vec4(0.0, 0.0, 0.5, 0.0), +    vec4(0.0, 0.0, 0.5, 1.0), +); + +impl Camera { +    pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 { +        //TODO looks distorted without padding; base on surface texture size instead? +        let aspect_ratio = bounds.width / (bounds.height + 150.0); + +        let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); +        let proj = glam::Mat4::perspective_rh( +            self.fov_y, +            aspect_ratio, +            self.near, +            self.far, +        ); + +        OPENGL_TO_WGPU_MATRIX * proj * view +    } + +    pub fn position(&self) -> glam::Vec4 { +        glam::Vec4::from((self.eye, 0.0)) +    } +} diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs new file mode 100644 index 00000000..8dbba4b1 --- /dev/null +++ b/examples/custom_shader/src/cubes.rs @@ -0,0 +1,99 @@ +use crate::camera::Camera; +use crate::primitive; +use crate::primitive::cube::Cube; +use glam::Vec3; +use iced::widget::shader; +use iced::{mouse, Color, Rectangle}; +use rand::Rng; +use std::cmp::Ordering; +use std::iter; +use std::time::Duration; + +pub const MAX: u32 = 500; + +#[derive(Clone)] +pub struct Cubes { +    pub size: f32, +    pub cubes: Vec<Cube>, +    pub camera: Camera, +    pub show_depth_buffer: bool, +    pub light_color: Color, +} + +impl Cubes { +    pub fn new() -> Self { +        let mut cubes = Self { +            size: 0.2, +            cubes: vec![], +            camera: Camera::default(), +            show_depth_buffer: false, +            light_color: Color::WHITE, +        }; + +        cubes.adjust_num_cubes(MAX); + +        cubes +    } + +    pub fn update(&mut self, time: Duration) { +        for cube in self.cubes.iter_mut() { +            cube.update(self.size, time.as_secs_f32()); +        } +    } + +    pub fn adjust_num_cubes(&mut self, num_cubes: u32) { +        let curr_cubes = self.cubes.len() as u32; + +        match num_cubes.cmp(&curr_cubes) { +            Ordering::Greater => { +                // spawn +                let cubes_2_spawn = (num_cubes - curr_cubes) as usize; + +                let mut cubes = 0; +                self.cubes.extend(iter::from_fn(|| { +                    if cubes < cubes_2_spawn { +                        cubes += 1; +                        Some(Cube::new(self.size, rnd_origin())) +                    } else { +                        None +                    } +                })); +            } +            Ordering::Less => { +                // chop +                let cubes_2_cut = curr_cubes - num_cubes; +                let new_len = self.cubes.len() - cubes_2_cut as usize; +                self.cubes.truncate(new_len); +            } +            _ => {} +        } +    } +} + +impl<Message> shader::Program<Message> for Cubes { +    type State = (); +    type Primitive = primitive::Primitive; + +    fn draw( +        &self, +        _state: &Self::State, +        _cursor: mouse::Cursor, +        bounds: Rectangle, +    ) -> Self::Primitive { +        primitive::Primitive::new( +            &self.cubes, +            &self.camera, +            bounds, +            self.show_depth_buffer, +            self.light_color, +        ) +    } +} + +fn rnd_origin() -> Vec3 { +    Vec3::new( +        rand::thread_rng().gen_range(-4.0..4.0), +        rand::thread_rng().gen_range(-4.0..4.0), +        rand::thread_rng().gen_range(-4.0..2.0), +    ) +} diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs new file mode 100644 index 00000000..76fa1625 --- /dev/null +++ b/examples/custom_shader/src/main.rs @@ -0,0 +1,174 @@ +mod camera; +mod cubes; +mod pipeline; +mod primitive; + +use crate::cubes::Cubes; +use iced::widget::{ +    checkbox, column, container, row, slider, text, vertical_space, Shader, +}; +use iced::{ +    executor, window, Alignment, Application, Color, Command, Element, Length, +    Renderer, Subscription, Theme, +}; +use std::time::Instant; + +fn main() -> iced::Result { +    IcedCubes::run(iced::Settings::default()) +} + +struct IcedCubes { +    start: Instant, +    cubes: Cubes, +    num_cubes_slider: u32, +} + +impl Default for IcedCubes { +    fn default() -> Self { +        Self { +            start: Instant::now(), +            cubes: Cubes::new(), +            num_cubes_slider: cubes::MAX, +        } +    } +} + +#[derive(Debug, Clone)] +enum Message { +    CubeAmountChanged(u32), +    CubeSizeChanged(f32), +    Tick(Instant), +    ShowDepthBuffer(bool), +    LightColorChanged(Color), +} + +impl Application for IcedCubes { +    type Executor = executor::Default; +    type Message = Message; +    type Theme = Theme; +    type Flags = (); + +    fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) { +        (IcedCubes::default(), Command::none()) +    } + +    fn title(&self) -> String { +        "Iced Cubes".to_string() +    } + +    fn update(&mut self, message: Self::Message) -> Command<Self::Message> { +        match message { +            Message::CubeAmountChanged(num) => { +                self.num_cubes_slider = num; +                self.cubes.adjust_num_cubes(num); +            } +            Message::CubeSizeChanged(size) => { +                self.cubes.size = size; +            } +            Message::Tick(time) => { +                self.cubes.update(time - self.start); +            } +            Message::ShowDepthBuffer(show) => { +                self.cubes.show_depth_buffer = show; +            } +            Message::LightColorChanged(color) => { +                self.cubes.light_color = color; +            } +        } + +        Command::none() +    } + +    fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> { +        let top_controls = row![ +            control( +                "Amount", +                slider( +                    1..=cubes::MAX, +                    self.num_cubes_slider, +                    Message::CubeAmountChanged +                ) +                .width(100) +            ), +            control( +                "Size", +                slider(0.1..=0.25, self.cubes.size, Message::CubeSizeChanged) +                    .step(0.01) +                    .width(100), +            ), +            checkbox( +                "Show Depth Buffer", +                self.cubes.show_depth_buffer, +                Message::ShowDepthBuffer +            ), +        ] +        .spacing(40); + +        let bottom_controls = row![ +            control( +                "R", +                slider(0.0..=1.0, self.cubes.light_color.r, move |r| { +                    Message::LightColorChanged(Color { +                        r, +                        ..self.cubes.light_color +                    }) +                }) +                .step(0.01) +                .width(100) +            ), +            control( +                "G", +                slider(0.0..=1.0, self.cubes.light_color.g, move |g| { +                    Message::LightColorChanged(Color { +                        g, +                        ..self.cubes.light_color +                    }) +                }) +                .step(0.01) +                .width(100) +            ), +            control( +                "B", +                slider(0.0..=1.0, self.cubes.light_color.b, move |b| { +                    Message::LightColorChanged(Color { +                        b, +                        ..self.cubes.light_color +                    }) +                }) +                .step(0.01) +                .width(100) +            ) +        ] +        .spacing(40); + +        let controls = column![top_controls, bottom_controls,] +            .spacing(10) +            .align_items(Alignment::Center); + +        let shader = Shader::new(&self.cubes) +            .width(Length::Fill) +            .height(Length::Fill); + +        container( +            column![shader, controls, vertical_space(20),] +                .spacing(40) +                .align_items(Alignment::Center), +        ) +        .width(Length::Fill) +        .height(Length::Fill) +        .center_x() +        .center_y() +        .into() +    } + +    fn subscription(&self) -> Subscription<Self::Message> { +        window::frames().map(Message::Tick) +    } +} + +fn control<'a>( +    label: &'static str, +    control: impl Into<Element<'a, Message>>, +) -> Element<'a, Message> { +    row![text(label), control.into()].spacing(10).into() +} diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs new file mode 100644 index 00000000..9dd154e8 --- /dev/null +++ b/examples/custom_shader/src/pipeline.rs @@ -0,0 +1,600 @@ +use crate::primitive; +use crate::primitive::cube; +use crate::primitive::{Buffer, Uniforms}; +use iced::{Rectangle, Size}; +use wgpu::util::DeviceExt; + +const SKY_TEXTURE_SIZE: u32 = 128; + +pub struct Pipeline { +    pipeline: wgpu::RenderPipeline, +    vertices: wgpu::Buffer, +    cubes: Buffer, +    uniforms: wgpu::Buffer, +    uniform_bind_group: wgpu::BindGroup, +    depth_texture_size: Size<u32>, +    depth_view: wgpu::TextureView, +    depth_pipeline: DepthPipeline, +} + +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        format: wgpu::TextureFormat, +        target_size: Size<u32>, +    ) -> Self { +        //vertices of one cube +        let vertices = +            device.create_buffer_init(&wgpu::util::BufferInitDescriptor { +                label: Some("cubes vertex buffer"), +                contents: bytemuck::cast_slice(&cube::Raw::vertices()), +                usage: wgpu::BufferUsages::VERTEX, +            }); + +        //cube instance data +        let cubes_buffer = Buffer::new( +            device, +            "cubes instance buffer", +            std::mem::size_of::<cube::Raw>() as u64, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        //uniforms for all cubes +        let uniforms = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("cubes uniform buffer"), +            size: std::mem::size_of::<Uniforms>() as u64, +            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, +            mapped_at_creation: false, +        }); + +        //depth buffer +        let depth_texture = device.create_texture(&wgpu::TextureDescriptor { +            label: Some("cubes depth texture"), +            size: wgpu::Extent3d { +                width: target_size.width, +                height: target_size.height, +                depth_or_array_layers: 1, +            }, +            mip_level_count: 1, +            sample_count: 1, +            dimension: wgpu::TextureDimension::D2, +            format: wgpu::TextureFormat::Depth32Float, +            usage: wgpu::TextureUsages::RENDER_ATTACHMENT +                | wgpu::TextureUsages::TEXTURE_BINDING, +            view_formats: &[], +        }); + +        let depth_view = +            depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + +        let normal_map_data = load_normal_map_data(); + +        //normal map +        let normal_texture = device.create_texture_with_data( +            queue, +            &wgpu::TextureDescriptor { +                label: Some("cubes normal map texture"), +                size: wgpu::Extent3d { +                    width: 1024, +                    height: 1024, +                    depth_or_array_layers: 1, +                }, +                mip_level_count: 1, +                sample_count: 1, +                dimension: wgpu::TextureDimension::D2, +                format: wgpu::TextureFormat::Rgba8Unorm, +                usage: wgpu::TextureUsages::TEXTURE_BINDING, +                view_formats: &[], +            }, +            &normal_map_data, +        ); + +        let normal_view = +            normal_texture.create_view(&wgpu::TextureViewDescriptor::default()); + +        //skybox texture for reflection/refraction +        let skybox_data = load_skybox_data(); + +        let skybox_texture = device.create_texture_with_data( +            queue, +            &wgpu::TextureDescriptor { +                label: Some("cubes skybox texture"), +                size: wgpu::Extent3d { +                    width: SKY_TEXTURE_SIZE, +                    height: SKY_TEXTURE_SIZE, +                    depth_or_array_layers: 6, //one for each face of the cube +                }, +                mip_level_count: 1, +                sample_count: 1, +                dimension: wgpu::TextureDimension::D2, +                format: wgpu::TextureFormat::Rgba8Unorm, +                usage: wgpu::TextureUsages::TEXTURE_BINDING, +                view_formats: &[], +            }, +            &skybox_data, +        ); + +        let sky_view = +            skybox_texture.create_view(&wgpu::TextureViewDescriptor { +                label: Some("cubes skybox texture view"), +                dimension: Some(wgpu::TextureViewDimension::Cube), +                ..Default::default() +            }); + +        let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor { +            label: Some("cubes skybox sampler"), +            address_mode_u: wgpu::AddressMode::ClampToEdge, +            address_mode_v: wgpu::AddressMode::ClampToEdge, +            address_mode_w: wgpu::AddressMode::ClampToEdge, +            mag_filter: wgpu::FilterMode::Linear, +            min_filter: wgpu::FilterMode::Linear, +            mipmap_filter: wgpu::FilterMode::Linear, +            ..Default::default() +        }); + +        let uniform_bind_group_layout = +            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { +                label: Some("cubes uniform bind group layout"), +                entries: &[ +                    wgpu::BindGroupLayoutEntry { +                        binding: 0, +                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, +                        ty: wgpu::BindingType::Buffer { +                            ty: wgpu::BufferBindingType::Uniform, +                            has_dynamic_offset: false, +                            min_binding_size: None, +                        }, +                        count: None, +                    }, +                    wgpu::BindGroupLayoutEntry { +                        binding: 1, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Texture { +                            sample_type: wgpu::TextureSampleType::Float { +                                filterable: true, +                            }, +                            view_dimension: wgpu::TextureViewDimension::Cube, +                            multisampled: false, +                        }, +                        count: None, +                    }, +                    wgpu::BindGroupLayoutEntry { +                        binding: 2, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Sampler( +                            wgpu::SamplerBindingType::Filtering, +                        ), +                        count: None, +                    }, +                    wgpu::BindGroupLayoutEntry { +                        binding: 3, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Texture { +                            sample_type: wgpu::TextureSampleType::Float { +                                filterable: true, +                            }, +                            view_dimension: wgpu::TextureViewDimension::D2, +                            multisampled: false, +                        }, +                        count: None, +                    }, +                ], +            }); + +        let uniform_bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("cubes uniform bind group"), +                layout: &uniform_bind_group_layout, +                entries: &[ +                    wgpu::BindGroupEntry { +                        binding: 0, +                        resource: uniforms.as_entire_binding(), +                    }, +                    wgpu::BindGroupEntry { +                        binding: 1, +                        resource: wgpu::BindingResource::TextureView(&sky_view), +                    }, +                    wgpu::BindGroupEntry { +                        binding: 2, +                        resource: wgpu::BindingResource::Sampler(&sky_sampler), +                    }, +                    wgpu::BindGroupEntry { +                        binding: 3, +                        resource: wgpu::BindingResource::TextureView( +                            &normal_view, +                        ), +                    }, +                ], +            }); + +        let layout = +            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { +                label: Some("cubes pipeline layout"), +                bind_group_layouts: &[&uniform_bind_group_layout], +                push_constant_ranges: &[], +            }); + +        let shader = +            device.create_shader_module(wgpu::ShaderModuleDescriptor { +                label: Some("cubes shader"), +                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( +                    include_str!("shaders/cubes.wgsl"), +                )), +            }); + +        let pipeline = +            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { +                label: Some("cubes pipeline"), +                layout: Some(&layout), +                vertex: wgpu::VertexState { +                    module: &shader, +                    entry_point: "vs_main", +                    buffers: &[primitive::Vertex::desc(), cube::Raw::desc()], +                }, +                primitive: wgpu::PrimitiveState::default(), +                depth_stencil: Some(wgpu::DepthStencilState { +                    format: wgpu::TextureFormat::Depth32Float, +                    depth_write_enabled: true, +                    depth_compare: wgpu::CompareFunction::Less, +                    stencil: wgpu::StencilState::default(), +                    bias: wgpu::DepthBiasState::default(), +                }), +                multisample: wgpu::MultisampleState { +                    count: 1, +                    mask: !0, +                    alpha_to_coverage_enabled: false, +                }, +                fragment: Some(wgpu::FragmentState { +                    module: &shader, +                    entry_point: "fs_main", +                    targets: &[Some(wgpu::ColorTargetState { +                        format, +                        blend: Some(wgpu::BlendState { +                            color: wgpu::BlendComponent { +                                src_factor: wgpu::BlendFactor::SrcAlpha, +                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, +                                operation: wgpu::BlendOperation::Add, +                            }, +                            alpha: wgpu::BlendComponent { +                                src_factor: wgpu::BlendFactor::One, +                                dst_factor: wgpu::BlendFactor::One, +                                operation: wgpu::BlendOperation::Max, +                            }, +                        }), +                        write_mask: wgpu::ColorWrites::ALL, +                    })], +                }), +                multiview: None, +            }); + +        let depth_pipeline = DepthPipeline::new( +            device, +            format, +            depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), +        ); + +        Self { +            pipeline, +            cubes: cubes_buffer, +            uniforms, +            uniform_bind_group, +            vertices, +            depth_texture_size: target_size, +            depth_view, +            depth_pipeline, +        } +    } + +    fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) { +        if self.depth_texture_size.height != size.height +            || self.depth_texture_size.width != size.width +        { +            let text = device.create_texture(&wgpu::TextureDescriptor { +                label: Some("cubes depth texture"), +                size: wgpu::Extent3d { +                    width: size.width, +                    height: size.height, +                    depth_or_array_layers: 1, +                }, +                mip_level_count: 1, +                sample_count: 1, +                dimension: wgpu::TextureDimension::D2, +                format: wgpu::TextureFormat::Depth32Float, +                usage: wgpu::TextureUsages::RENDER_ATTACHMENT +                    | wgpu::TextureUsages::TEXTURE_BINDING, +                view_formats: &[], +            }); + +            self.depth_view = +                text.create_view(&wgpu::TextureViewDescriptor::default()); +            self.depth_texture_size = size; + +            self.depth_pipeline.update(device, &text); +        } +    } + +    pub fn update( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        target_size: Size<u32>, +        uniforms: &Uniforms, +        num_cubes: usize, +        cubes: &[cube::Raw], +    ) { +        //recreate depth texture if surface texture size has changed +        self.update_depth_texture(device, target_size); + +        // update uniforms +        queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms)); + +        //resize cubes vertex buffer if cubes amount changed +        let new_size = num_cubes * std::mem::size_of::<cube::Raw>(); +        self.cubes.resize(device, new_size as u64); + +        //always write new cube data since they are constantly rotating +        queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes)); +    } + +    pub fn render( +        &self, +        target: &wgpu::TextureView, +        encoder: &mut wgpu::CommandEncoder, +        bounds: Rectangle<u32>, +        num_cubes: u32, +        show_depth: bool, +    ) { +        { +            let mut pass = +                encoder.begin_render_pass(&wgpu::RenderPassDescriptor { +                    label: Some("cubes.pipeline.pass"), +                    color_attachments: &[Some( +                        wgpu::RenderPassColorAttachment { +                            view: target, +                            resolve_target: None, +                            ops: wgpu::Operations { +                                load: wgpu::LoadOp::Load, +                                store: true, +                            }, +                        }, +                    )], +                    depth_stencil_attachment: Some( +                        wgpu::RenderPassDepthStencilAttachment { +                            view: &self.depth_view, +                            depth_ops: Some(wgpu::Operations { +                                load: wgpu::LoadOp::Clear(1.0), +                                store: true, +                            }), +                            stencil_ops: None, +                        }, +                    ), +                }); + +            pass.set_scissor_rect( +                bounds.x, +                bounds.y, +                bounds.width, +                bounds.height, +            ); +            pass.set_pipeline(&self.pipeline); +            pass.set_bind_group(0, &self.uniform_bind_group, &[]); +            pass.set_vertex_buffer(0, self.vertices.slice(..)); +            pass.set_vertex_buffer(1, self.cubes.raw.slice(..)); +            pass.draw(0..36, 0..num_cubes); +        } + +        if show_depth { +            self.depth_pipeline.render(encoder, target, bounds); +        } +    } +} + +struct DepthPipeline { +    pipeline: wgpu::RenderPipeline, +    bind_group_layout: wgpu::BindGroupLayout, +    bind_group: wgpu::BindGroup, +    sampler: wgpu::Sampler, +    depth_view: wgpu::TextureView, +} + +impl DepthPipeline { +    pub fn new( +        device: &wgpu::Device, +        format: wgpu::TextureFormat, +        depth_texture: wgpu::TextureView, +    ) -> Self { +        let sampler = device.create_sampler(&wgpu::SamplerDescriptor { +            label: Some("cubes.depth_pipeline.sampler"), +            ..Default::default() +        }); + +        let bind_group_layout = +            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { +                label: Some("cubes.depth_pipeline.bind_group_layout"), +                entries: &[ +                    wgpu::BindGroupLayoutEntry { +                        binding: 0, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Sampler( +                            wgpu::SamplerBindingType::NonFiltering, +                        ), +                        count: None, +                    }, +                    wgpu::BindGroupLayoutEntry { +                        binding: 1, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Texture { +                            sample_type: wgpu::TextureSampleType::Float { +                                filterable: false, +                            }, +                            view_dimension: wgpu::TextureViewDimension::D2, +                            multisampled: false, +                        }, +                        count: None, +                    }, +                ], +            }); + +        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("cubes.depth_pipeline.bind_group"), +            layout: &bind_group_layout, +            entries: &[ +                wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Sampler(&sampler), +                }, +                wgpu::BindGroupEntry { +                    binding: 1, +                    resource: wgpu::BindingResource::TextureView( +                        &depth_texture, +                    ), +                }, +            ], +        }); + +        let layout = +            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { +                label: Some("cubes.depth_pipeline.layout"), +                bind_group_layouts: &[&bind_group_layout], +                push_constant_ranges: &[], +            }); + +        let shader = +            device.create_shader_module(wgpu::ShaderModuleDescriptor { +                label: Some("cubes.depth_pipeline.shader"), +                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( +                    include_str!("shaders/depth.wgsl"), +                )), +            }); + +        let pipeline = +            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { +                label: Some("cubes.depth_pipeline.pipeline"), +                layout: Some(&layout), +                vertex: wgpu::VertexState { +                    module: &shader, +                    entry_point: "vs_main", +                    buffers: &[], +                }, +                primitive: Default::default(), +                depth_stencil: Some(wgpu::DepthStencilState { +                    format: wgpu::TextureFormat::Depth32Float, +                    depth_write_enabled: false, +                    depth_compare: wgpu::CompareFunction::Less, +                    stencil: Default::default(), +                    bias: Default::default(), +                }), +                multisample: Default::default(), +                fragment: Some(wgpu::FragmentState { +                    module: &shader, +                    entry_point: "fs_main", +                    targets: &[Some(wgpu::ColorTargetState { +                        format, +                        blend: Some(wgpu::BlendState::REPLACE), +                        write_mask: wgpu::ColorWrites::ALL, +                    })], +                }), +                multiview: None, +            }); + +        Self { +            pipeline, +            bind_group_layout, +            bind_group, +            sampler, +            depth_view: depth_texture, +        } +    } + +    pub fn update( +        &mut self, +        device: &wgpu::Device, +        depth_texture: &wgpu::Texture, +    ) { +        self.depth_view = +            depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + +        self.bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("cubes.depth_pipeline.bind_group"), +                layout: &self.bind_group_layout, +                entries: &[ +                    wgpu::BindGroupEntry { +                        binding: 0, +                        resource: wgpu::BindingResource::Sampler(&self.sampler), +                    }, +                    wgpu::BindGroupEntry { +                        binding: 1, +                        resource: wgpu::BindingResource::TextureView( +                            &self.depth_view, +                        ), +                    }, +                ], +            }); +    } + +    pub fn render( +        &self, +        encoder: &mut wgpu::CommandEncoder, +        target: &wgpu::TextureView, +        bounds: Rectangle<u32>, +    ) { +        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { +            label: Some("cubes.pipeline.depth_pass"), +            color_attachments: &[Some(wgpu::RenderPassColorAttachment { +                view: target, +                resolve_target: None, +                ops: wgpu::Operations { +                    load: wgpu::LoadOp::Load, +                    store: true, +                }, +            })], +            depth_stencil_attachment: Some( +                wgpu::RenderPassDepthStencilAttachment { +                    view: &self.depth_view, +                    depth_ops: None, +                    stencil_ops: None, +                }, +            ), +        }); + +        pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); +        pass.set_pipeline(&self.pipeline); +        pass.set_bind_group(0, &self.bind_group, &[]); +        pass.draw(0..6, 0..1); +    } +} + +fn load_skybox_data() -> Vec<u8> { +    let pos_x: &[u8] = include_bytes!("textures/skybox/pos_x.jpg"); +    let neg_x: &[u8] = include_bytes!("textures/skybox/neg_x.jpg"); +    let pos_y: &[u8] = include_bytes!("textures/skybox/pos_y.jpg"); +    let neg_y: &[u8] = include_bytes!("textures/skybox/neg_y.jpg"); +    let pos_z: &[u8] = include_bytes!("textures/skybox/pos_z.jpg"); +    let neg_z: &[u8] = include_bytes!("textures/skybox/neg_z.jpg"); + +    let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; + +    data.iter().fold(vec![], |mut acc, bytes| { +        let i = image::load_from_memory_with_format( +            bytes, +            image::ImageFormat::Jpeg, +        ) +        .unwrap() +        .to_rgba8() +        .into_raw(); + +        acc.extend(i); +        acc +    }) +} + +fn load_normal_map_data() -> Vec<u8> { +    let bytes: &[u8] = include_bytes!("textures/ice_cube_normal_map.png"); + +    image::load_from_memory_with_format(bytes, image::ImageFormat::Png) +        .unwrap() +        .to_rgba8() +        .into_raw() +} diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs new file mode 100644 index 00000000..2201218f --- /dev/null +++ b/examples/custom_shader/src/primitive.rs @@ -0,0 +1,95 @@ +pub mod cube; +pub mod vertex; + +mod buffer; +mod uniforms; + +use crate::camera::Camera; +use crate::pipeline::Pipeline; +use crate::primitive::cube::Cube; +use iced::advanced::graphics::Transformation; +use iced::widget::shader; +use iced::{Color, Rectangle, Size}; + +pub use crate::primitive::vertex::Vertex; +pub use buffer::Buffer; +pub use uniforms::Uniforms; + +/// A collection of `Cube`s that can be rendered. +#[derive(Debug)] +pub struct Primitive { +    cubes: Vec<cube::Raw>, +    uniforms: Uniforms, +    show_depth_buffer: bool, +} + +impl Primitive { +    pub fn new( +        cubes: &[Cube], +        camera: &Camera, +        bounds: Rectangle, +        show_depth_buffer: bool, +        light_color: Color, +    ) -> Self { +        let uniforms = Uniforms::new(camera, bounds, light_color); + +        Self { +            cubes: cubes +                .iter() +                .map(cube::Raw::from_cube) +                .collect::<Vec<cube::Raw>>(), +            uniforms, +            show_depth_buffer, +        } +    } +} + +impl shader::Primitive for Primitive { +    fn prepare( +        &self, +        format: wgpu::TextureFormat, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        target_size: Size<u32>, +        _scale_factor: f32, +        _transform: Transformation, +        storage: &mut shader::Storage, +    ) { +        if !storage.has::<Pipeline>() { +            storage.store(Pipeline::new(device, queue, format, target_size)) +        } + +        let pipeline = storage.get_mut::<Pipeline>().unwrap(); + +        //upload data to GPU +        pipeline.update( +            device, +            queue, +            target_size, +            &self.uniforms, +            self.cubes.len(), +            &self.cubes, +        ); +    } + +    fn render( +        &self, +        storage: &shader::Storage, +        bounds: Rectangle<u32>, +        target: &wgpu::TextureView, +        _target_size: Size<u32>, +        encoder: &mut wgpu::CommandEncoder, +    ) { +        //at this point our pipeline should always be initialized +        let pipeline = storage.get::<Pipeline>().unwrap(); + +        //render primitive +        pipeline.render( +            target, +            encoder, +            bounds, +            self.cubes.len() as u32, +            self.show_depth_buffer, +        ) +    } +} diff --git a/examples/custom_shader/src/primitive/buffer.rs b/examples/custom_shader/src/primitive/buffer.rs new file mode 100644 index 00000000..377ce1bb --- /dev/null +++ b/examples/custom_shader/src/primitive/buffer.rs @@ -0,0 +1,39 @@ +// A custom buffer container for dynamic resizing. +pub struct Buffer { +    pub raw: wgpu::Buffer, +    label: &'static str, +    size: u64, +    usage: wgpu::BufferUsages, +} + +impl Buffer { +    pub fn new( +        device: &wgpu::Device, +        label: &'static str, +        size: u64, +        usage: wgpu::BufferUsages, +    ) -> Self { +        Self { +            raw: device.create_buffer(&wgpu::BufferDescriptor { +                label: Some(label), +                size, +                usage, +                mapped_at_creation: false, +            }), +            label, +            size, +            usage, +        } +    } + +    pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) { +        if new_size > self.size { +            self.raw = device.create_buffer(&wgpu::BufferDescriptor { +                label: Some(self.label), +                size: new_size, +                usage: self.usage, +                mapped_at_creation: false, +            }); +        } +    } +} diff --git a/examples/custom_shader/src/primitive/cube.rs b/examples/custom_shader/src/primitive/cube.rs new file mode 100644 index 00000000..c23f2132 --- /dev/null +++ b/examples/custom_shader/src/primitive/cube.rs @@ -0,0 +1,324 @@ +use crate::primitive::Vertex; +use glam::{vec2, vec3, Vec3}; +use rand::{thread_rng, Rng}; + +/// A single instance of a cube. +#[derive(Debug, Clone)] +pub struct Cube { +    pub rotation: glam::Quat, +    pub position: Vec3, +    pub size: f32, +    rotation_dir: f32, +    rotation_axis: glam::Vec3, +} + +impl Default for Cube { +    fn default() -> Self { +        Self { +            rotation: glam::Quat::IDENTITY, +            position: glam::Vec3::ZERO, +            size: 0.1, +            rotation_dir: 1.0, +            rotation_axis: glam::Vec3::Y, +        } +    } +} + +impl Cube { +    pub fn new(size: f32, origin: Vec3) -> Self { +        let rnd = thread_rng().gen_range(0.0..=1.0f32); + +        Self { +            rotation: glam::Quat::IDENTITY, +            position: origin + Vec3::new(0.1, 0.1, 0.1), +            size, +            rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 }, +            rotation_axis: if rnd <= 0.33 { +                glam::Vec3::Y +            } else if rnd <= 0.66 { +                glam::Vec3::X +            } else { +                glam::Vec3::Z +            }, +        } +    } + +    pub fn update(&mut self, size: f32, time: f32) { +        self.rotation = glam::Quat::from_axis_angle( +            self.rotation_axis, +            time / 2.0 * self.rotation_dir, +        ); +        self.size = size; +    } +} + +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)] +#[repr(C)] +pub struct Raw { +    transformation: glam::Mat4, +    normal: glam::Mat3, +    _padding: [f32; 3], +} + +impl Raw { +    const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![ +        //cube transformation matrix +        4 => Float32x4, +        5 => Float32x4, +        6 => Float32x4, +        7 => Float32x4, +        //normal rotation matrix +        8 => Float32x3, +        9 => Float32x3, +        10 => Float32x3, +    ]; + +    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { +        wgpu::VertexBufferLayout { +            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress, +            step_mode: wgpu::VertexStepMode::Instance, +            attributes: &Self::ATTRIBS, +        } +    } +} + +impl Raw { +    pub fn from_cube(cube: &Cube) -> Raw { +        Raw { +            transformation: glam::Mat4::from_scale_rotation_translation( +                glam::vec3(cube.size, cube.size, cube.size), +                cube.rotation, +                cube.position, +            ), +            normal: glam::Mat3::from_quat(cube.rotation), +            _padding: [0.0; 3], +        } +    } + +    pub fn vertices() -> [Vertex; 36] { +        [ +            //face 1 +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(0.0, 0.0, -1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            //face 2 +            Vertex { +                pos: vec3(-0.5, -0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, 0.5), +                normal: vec3(0.0, 0.0, 1.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            //face 3 +            Vertex { +                pos: vec3(-0.5, 0.5, 0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, -0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, 0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, 0.5), +                normal: vec3(-1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 1.0), +            }, +            //face 4 +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, -0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, -0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, -0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, 0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(1.0, 0.0, 0.0), +                tangent: vec3(0.0, 0.0, -1.0), +                uv: vec2(0.0, 1.0), +            }, +            //face 5 +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, -0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, 0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, -0.5, 0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, 0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, -0.5, -0.5), +                normal: vec3(0.0, -1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            //face 6 +            Vertex { +                pos: vec3(-0.5, 0.5, -0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, -0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 1.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(0.5, 0.5, 0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(1.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, 0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 0.0), +            }, +            Vertex { +                pos: vec3(-0.5, 0.5, -0.5), +                normal: vec3(0.0, 1.0, 0.0), +                tangent: vec3(1.0, 0.0, 0.0), +                uv: vec2(0.0, 1.0), +            }, +        ] +    } +} diff --git a/examples/custom_shader/src/primitive/uniforms.rs b/examples/custom_shader/src/primitive/uniforms.rs new file mode 100644 index 00000000..4fcb413b --- /dev/null +++ b/examples/custom_shader/src/primitive/uniforms.rs @@ -0,0 +1,22 @@ +use crate::camera::Camera; +use iced::{Color, Rectangle}; + +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Uniforms { +    camera_proj: glam::Mat4, +    camera_pos: glam::Vec4, +    light_color: glam::Vec4, +} + +impl Uniforms { +    pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self { +        let camera_proj = camera.build_view_proj_matrix(bounds); + +        Self { +            camera_proj, +            camera_pos: camera.position(), +            light_color: glam::Vec4::from(light_color.into_linear()), +        } +    } +} diff --git a/examples/custom_shader/src/primitive/vertex.rs b/examples/custom_shader/src/primitive/vertex.rs new file mode 100644 index 00000000..6d17aa0f --- /dev/null +++ b/examples/custom_shader/src/primitive/vertex.rs @@ -0,0 +1,29 @@ +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Vertex { +    pub pos: glam::Vec3, +    pub normal: glam::Vec3, +    pub tangent: glam::Vec3, +    pub uv: glam::Vec2, +} + +impl Vertex { +    const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![ +        //position +        0 => Float32x3, +        //normal +        1 => Float32x3, +        //tangent +        2 => Float32x3, +        //uv +        3 => Float32x2, +    ]; + +    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { +        wgpu::VertexBufferLayout { +            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress, +            step_mode: wgpu::VertexStepMode::Vertex, +            attributes: &Self::ATTRIBS, +        } +    } +} diff --git a/examples/custom_shader/src/shaders/cubes.wgsl b/examples/custom_shader/src/shaders/cubes.wgsl new file mode 100644 index 00000000..cd7f94d8 --- /dev/null +++ b/examples/custom_shader/src/shaders/cubes.wgsl @@ -0,0 +1,123 @@ +struct Uniforms { +    projection: mat4x4<f32>, +    camera_pos: vec4<f32>, +    light_color: vec4<f32>, +} + +const LIGHT_POS: vec3<f32> = vec3<f32>(0.0, 3.0, 3.0); + +@group(0) @binding(0) var<uniform> uniforms: Uniforms; +@group(0) @binding(1) var sky_texture: texture_cube<f32>; +@group(0) @binding(2) var tex_sampler: sampler; +@group(0) @binding(3) var normal_texture: texture_2d<f32>; + +struct Vertex { +    @location(0) position: vec3<f32>, +    @location(1) normal: vec3<f32>, +    @location(2) tangent: vec3<f32>, +    @location(3) uv: vec2<f32>, +} + +struct Cube { +    @location(4) matrix_0: vec4<f32>, +    @location(5) matrix_1: vec4<f32>, +    @location(6) matrix_2: vec4<f32>, +    @location(7) matrix_3: vec4<f32>, +    @location(8) normal_matrix_0: vec3<f32>, +    @location(9) normal_matrix_1: vec3<f32>, +    @location(10) normal_matrix_2: vec3<f32>, +} + +struct Output { +    @builtin(position) clip_pos: vec4<f32>, +    @location(0) uv: vec2<f32>, +    @location(1) tangent_pos: vec3<f32>, +    @location(2) tangent_camera_pos: vec3<f32>, +    @location(3) tangent_light_pos: vec3<f32>, +} + +@vertex +fn vs_main(vertex: Vertex, cube: Cube) -> Output { +     let cube_matrix = mat4x4<f32>( +         cube.matrix_0, +         cube.matrix_1, +         cube.matrix_2, +         cube.matrix_3, +     ); + +    let normal_matrix = mat3x3<f32>( +        cube.normal_matrix_0, +        cube.normal_matrix_1, +        cube.normal_matrix_2, +    ); + +    //convert to tangent space to calculate lighting in same coordinate space as normal map sample +    let tangent = normalize(normal_matrix * vertex.tangent); +    let normal = normalize(normal_matrix * vertex.normal); +    let bitangent = cross(tangent, normal); + +    //shift everything into tangent space +    let tbn = transpose(mat3x3<f32>(tangent, bitangent, normal)); + +    let world_pos = cube_matrix * vec4<f32>(vertex.position, 1.0); + +    var out: Output; +    out.clip_pos = uniforms.projection * world_pos; +    out.uv = vertex.uv; +    out.tangent_pos = tbn * world_pos.xyz; +    out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz; +    out.tangent_light_pos = tbn * LIGHT_POS; + +    return out; +} + +//cube properties +const CUBE_BASE_COLOR: vec4<f32> = vec4<f32>(0.294118, 0.462745, 0.611765, 0.6); +const SHINE_DAMPER: f32 = 1.0; +const REFLECTIVITY: f32 = 0.8; +const REFRACTION_INDEX: f32 = 1.31; + +//fog, for the ~* cinematic effect *~ +const FOG_DENSITY: f32 = 0.15; +const FOG_GRADIENT: f32 = 8.0; +const FOG_COLOR: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0); + +@fragment +fn fs_main(in: Output) -> @location(0) vec4<f32> { +    let to_camera = in.tangent_camera_pos - in.tangent_pos; + +    //normal sample from texture +    var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz; +    normal = normal * 2.0 - 1.0; + +    //diffuse +    let dir_to_light: vec3<f32> = normalize(in.tangent_light_pos - in.tangent_pos); +    let brightness = max(dot(normal, dir_to_light), 0.0); +    let diffuse: vec3<f32> = brightness * uniforms.light_color.xyz; + +    //specular +    let dir_to_camera = normalize(to_camera); +    let light_dir = -dir_to_light; +    let reflected_light_dir = reflect(light_dir, normal); +    let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0); +    let damped_factor = pow(specular_factor, SHINE_DAMPER); +    let specular: vec3<f32> = damped_factor * uniforms.light_color.xyz * REFLECTIVITY; + +    //fog +    let distance = length(to_camera); +    let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0); + +    //reflection +    let reflection_dir = reflect(dir_to_camera, normal); +    let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir); +    let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX); +    let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir); +    let final_reflect_color = mix(reflection_color, refraction_color, 0.5); + +    //mix it all together! +    var color = vec4<f32>(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w); +    color = mix(color, final_reflect_color, 0.8); +    color = mix(FOG_COLOR, color, visibility); + +    return color; +} diff --git a/examples/custom_shader/src/shaders/depth.wgsl b/examples/custom_shader/src/shaders/depth.wgsl new file mode 100644 index 00000000..a3f7e5ec --- /dev/null +++ b/examples/custom_shader/src/shaders/depth.wgsl @@ -0,0 +1,48 @@ +var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>( +    vec2<f32>(-1.0, 1.0), +    vec2<f32>(-1.0, -1.0), +    vec2<f32>(1.0, -1.0), +    vec2<f32>(-1.0, 1.0), +    vec2<f32>(1.0, 1.0), +    vec2<f32>(1.0, -1.0) +); + +var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>( +    vec2<f32>(0.0, 0.0), +    vec2<f32>(0.0, 1.0), +    vec2<f32>(1.0, 1.0), +    vec2<f32>(0.0, 0.0), +    vec2<f32>(1.0, 0.0), +    vec2<f32>(1.0, 1.0) +); + +@group(0) @binding(0) var depth_sampler: sampler; +@group(0) @binding(1) var depth_texture: texture_2d<f32>; + +struct Output { +    @builtin(position) position: vec4<f32>, +    @location(0) uv: vec2<f32>, +} + +@vertex +fn vs_main(@builtin(vertex_index) v_index: u32) -> Output { +    var out: Output; + +    out.position = vec4<f32>(positions[v_index], 0.0, 1.0); +    out.uv = uvs[v_index]; + +    return out; +} + +@fragment +fn fs_main(input: Output) -> @location(0) vec4<f32> { +    let depth = textureSample(depth_texture, depth_sampler, input.uv).r; + +    if (depth > .9999) { +        discard; +    } + +    let c = 1.0 - depth; + +    return vec4<f32>(c, c, c, 1.0); +} diff --git a/examples/custom_shader/src/textures/ice_cube_normal_map.png b/examples/custom_shader/src/textures/ice_cube_normal_map.png Binary files differnew file mode 100644 index 00000000..7b4b7228 --- /dev/null +++ b/examples/custom_shader/src/textures/ice_cube_normal_map.png diff --git a/examples/custom_shader/src/textures/skybox/neg_x.jpg b/examples/custom_shader/src/textures/skybox/neg_x.jpg Binary files differnew file mode 100644 index 00000000..00cc783d --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/neg_x.jpg diff --git a/examples/custom_shader/src/textures/skybox/neg_y.jpg b/examples/custom_shader/src/textures/skybox/neg_y.jpg Binary files differnew file mode 100644 index 00000000..548f6445 --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/neg_y.jpg diff --git a/examples/custom_shader/src/textures/skybox/neg_z.jpg b/examples/custom_shader/src/textures/skybox/neg_z.jpg Binary files differnew file mode 100644 index 00000000..5698512e --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/neg_z.jpg diff --git a/examples/custom_shader/src/textures/skybox/pos_x.jpg b/examples/custom_shader/src/textures/skybox/pos_x.jpg Binary files differnew file mode 100644 index 00000000..dddecba7 --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/pos_x.jpg diff --git a/examples/custom_shader/src/textures/skybox/pos_y.jpg b/examples/custom_shader/src/textures/skybox/pos_y.jpg Binary files differnew file mode 100644 index 00000000..361427fd --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/pos_y.jpg diff --git a/examples/custom_shader/src/textures/skybox/pos_z.jpg b/examples/custom_shader/src/textures/skybox/pos_z.jpg Binary files differnew file mode 100644 index 00000000..0085a49e --- /dev/null +++ b/examples/custom_shader/src/textures/skybox/pos_z.jpg diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c26d52fe..0f32fca0 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -271,6 +271,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {                                  &queue,                                  &mut encoder,                                  None, +                                frame.texture.format(),                                  &view,                                  primitive,                                  &viewport, diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index a7aea352..6741d7cf 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -16,7 +16,6 @@ all-features = true  [features]  geometry = ["lyon_path"] -opengl = []  image = ["dep:image", "kamadak-exif"]  web-colors = [] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 78dec847..8c5ee2f0 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -7,6 +7,7 @@ pub mod compositor;  pub mod geometry;  mod settings; +pub mod widget;  pub use iced_graphics as graphics;  pub use iced_graphics::core; @@ -59,6 +60,26 @@ impl<T> Renderer<T> {              }          }      } + +    pub fn draw_custom<P: widget::shader::Primitive>( +        &mut self, +        bounds: Rectangle, +        primitive: P, +    ) { +        match self { +            Renderer::TinySkia(_) => { +                log::warn!( +                    "Custom shader primitive is unavailable with tiny-skia." +                ); +            } +            #[cfg(feature = "wgpu")] +            Renderer::Wgpu(renderer) => { +                renderer.draw_primitive(iced_wgpu::Primitive::Custom( +                    iced_wgpu::primitive::Custom::shader(bounds, primitive), +                )) +            } +        } +    }  }  impl<T> core::Renderer for Renderer<T> { diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 6c0c2a83..0422c99c 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -9,3 +9,6 @@ pub mod qr_code;  #[cfg(feature = "qr_code")]  pub use qr_code::QRCode; + +#[cfg(feature = "wgpu")] +pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs new file mode 100644 index 00000000..da42a7dd --- /dev/null +++ b/renderer/src/widget/shader.rs @@ -0,0 +1,215 @@ +//! A custom shader widget for wgpu applications. +use crate::core::event::Status; +use crate::core::layout::{Limits, Node}; +use crate::core::mouse::{Cursor, Interaction}; +use crate::core::renderer::Style; +use crate::core::widget::tree::{State, Tag}; +use crate::core::widget::{tree, Tree}; +use crate::core::{ +    self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, +    Shell, Size, Widget, +}; +use std::marker::PhantomData; + +mod event; +mod program; + +pub use event::Event; +pub use iced_wgpu::custom::Primitive; +pub use iced_wgpu::custom::Storage; +pub use program::Program; + +/// A widget which can render custom shaders with Iced's `wgpu` backend. +/// +/// Must be initialized with a [`Program`], which describes the internal widget state & how +/// its [`Program::Primitive`]s are drawn. +#[allow(missing_debug_implementations)] +pub struct Shader<Message, P: Program<Message>> { +    width: Length, +    height: Length, +    program: P, +    _message: PhantomData<Message>, +} + +impl<Message, P: Program<Message>> Shader<Message, P> { +    /// Create a new custom [`Shader`]. +    pub fn new(program: P) -> Self { +        Self { +            width: Length::Fixed(100.0), +            height: Length::Fixed(100.0), +            program, +            _message: PhantomData, +        } +    } + +    /// Set the `width` of the custom [`Shader`]. +    pub fn width(mut self, width: impl Into<Length>) -> Self { +        self.width = width.into(); +        self +    } + +    /// Set the `height` of the custom [`Shader`]. +    pub fn height(mut self, height: impl Into<Length>) -> Self { +        self.height = height.into(); +        self +    } +} + +impl<P, Message, Theme> Widget<Message, crate::Renderer<Theme>> +    for Shader<Message, P> +where +    P: Program<Message>, +{ +    fn tag(&self) -> Tag { +        struct Tag<T>(T); +        tree::Tag::of::<Tag<P::State>>() +    } + +    fn state(&self) -> State { +        tree::State::new(P::State::default()) +    } + +    fn width(&self) -> Length { +        self.width +    } + +    fn height(&self) -> Length { +        self.height +    } + +    fn layout( +        &self, +        _tree: &mut Tree, +        _renderer: &crate::Renderer<Theme>, +        limits: &Limits, +    ) -> Node { +        let limits = limits.width(self.width).height(self.height); +        let size = limits.resolve(Size::ZERO); + +        layout::Node::new(size) +    } + +    fn on_event( +        &mut self, +        tree: &mut Tree, +        event: crate::core::Event, +        layout: Layout<'_>, +        cursor: Cursor, +        _renderer: &crate::Renderer<Theme>, +        _clipboard: &mut dyn Clipboard, +        shell: &mut Shell<'_, Message>, +        _viewport: &Rectangle, +    ) -> Status { +        let bounds = layout.bounds(); + +        let custom_shader_event = match event { +            core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), +            core::Event::Keyboard(keyboard_event) => { +                Some(Event::Keyboard(keyboard_event)) +            } +            core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), +            _ => None, +        }; + +        if let Some(custom_shader_event) = custom_shader_event { +            let state = tree.state.downcast_mut::<P::State>(); + +            let (event_status, message) = self.program.update( +                state, +                custom_shader_event, +                bounds, +                cursor, +                shell, +            ); + +            if let Some(message) = message { +                shell.publish(message); +            } + +            return event_status; +        } + +        event::Status::Ignored +    } + +    fn mouse_interaction( +        &self, +        tree: &Tree, +        layout: Layout<'_>, +        cursor: Cursor, +        _viewport: &Rectangle, +        _renderer: &crate::Renderer<Theme>, +    ) -> mouse::Interaction { +        let bounds = layout.bounds(); +        let state = tree.state.downcast_ref::<P::State>(); + +        self.program.mouse_interaction(state, bounds, cursor) +    } + +    fn draw( +        &self, +        tree: &widget::Tree, +        renderer: &mut crate::Renderer<Theme>, +        _theme: &Theme, +        _style: &Style, +        layout: Layout<'_>, +        cursor_position: mouse::Cursor, +        _viewport: &Rectangle, +    ) { +        let bounds = layout.bounds(); +        let state = tree.state.downcast_ref::<P::State>(); + +        renderer.draw_custom( +            bounds, +            self.program.draw(state, cursor_position, bounds), +        ); +    } +} + +impl<'a, M, P, Theme> From<Shader<M, P>> +    for Element<'a, M, crate::Renderer<Theme>> +where +    M: 'a, +    P: Program<M> + 'a, +{ +    fn from(custom: Shader<M, P>) -> Element<'a, M, crate::Renderer<Theme>> { +        Element::new(custom) +    } +} + +impl<Message, T> Program<Message> for &T +where +    T: Program<Message>, +{ +    type State = T::State; +    type Primitive = T::Primitive; + +    fn update( +        &self, +        state: &mut Self::State, +        event: Event, +        bounds: Rectangle, +        cursor: Cursor, +        shell: &mut Shell<'_, Message>, +    ) -> (Status, Option<Message>) { +        T::update(self, state, event, bounds, cursor, shell) +    } + +    fn draw( +        &self, +        state: &Self::State, +        cursor: Cursor, +        bounds: Rectangle, +    ) -> Self::Primitive { +        T::draw(self, state, cursor, bounds) +    } + +    fn mouse_interaction( +        &self, +        state: &Self::State, +        bounds: Rectangle, +        cursor: Cursor, +    ) -> Interaction { +        T::mouse_interaction(self, state, bounds, cursor) +    } +} diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs new file mode 100644 index 00000000..981b30d7 --- /dev/null +++ b/renderer/src/widget/shader/event.rs @@ -0,0 +1,21 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +pub use crate::core::event::Status; + +/// A [`Shader`] event. +/// +/// [`Shader`]: crate::widget::shader::Shader; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { +    /// A mouse event. +    Mouse(mouse::Event), + +    /// A touch event. +    Touch(touch::Event), + +    /// A keyboard event. +    Keyboard(keyboard::Event), +} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs new file mode 100644 index 00000000..b8871688 --- /dev/null +++ b/renderer/src/widget/shader/program.rs @@ -0,0 +1,60 @@ +use crate::core::{event, mouse, Rectangle, Shell}; +use crate::widget; +use widget::shader; + +/// The state and logic of a [`Shader`] widget. +/// +/// A [`Program`] can mutate the internal state of a [`Shader`] widget +/// and produce messages for an application. +/// +/// [`Shader`]: crate::widget::shader::Shader +pub trait Program<Message> { +    /// The internal state of the [`Program`]. +    type State: Default + 'static; + +    /// The type of primitive this [`Program`] can draw. +    type Primitive: shader::Primitive + 'static; + +    /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes +    /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a +    /// redraw for the window, etc. +    /// +    /// By default, this method does and returns nothing. +    /// +    /// [`State`]: Self::State +    fn update( +        &self, +        _state: &mut Self::State, +        _event: shader::Event, +        _bounds: Rectangle, +        _cursor: mouse::Cursor, +        _shell: &mut Shell<'_, Message>, +    ) -> (event::Status, Option<Message>) { +        (event::Status::Ignored, None) +    } + +    /// Draws the [`Primitive`]. +    /// +    /// [`Primitive`]: Self::Primitive +    fn draw( +        &self, +        state: &Self::State, +        cursor: mouse::Cursor, +        bounds: Rectangle, +    ) -> Self::Primitive; + +    /// Returns the current mouse interaction of the [`Program`]. +    /// +    /// The interaction returned will be in effect even if the cursor position is out of +    /// bounds of the [`Shader`]'s program. +    /// +    /// [`Shader`]: crate::widget::shader::Shader +    fn mouse_interaction( +        &self, +        _state: &Self::State, +        _bounds: Rectangle, +        _cursor: mouse::Cursor, +    ) -> mouse::Interaction { +        mouse::Interaction::default() +    } +} diff --git a/style/src/theme.rs b/style/src/theme.rs index 47010728..cc31d72d 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,7 +1,7 @@  //! Use the built-in theme and styles.  pub mod palette; -pub use palette::Palette; +pub use self::palette::Palette;  use crate::application;  use crate::button; diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 2bd29f42..907611d9 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -3,9 +3,7 @@ use crate::graphics::backend;  use crate::graphics::color;  use crate::graphics::{Transformation, Viewport};  use crate::primitive::{self, Primitive}; -use crate::quad; -use crate::text; -use crate::triangle; +use crate::{custom, quad, text, triangle};  use crate::{Layer, Settings};  #[cfg(feature = "tracing")] @@ -25,6 +23,7 @@ pub struct Backend {      quad_pipeline: quad::Pipeline,      text_pipeline: text::Pipeline,      triangle_pipeline: triangle::Pipeline, +    pipeline_storage: custom::Storage,      #[cfg(any(feature = "image", feature = "svg"))]      image_pipeline: image::Pipeline, @@ -50,6 +49,7 @@ impl Backend {              quad_pipeline,              text_pipeline,              triangle_pipeline, +            pipeline_storage: custom::Storage::default(),              #[cfg(any(feature = "image", feature = "svg"))]              image_pipeline, @@ -66,6 +66,7 @@ impl Backend {          queue: &wgpu::Queue,          encoder: &mut wgpu::CommandEncoder,          clear_color: Option<Color>, +        format: wgpu::TextureFormat,          frame: &wgpu::TextureView,          primitives: &[Primitive],          viewport: &Viewport, @@ -88,6 +89,7 @@ impl Backend {          self.prepare(              device,              queue, +            format,              encoder,              scale_factor,              target_size, @@ -117,6 +119,7 @@ impl Backend {          &mut self,          device: &wgpu::Device,          queue: &wgpu::Queue, +        format: wgpu::TextureFormat,          _encoder: &mut wgpu::CommandEncoder,          scale_factor: f32,          target_size: Size<u32>, @@ -179,6 +182,20 @@ impl Backend {                      target_size,                  );              } + +            if !layer.shaders.is_empty() { +                for shader in &layer.shaders { +                    shader.primitive.prepare( +                        format, +                        device, +                        queue, +                        target_size, +                        scale_factor, +                        transformation, +                        &mut self.pipeline_storage, +                    ); +                } +            }          }      } @@ -302,6 +319,47 @@ impl Backend {                  text_layer += 1;              } + +            // kill render pass to let custom shaders get mut access to encoder +            let _ = ManuallyDrop::into_inner(render_pass); + +            if !layer.shaders.is_empty() { +                for shader in &layer.shaders { +                    //This extra check is needed since each custom pipeline must set it's own +                    //scissor rect, which will panic if bounds.w/h < 1 +                    let bounds = shader.bounds * scale_factor; + +                    if bounds.width < 1.0 || bounds.height < 1.0 { +                        continue; +                    } + +                    shader.primitive.render( +                        &self.pipeline_storage, +                        bounds.into(), +                        target, +                        target_size, +                        encoder, +                    ); +                } +            } + +            // recreate and continue processing layers +            render_pass = ManuallyDrop::new(encoder.begin_render_pass( +                &wgpu::RenderPassDescriptor { +                    label: Some("iced_wgpu::quad render pass"), +                    color_attachments: &[Some( +                        wgpu::RenderPassColorAttachment { +                            view: target, +                            resolve_target: None, +                            ops: wgpu::Operations { +                                load: wgpu::LoadOp::Load, +                                store: true, +                            }, +                        }, +                    )], +                    depth_stencil_attachment: None, +                }, +            ));          }          let _ = ManuallyDrop::into_inner(render_pass); diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs new file mode 100644 index 00000000..65dd0496 --- /dev/null +++ b/wgpu/src/custom.rs @@ -0,0 +1,66 @@ +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// Stores custom, user-provided pipelines. +#[derive(Default, Debug)] +pub struct Storage { +    pipelines: HashMap<TypeId, Box<dyn Any>>, +} + +impl Storage { +    /// Returns `true` if `Storage` contains a pipeline with type `T`. +    pub fn has<T: 'static>(&self) -> bool { +        self.pipelines.get(&TypeId::of::<T>()).is_some() +    } + +    /// Inserts the pipeline `T` in to [`Storage`]. +    pub fn store<T: 'static>(&mut self, pipeline: T) { +        let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline)); +    } + +    /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. +    pub fn get<T: 'static>(&self) -> Option<&T> { +        self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { +            pipeline +                .downcast_ref::<T>() +                .expect("Pipeline with this type does not exist in Storage.") +        }) +    } + +    /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. +    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { +        self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { +            pipeline +                .downcast_mut::<T>() +                .expect("Pipeline with this type does not exist in Storage.") +        }) +    } +} + +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { +    /// Processes the [`Primitive`], allowing for GPU buffer allocation. +    fn prepare( +        &self, +        format: wgpu::TextureFormat, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        target_size: Size<u32>, +        scale_factor: f32, +        transform: Transformation, +        storage: &mut Storage, +    ); + +    /// Renders the [`Primitive`]. +    fn render( +        &self, +        storage: &Storage, +        bounds: Rectangle<u32>, +        target: &wgpu::TextureView, +        target_size: Size<u32>, +        encoder: &mut wgpu::CommandEncoder, +    ); +} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 286801e6..d451cbfd 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -34,6 +34,9 @@ pub struct Layer<'a> {      /// The images of the [`Layer`].      pub images: Vec<Image>, + +    /// The custom shader primitives of this [`Layer`]. +    pub shaders: Vec<primitive::Shader>,  }  impl<'a> Layer<'a> { @@ -45,6 +48,7 @@ impl<'a> Layer<'a> {              meshes: Vec::new(),              text: Vec::new(),              images: Vec::new(), +            shaders: Vec::new(),          }      } @@ -308,6 +312,18 @@ impl<'a> Layer<'a> {                          }                      }                  }, +                primitive::Custom::Shader(shader) => { +                    let layer = &mut layers[current_layer]; + +                    let bounds = Rectangle::new( +                        Point::new(translation.x, translation.y), +                        shader.bounds.size(), +                    ); + +                    if layer.bounds.intersection(&bounds).is_some() { +                        layer.shaders.push(shader.clone()); +                    } +                }              },          }      } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 424dfeb3..13d8e886 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,6 +29,7 @@      rustdoc::broken_intra_doc_links  )]  #![cfg_attr(docsrs, feature(doc_auto_cfg))] +pub mod custom;  pub mod layer;  pub mod primitive;  pub mod settings; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 8dbf3008..4347dcda 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,6 +1,10 @@  //! Draw using different graphical primitives.  use crate::core::Rectangle; +use crate::custom;  use crate::graphics::{Damage, Mesh}; +use std::any::Any; +use std::fmt::Debug; +use std::sync::Arc;  /// The graphical primitives supported by `iced_wgpu`.  pub type Primitive = crate::graphics::Primitive<Custom>; @@ -10,12 +14,44 @@ pub type Primitive = crate::graphics::Primitive<Custom>;  pub enum Custom {      /// A mesh primitive.      Mesh(Mesh), +    /// A custom shader primitive +    Shader(Shader), +} + +impl Custom { +    /// Create a custom [`Shader`] primitive. +    pub fn shader<P: custom::Primitive>( +        bounds: Rectangle, +        primitive: P, +    ) -> Self { +        Self::Shader(Shader { +            bounds, +            primitive: Arc::new(primitive), +        }) +    }  }  impl Damage for Custom {      fn bounds(&self) -> Rectangle {          match self {              Self::Mesh(mesh) => mesh.bounds(), +            Self::Shader(shader) => shader.bounds,          }      }  } + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Shader { +    /// The bounds of the [`Shader`]. +    pub bounds: Rectangle, + +    /// The [`custom::Primitive`] to render. +    pub primitive: Arc<dyn custom::Primitive>, +} + +impl PartialEq for Shader { +    fn eq(&self, other: &Self) -> bool { +        self.primitive.type_id() == other.primitive.type_id() +    } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 1ddbe5fe..90d64e17 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -178,6 +178,7 @@ pub fn present<Theme, T: AsRef<str>>(                  &compositor.queue,                  &mut encoder,                  Some(background_color), +                frame.texture.format(),                  view,                  primitives,                  viewport, @@ -357,6 +358,7 @@ pub fn screenshot<Theme, T: AsRef<str>>(          &compositor.queue,          &mut encoder,          Some(background_color), +        texture.format(),          &view,          primitives,          viewport, diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 6d62c181..032f801c 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -20,6 +20,7 @@ image = ["iced_renderer/image"]  svg = ["iced_renderer/svg"]  canvas = ["iced_renderer/geometry"]  qr_code = ["canvas", "qrcode"] +wgpu = []  [dependencies]  iced_renderer.workspace = true diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 2f959370..e052f398 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -97,6 +97,9 @@ pub use tooltip::Tooltip;  #[doc(no_inline)]  pub use vertical_slider::VerticalSlider; +#[cfg(feature = "wgpu")] +pub use renderer::widget::shader::{self, Shader}; +  #[cfg(feature = "svg")]  pub mod svg;  | 
