From 781ef1f94c4859aeeb852f801b72be095b8ff82b Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 14 Sep 2023 13:58:36 -0700 Subject: Added support for custom shader widget for iced_wgpu backend. --- Cargo.toml | 2 +- core/src/rectangle.rs | 11 + examples/custom_shader/Cargo.toml | 13 + examples/custom_shader/src/camera.rs | 53 ++ examples/custom_shader/src/cubes.rs | 99 ++++ examples/custom_shader/src/main.rs | 174 ++++++ examples/custom_shader/src/pipeline.rs | 600 +++++++++++++++++++++ examples/custom_shader/src/primitive.rs | 95 ++++ examples/custom_shader/src/primitive/buffer.rs | 39 ++ examples/custom_shader/src/primitive/cube.rs | 324 +++++++++++ examples/custom_shader/src/primitive/uniforms.rs | 22 + examples/custom_shader/src/primitive/vertex.rs | 29 + examples/custom_shader/src/shaders/cubes.wgsl | 123 +++++ examples/custom_shader/src/shaders/depth.wgsl | 48 ++ .../src/textures/ice_cube_normal_map.png | Bin 0 -> 1773656 bytes .../custom_shader/src/textures/skybox/neg_x.jpg | Bin 0 -> 7549 bytes .../custom_shader/src/textures/skybox/neg_y.jpg | Bin 0 -> 2722 bytes .../custom_shader/src/textures/skybox/neg_z.jpg | Bin 0 -> 3986 bytes .../custom_shader/src/textures/skybox/pos_x.jpg | Bin 0 -> 5522 bytes .../custom_shader/src/textures/skybox/pos_y.jpg | Bin 0 -> 3382 bytes .../custom_shader/src/textures/skybox/pos_z.jpg | Bin 0 -> 5205 bytes examples/integration/src/main.rs | 1 + graphics/Cargo.toml | 1 - renderer/src/lib.rs | 21 + renderer/src/widget.rs | 3 + renderer/src/widget/shader.rs | 215 ++++++++ renderer/src/widget/shader/event.rs | 21 + renderer/src/widget/shader/program.rs | 60 +++ style/src/theme.rs | 2 +- wgpu/src/backend.rs | 64 ++- wgpu/src/custom.rs | 66 +++ wgpu/src/layer.rs | 16 + wgpu/src/lib.rs | 1 + wgpu/src/primitive.rs | 36 ++ wgpu/src/window/compositor.rs | 2 + widget/Cargo.toml | 1 + widget/src/lib.rs | 3 + 37 files changed, 2139 insertions(+), 6 deletions(-) create mode 100644 examples/custom_shader/Cargo.toml create mode 100644 examples/custom_shader/src/camera.rs create mode 100644 examples/custom_shader/src/cubes.rs create mode 100644 examples/custom_shader/src/main.rs create mode 100644 examples/custom_shader/src/pipeline.rs create mode 100644 examples/custom_shader/src/primitive.rs create mode 100644 examples/custom_shader/src/primitive/buffer.rs create mode 100644 examples/custom_shader/src/primitive/cube.rs create mode 100644 examples/custom_shader/src/primitive/uniforms.rs create mode 100644 examples/custom_shader/src/primitive/vertex.rs create mode 100644 examples/custom_shader/src/shaders/cubes.wgsl create mode 100644 examples/custom_shader/src/shaders/depth.wgsl create mode 100644 examples/custom_shader/src/textures/ice_cube_normal_map.png create mode 100644 examples/custom_shader/src/textures/skybox/neg_x.jpg create mode 100644 examples/custom_shader/src/textures/skybox/neg_y.jpg create mode 100644 examples/custom_shader/src/textures/skybox/neg_z.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_x.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_y.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_z.jpg create mode 100644 renderer/src/widget/shader.rs create mode 100644 renderer/src/widget/shader/event.rs create mode 100644 renderer/src/widget/shader/program.rs create mode 100644 wgpu/src/custom.rs diff --git a/Cargo.toml b/Cargo.toml index d69c95cf..ad4cd1bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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> for Rectangle { } } +impl From> for Rectangle { + fn from(rectangle: Rectangle) -> Self { + Rectangle { + x: rectangle.x as u32, + y: rectangle.y as u32, + width: rectangle.width as u32, + height: rectangle.height as u32, + } + } +} + impl std::ops::Add> for Rectangle where T: std::ops::Add, 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 "] +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, + 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 shader::Program 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) { + (IcedCubes::default(), Command::none()) + } + + fn title(&self) -> String { + "Iced Cubes".to_string() + } + + fn update(&mut self, message: Self::Message) -> Command { + 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> { + 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 { + window::frames().map(Message::Tick) + } +} + +fn control<'a>( + label: &'static str, + control: impl Into>, +) -> 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, + depth_view: wgpu::TextureView, + depth_pipeline: DepthPipeline, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + target_size: Size, + ) -> 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::() 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::() 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) { + 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, + 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::(); + 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, + 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, + ) { + 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 { + 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 { + 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, + 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::>(), + uniforms, + show_depth_buffer, + } + } +} + +impl shader::Primitive for Primitive { + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + _scale_factor: f32, + _transform: Transformation, + storage: &mut shader::Storage, + ) { + if !storage.has::() { + storage.store(Pipeline::new(device, queue, format, target_size)) + } + + let pipeline = storage.get_mut::().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, + target: &wgpu::TextureView, + _target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ) { + //at this point our pipeline should always be initialized + let pipeline = storage.get::().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::() 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::() 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, + camera_pos: vec4, + light_color: vec4, +} + +const LIGHT_POS: vec3 = vec3(0.0, 3.0, 3.0); + +@group(0) @binding(0) var uniforms: Uniforms; +@group(0) @binding(1) var sky_texture: texture_cube; +@group(0) @binding(2) var tex_sampler: sampler; +@group(0) @binding(3) var normal_texture: texture_2d; + +struct Vertex { + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) tangent: vec3, + @location(3) uv: vec2, +} + +struct Cube { + @location(4) matrix_0: vec4, + @location(5) matrix_1: vec4, + @location(6) matrix_2: vec4, + @location(7) matrix_3: vec4, + @location(8) normal_matrix_0: vec3, + @location(9) normal_matrix_1: vec3, + @location(10) normal_matrix_2: vec3, +} + +struct Output { + @builtin(position) clip_pos: vec4, + @location(0) uv: vec2, + @location(1) tangent_pos: vec3, + @location(2) tangent_camera_pos: vec3, + @location(3) tangent_light_pos: vec3, +} + +@vertex +fn vs_main(vertex: Vertex, cube: Cube) -> Output { + let cube_matrix = mat4x4( + cube.matrix_0, + cube.matrix_1, + cube.matrix_2, + cube.matrix_3, + ); + + let normal_matrix = mat3x3( + 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(tangent, bitangent, normal)); + + let world_pos = cube_matrix * vec4(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 = vec4(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 = vec4(1.0, 1.0, 1.0, 1.0); + +@fragment +fn fs_main(in: Output) -> @location(0) vec4 { + 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 = normalize(in.tangent_light_pos - in.tangent_pos); + let brightness = max(dot(normal, dir_to_light), 0.0); + let diffuse: vec3 = 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 = 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(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 positions: array, 6> = array, 6>( + vec2(-1.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0), + vec2(-1.0, 1.0), + vec2(1.0, 1.0), + vec2(1.0, -1.0) +); + +var uvs: array, 6> = array, 6>( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +@group(0) @binding(0) var depth_sampler: sampler; +@group(0) @binding(1) var depth_texture: texture_2d; + +struct Output { + @builtin(position) position: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(@builtin(vertex_index) v_index: u32) -> Output { + var out: Output; + + out.position = vec4(positions[v_index], 0.0, 1.0); + out.uv = uvs[v_index]; + + return out; +} + +@fragment +fn fs_main(input: Output) -> @location(0) vec4 { + let depth = textureSample(depth_texture, depth_sampler, input.uv).r; + + if (depth > .9999) { + discard; + } + + let c = 1.0 - depth; + + return vec4(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 new file mode 100644 index 00000000..7b4b7228 Binary files /dev/null and b/examples/custom_shader/src/textures/ice_cube_normal_map.png differ diff --git a/examples/custom_shader/src/textures/skybox/neg_x.jpg b/examples/custom_shader/src/textures/skybox/neg_x.jpg new file mode 100644 index 00000000..00cc783d Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_x.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/neg_y.jpg b/examples/custom_shader/src/textures/skybox/neg_y.jpg new file mode 100644 index 00000000..548f6445 Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_y.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/neg_z.jpg b/examples/custom_shader/src/textures/skybox/neg_z.jpg new file mode 100644 index 00000000..5698512e Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_z.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_x.jpg b/examples/custom_shader/src/textures/skybox/pos_x.jpg new file mode 100644 index 00000000..dddecba7 Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_x.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_y.jpg b/examples/custom_shader/src/textures/skybox/pos_y.jpg new file mode 100644 index 00000000..361427fd Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_y.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_z.jpg b/examples/custom_shader/src/textures/skybox/pos_z.jpg new file mode 100644 index 00000000..0085a49e Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_z.jpg differ 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> { &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 Renderer { } } } + + pub fn draw_custom( + &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 core::Renderer for Renderer { 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> { + width: Length, + height: Length, + program: P, + _message: PhantomData, +} + +impl> Shader { + /// 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) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } +} + +impl Widget> + for Shader +where + P: Program, +{ + fn tag(&self) -> Tag { + struct Tag(T); + tree::Tag::of::>() + } + + 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, + 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, + _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::(); + + 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, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut crate::Renderer, + _theme: &Theme, + _style: &Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + renderer.draw_custom( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, M, P, Theme> From> + for Element<'a, M, crate::Renderer> +where + M: 'a, + P: Program + 'a, +{ + fn from(custom: Shader) -> Element<'a, M, crate::Renderer> { + Element::new(custom) + } +} + +impl Program for &T +where + T: Program, +{ + 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) { + 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 { + /// 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) { + (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, + 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, @@ -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>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a pipeline with type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the pipeline `T` in to [`Storage`]. + pub fn store(&mut self, pipeline: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); + } + + /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .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(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .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, + scale_factor: f32, + transform: Transformation, + storage: &mut Storage, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + storage: &Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + target_size: Size, + 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, + + /// The custom shader primitives of this [`Layer`]. + pub shaders: Vec, } 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; @@ -10,12 +14,44 @@ pub type Primitive = crate::graphics::Primitive; pub enum Custom { /// A mesh primitive. Mesh(Mesh), + /// A custom shader primitive + Shader(Shader), +} + +impl Custom { + /// Create a custom [`Shader`] primitive. + pub fn shader( + 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, +} + +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>( &compositor.queue, &mut encoder, Some(background_color), + frame.texture.format(), view, primitives, viewport, @@ -357,6 +358,7 @@ pub fn screenshot>( &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; -- cgit From 36e85215932079fa324cfefb620602ad79f7df3d Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 18 Sep 2023 09:04:28 -0700 Subject: Removed `Into` for Rectangle from u32 --- core/src/rectangle.rs | 11 ----------- wgpu/src/backend.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index d5437d51..c1c2eeac 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -183,17 +183,6 @@ impl From> for Rectangle { } } -impl From> for Rectangle { - fn from(rectangle: Rectangle) -> Self { - Rectangle { - x: rectangle.x as u32, - y: rectangle.y as u32, - width: rectangle.width as u32, - height: rectangle.height as u32, - } - } -} - impl std::ops::Add> for Rectangle where T: std::ops::Add, diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 907611d9..ace2ef95 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -335,7 +335,7 @@ impl Backend { shader.primitive.render( &self.pipeline_storage, - bounds.into(), + bounds.snap(), target, target_size, encoder, -- cgit From 91fca024b629b7ddf4de533a75f01593954362f6 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 21 Sep 2023 13:24:54 -0700 Subject: Reexport Transformation from widget::shader --- renderer/src/widget/shader.rs | 1 + widget/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index da42a7dd..85fc13c8 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -18,6 +18,7 @@ pub use event::Event; pub use iced_wgpu::custom::Primitive; pub use iced_wgpu::custom::Storage; pub use program::Program; +pub use iced_graphics::Transformation; /// A widget which can render custom shaders with Iced's `wgpu` backend. /// diff --git a/widget/src/lib.rs b/widget/src/lib.rs index e052f398..5220e83a 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -98,7 +98,7 @@ pub use tooltip::Tooltip; pub use vertical_slider::VerticalSlider; #[cfg(feature = "wgpu")] -pub use renderer::widget::shader::{self, Shader}; +pub use renderer::widget::shader::{self, Shader, Transformation}; #[cfg(feature = "svg")] pub mod svg; -- cgit From 65f4ff060a36c6dc3afea20d75f541d72460d333 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 28 Sep 2023 09:48:38 -0700 Subject: Added redraw request handling to widget events. --- renderer/src/widget/shader.rs | 8 ++++---- renderer/src/widget/shader/event.rs | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index 85fc13c8..385fa981 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -5,10 +5,7 @@ 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 crate::core::{self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget, window}; use std::marker::PhantomData; mod event; @@ -109,6 +106,9 @@ where Some(Event::Keyboard(keyboard_event)) } core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Window(window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } _ => None, }; diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs index 981b30d7..c1696580 100644 --- a/renderer/src/widget/shader/event.rs +++ b/renderer/src/widget/shader/event.rs @@ -1,4 +1,5 @@ //! Handle events of a custom shader widget. +use std::time::Instant; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -18,4 +19,7 @@ pub enum Event { /// A keyboard event. Keyboard(keyboard::Event), + + /// A window requested a redraw. + RedrawRequested(Instant), } -- cgit From de9420e7df7d909bca611c360182dec54c5b1aae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:33:04 +0100 Subject: Fix latest `wgpu` changes --- wgpu/src/backend.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index ace2ef95..27ef0b3c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -353,11 +353,13 @@ impl Backend { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, }, )], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }, )); } -- cgit From 46a48af97fa472e1158e07d4deb988c5601197e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:34:15 +0100 Subject: Write missing documentation for `custom` module in `iced_wgpu` --- wgpu/src/custom.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs index 65dd0496..65a6f133 100644 --- a/wgpu/src/custom.rs +++ b/wgpu/src/custom.rs @@ -1,3 +1,4 @@ +//! Draw custom primitives. use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; use std::any::{Any, TypeId}; -- cgit From 3e8ed05356dde17a6e31a0dc927a3c19b593b09a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:38:16 +0100 Subject: Update `wgpu` in `custom_shader` example --- examples/custom_shader/Cargo.toml | 15 ++++++++++----- examples/custom_shader/src/pipeline.rs | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml index 7a927811..0b8466a9 100644 --- a/examples/custom_shader/Cargo.toml +++ b/examples/custom_shader/Cargo.toml @@ -5,9 +5,14 @@ authors = ["Bingus "] 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"] } +iced.workspace = true +iced.features = ["debug", "advanced"] + +image.workspace = true +wgpu.workspace = true +bytemuck.workspace = true + +glam.workspace = true +glam.features = ["bytemuck"] + rand = "0.8.5" diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index 9dd154e8..eef1081d 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -355,7 +355,7 @@ impl Pipeline { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, }, )], @@ -364,11 +364,13 @@ impl Pipeline { view: &self.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }, ), + timestamp_writes: None, + occlusion_query_set: None, }); pass.set_scissor_rect( @@ -547,7 +549,7 @@ impl DepthPipeline { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some( @@ -557,6 +559,8 @@ impl DepthPipeline { stencil_ops: None, }, ), + timestamp_writes: None, + occlusion_query_set: None, }); pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); -- cgit From 33f626294452aefd1cd04f455fa1d1dfcb7f549e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:43:38 +0100 Subject: Fix `clippy` lints :crab: --- examples/custom_shader/src/cubes.rs | 2 +- examples/custom_shader/src/pipeline.rs | 8 ++++---- examples/custom_shader/src/primitive.rs | 4 ++-- renderer/src/lib.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs index 8dbba4b1..00e608e3 100644 --- a/examples/custom_shader/src/cubes.rs +++ b/examples/custom_shader/src/cubes.rs @@ -65,7 +65,7 @@ impl Cubes { let new_len = self.cubes.len() - cubes_2_cut as usize; self.cubes.truncate(new_len); } - _ => {} + Ordering::Equal => {} } } } diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index eef1081d..44ad17a2 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -479,15 +479,15 @@ impl DepthPipeline { entry_point: "vs_main", buffers: &[], }, - primitive: Default::default(), + primitive: wgpu::PrimitiveState::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(), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), }), - multisample: Default::default(), + multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index 2201218f..f81ce95d 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -56,7 +56,7 @@ impl shader::Primitive for Primitive { storage: &mut shader::Storage, ) { if !storage.has::() { - storage.store(Pipeline::new(device, queue, format, target_size)) + storage.store(Pipeline::new(device, queue, format, target_size)); } let pipeline = storage.get_mut::().unwrap(); @@ -90,6 +90,6 @@ impl shader::Primitive for Primitive { bounds, self.cubes.len() as u32, self.show_depth_buffer, - ) + ); } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 8c5ee2f0..e4b1eda9 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -76,7 +76,7 @@ impl Renderer { Renderer::Wgpu(renderer) => { renderer.draw_primitive(iced_wgpu::Primitive::Custom( iced_wgpu::primitive::Custom::shader(bounds, primitive), - )) + )); } } } -- cgit From 226eac35c3fa35be328f6390fdf2a52a38ed2b0f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:51:04 +0100 Subject: Remove old `widget` modules in `iced_renderer` --- renderer/src/widget.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 0422c99c..4b7dad5d 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -1,14 +1,2 @@ -#[cfg(feature = "canvas")] -pub mod canvas; - -#[cfg(feature = "canvas")] -pub use canvas::Canvas; - -#[cfg(feature = "qr_code")] -pub mod qr_code; - -#[cfg(feature = "qr_code")] -pub use qr_code::QRCode; - #[cfg(feature = "wgpu")] pub mod shader; -- cgit From 2dda9132cda6d2a2279759f3447bae4e1c277555 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:52:34 +0100 Subject: Run `cargo fmt` --- renderer/src/widget/shader.rs | 7 +++++-- renderer/src/widget/shader/event.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index 385fa981..e218f35a 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -5,17 +5,20 @@ 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, window}; +use crate::core::{ + self, layout, mouse, widget, window, Clipboard, Element, Layout, Length, + Rectangle, Shell, Size, Widget, +}; use std::marker::PhantomData; mod event; mod program; pub use event::Event; +pub use iced_graphics::Transformation; pub use iced_wgpu::custom::Primitive; pub use iced_wgpu::custom::Storage; pub use program::Program; -pub use iced_graphics::Transformation; /// A widget which can render custom shaders with Iced's `wgpu` backend. /// diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs index c1696580..8901fb31 100644 --- a/renderer/src/widget/shader/event.rs +++ b/renderer/src/widget/shader/event.rs @@ -1,8 +1,8 @@ //! Handle events of a custom shader widget. -use std::time::Instant; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; +use std::time::Instant; pub use crate::core::event::Status; -- cgit From 9489e29e6619b14ed9f41a8887c4b34158266f71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:49:49 +0100 Subject: Re-organize `custom` module as `pipeline` module ... and move `Shader` widget to `iced_widget` crate --- examples/custom_shader/src/main.rs | 11 +- examples/custom_shader/src/primitive.rs | 15 ++- renderer/src/lib.rs | 44 ++++--- renderer/src/widget.rs | 2 - renderer/src/widget/shader.rs | 219 -------------------------------- renderer/src/widget/shader/event.rs | 25 ---- renderer/src/widget/shader/program.rs | 60 --------- wgpu/src/backend.rs | 29 +++-- wgpu/src/custom.rs | 63 +-------- wgpu/src/layer.rs | 19 ++- wgpu/src/lib.rs | 1 - wgpu/src/primitive.rs | 43 ++----- wgpu/src/primitive/pipeline.rs | 117 +++++++++++++++++ widget/src/lib.rs | 6 +- widget/src/shader.rs | 219 ++++++++++++++++++++++++++++++++ widget/src/shader/event.rs | 26 ++++ widget/src/shader/program.rs | 62 +++++++++ 17 files changed, 505 insertions(+), 456 deletions(-) delete mode 100644 renderer/src/widget.rs delete mode 100644 renderer/src/widget/shader.rs delete mode 100644 renderer/src/widget/shader/event.rs delete mode 100644 renderer/src/widget/shader/program.rs create mode 100644 wgpu/src/primitive/pipeline.rs create mode 100644 widget/src/shader.rs create mode 100644 widget/src/shader/event.rs create mode 100644 widget/src/shader/program.rs diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 76fa1625..f6370f46 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -3,15 +3,20 @@ mod cubes; mod pipeline; mod primitive; +use crate::camera::Camera; use crate::cubes::Cubes; +use crate::pipeline::Pipeline; + +use iced::executor; +use iced::time::Instant; use iced::widget::{ checkbox, column, container, row, slider, text, vertical_space, Shader, }; +use iced::window; use iced::{ - executor, window, Alignment, Application, Color, Command, Element, Length, - Renderer, Subscription, Theme, + Alignment, Application, Color, Command, Element, Length, Renderer, + Subscription, Theme, }; -use std::time::Instant; fn main() -> iced::Result { IcedCubes::run(iced::Settings::default()) diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index f81ce95d..520ceb8e 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -4,17 +4,18 @@ pub mod vertex; mod buffer; mod uniforms; -use crate::camera::Camera; -use crate::pipeline::Pipeline; -use crate::primitive::cube::Cube; +pub use buffer::Buffer; +pub use cube::Cube; +pub use uniforms::Uniforms; +pub use vertex::Vertex; + +use crate::Camera; +use crate::Pipeline; + 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 { diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index e4b1eda9..1fc4c86b 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,13 +1,15 @@ #![forbid(rust_2018_idioms)] #![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#[cfg(feature = "wgpu")] +pub use iced_wgpu as wgpu; + pub mod compositor; #[cfg(feature = "geometry")] pub mod geometry; mod settings; -pub mod widget; pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -60,26 +62,6 @@ impl Renderer { } } } - - pub fn draw_custom( - &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 core::Renderer for Renderer { @@ -292,3 +274,23 @@ impl crate::graphics::geometry::Renderer for Renderer { } } } + +#[cfg(feature = "wgpu")] +impl iced_wgpu::primitive::pipeline::Renderer for Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl wgpu::primitive::pipeline::Primitive, + ) { + match self { + Self::TinySkia(_renderer) => { + log::warn!( + "Custom shader primitive is unavailable with tiny-skia." + ); + } + Self::Wgpu(renderer) => { + renderer.draw_pipeline_primitive(bounds, primitive); + } + } + } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs deleted file mode 100644 index 4b7dad5d..00000000 --- a/renderer/src/widget.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "wgpu")] -pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs deleted file mode 100644 index e218f35a..00000000 --- a/renderer/src/widget/shader.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! 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, window, Clipboard, Element, Layout, Length, - Rectangle, Shell, Size, Widget, -}; -use std::marker::PhantomData; - -mod event; -mod program; - -pub use event::Event; -pub use iced_graphics::Transformation; -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> { - width: Length, - height: Length, - program: P, - _message: PhantomData, -} - -impl> Shader { - /// 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) -> Self { - self.width = width.into(); - self - } - - /// Set the `height` of the custom [`Shader`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } -} - -impl Widget> - for Shader -where - P: Program, -{ - fn tag(&self) -> Tag { - struct Tag(T); - tree::Tag::of::>() - } - - 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, - 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, - _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)), - core::Event::Window(window::Event::RedrawRequested(instant)) => { - Some(Event::RedrawRequested(instant)) - } - _ => None, - }; - - if let Some(custom_shader_event) = custom_shader_event { - let state = tree.state.downcast_mut::(); - - 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, - ) -> mouse::Interaction { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - self.program.mouse_interaction(state, bounds, cursor) - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut crate::Renderer, - _theme: &Theme, - _style: &Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - renderer.draw_custom( - bounds, - self.program.draw(state, cursor_position, bounds), - ); - } -} - -impl<'a, M, P, Theme> From> - for Element<'a, M, crate::Renderer> -where - M: 'a, - P: Program + 'a, -{ - fn from(custom: Shader) -> Element<'a, M, crate::Renderer> { - Element::new(custom) - } -} - -impl Program for &T -where - T: Program, -{ - 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) { - 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 deleted file mode 100644 index 8901fb31..00000000 --- a/renderer/src/widget/shader/event.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Handle events of a custom shader widget. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::touch; -use std::time::Instant; - -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), - - /// A window requested a redraw. - RedrawRequested(Instant), -} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs deleted file mode 100644 index b8871688..00000000 --- a/renderer/src/widget/shader/program.rs +++ /dev/null @@ -1,60 +0,0 @@ -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 { - /// 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) { - (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/wgpu/src/backend.rs b/wgpu/src/backend.rs index 27ef0b3c..f89bcee1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -2,8 +2,11 @@ use crate::core::{Color, Size}; use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; +use crate::primitive::pipeline; use crate::primitive::{self, Primitive}; -use crate::{custom, quad, text, triangle}; +use crate::quad; +use crate::text; +use crate::triangle; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -23,7 +26,7 @@ pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - pipeline_storage: custom::Storage, + pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, @@ -49,7 +52,7 @@ impl Backend { quad_pipeline, text_pipeline, triangle_pipeline, - pipeline_storage: custom::Storage::default(), + pipeline_storage: pipeline::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, @@ -183,9 +186,9 @@ impl Backend { ); } - if !layer.shaders.is_empty() { - for shader in &layer.shaders { - shader.primitive.prepare( + if !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + pipeline.primitive.prepare( format, device, queue, @@ -323,19 +326,17 @@ impl Backend { // 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 !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + let bounds = (pipeline.bounds * scale_factor).snap(); - if bounds.width < 1.0 || bounds.height < 1.0 { + if bounds.width < 1 || bounds.height < 1 { continue; } - shader.primitive.render( + pipeline.primitive.render( &self.pipeline_storage, - bounds.snap(), + bounds, target, target_size, encoder, diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs index 65a6f133..98e2b396 100644 --- a/wgpu/src/custom.rs +++ b/wgpu/src/custom.rs @@ -1,67 +1,8 @@ //! Draw custom primitives. use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; +use crate::primitive; + 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>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a pipeline with type `T`. - pub fn has(&self) -> bool { - self.pipelines.get(&TypeId::of::()).is_some() - } - - /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store(&mut self, pipeline: T) { - let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); - } - - /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. - pub fn get(&self) -> Option<&T> { - self.pipelines.get(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_ref::() - .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(&mut self) -> Option<&mut T> { - self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_mut::() - .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, - scale_factor: f32, - transform: Transformation, - storage: &mut Storage, - ); - - /// Renders the [`Primitive`]. - fn render( - &self, - storage: &Storage, - bounds: Rectangle, - target: &wgpu::TextureView, - target_size: Size, - encoder: &mut wgpu::CommandEncoder, - ); -} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index d451cbfd..33aaf670 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -35,8 +35,8 @@ pub struct Layer<'a> { /// The images of the [`Layer`]. pub images: Vec, - /// The custom shader primitives of this [`Layer`]. - pub shaders: Vec, + /// The custom pipelines of this [`Layer`]. + pub pipelines: Vec, } impl<'a> Layer<'a> { @@ -48,7 +48,7 @@ impl<'a> Layer<'a> { meshes: Vec::new(), text: Vec::new(), images: Vec::new(), - shaders: Vec::new(), + pipelines: Vec::new(), } } @@ -312,16 +312,21 @@ impl<'a> Layer<'a> { } } }, - primitive::Custom::Shader(shader) => { + primitive::Custom::Pipeline(pipeline) => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), - shader.bounds.size(), + pipeline.bounds.size(), ); - if layer.bounds.intersection(&bounds).is_some() { - layer.shaders.push(shader.clone()); + if let Some(clip_bounds) = + layer.bounds.intersection(&bounds) + { + layer.pipelines.push(primitive::Pipeline { + bounds: clip_bounds, + primitive: pipeline.primitive.clone(), + }); } } }, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 13d8e886..424dfeb3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,7 +29,6 @@ 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 4347dcda..fff927ea 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,10 +1,12 @@ //! Draw using different graphical primitives. +pub mod pipeline; + +pub use pipeline::Pipeline; + 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; @@ -14,44 +16,15 @@ pub type Primitive = crate::graphics::Primitive; pub enum Custom { /// A mesh primitive. Mesh(Mesh), - /// A custom shader primitive - Shader(Shader), -} - -impl Custom { - /// Create a custom [`Shader`] primitive. - pub fn shader( - bounds: Rectangle, - primitive: P, - ) -> Self { - Self::Shader(Shader { - bounds, - primitive: Arc::new(primitive), - }) - } + /// A custom pipeline primitive. + Pipeline(Pipeline), } impl Damage for Custom { fn bounds(&self) -> Rectangle { match self { Self::Mesh(mesh) => mesh.bounds(), - Self::Shader(shader) => shader.bounds, + Self::Pipeline(pipeline) => pipeline.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, -} - -impl PartialEq for Shader { - fn eq(&self, other: &Self) -> bool { - self.primitive.type_id() == other.primitive.type_id() - } -} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs new file mode 100644 index 00000000..b59aeff1 --- /dev/null +++ b/wgpu/src/primitive/pipeline.rs @@ -0,0 +1,117 @@ +//! Draw primitives using custom pipelines. +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; + +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Pipeline { + /// The bounds of the [`Shader`]. + pub bounds: Rectangle, + + /// The [`custom::Primitive`] to render. + pub primitive: Arc, +} + +impl Pipeline { + /// Creates a new [`Pipeline`] with the given [`Primitive`]. + pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { + Pipeline { + bounds, + primitive: Arc::new(primitive), + } + } +} + +impl PartialEq for Pipeline { + fn eq(&self, other: &Self) -> bool { + self.primitive.type_id() == other.primitive.type_id() + } +} + +/// 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, + scale_factor: f32, + transform: Transformation, + storage: &mut Storage, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + storage: &Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ); +} + +/// A renderer than can draw custom pipeline primitives. +pub trait Renderer: crate::core::Renderer { + /// Draws a custom pipeline primitive. + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ); +} + +impl Renderer for crate::Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ) { + self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( + Pipeline::new(bounds, primitive), + ))); + } +} + +/// Stores custom, user-provided pipelines. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: HashMap>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a pipeline with type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the pipeline `T` in to [`Storage`]. + pub fn store(&mut self, pipeline: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); + } + + /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .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(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } +} diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 5220e83a..07378d83 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -98,7 +98,11 @@ pub use tooltip::Tooltip; pub use vertical_slider::VerticalSlider; #[cfg(feature = "wgpu")] -pub use renderer::widget::shader::{self, Shader, Transformation}; +pub mod shader; + +#[cfg(feature = "wgpu")] +#[doc(no_inline)] +pub use shader::Shader; #[cfg(feature = "svg")] pub mod svg; diff --git a/widget/src/shader.rs b/widget/src/shader.rs new file mode 100644 index 00000000..9d482537 --- /dev/null +++ b/widget/src/shader.rs @@ -0,0 +1,219 @@ +//! A custom shader widget for wgpu applications. +mod event; +mod program; + +pub use event::Event; +pub use program::Program; + +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::window; +use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::renderer::wgpu::primitive::pipeline; + +use std::marker::PhantomData; + +pub use pipeline::{Primitive, Storage}; + +/// 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> { + width: Length, + height: Length, + program: P, + _message: PhantomData, +} + +impl> Shader { + /// 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) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } +} + +impl Widget for Shader +where + P: Program, + Renderer: pipeline::Renderer, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::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: &Renderer, + limits: &layout::Limits, + ) -> layout::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: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::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)), + core::Event::Window(window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::(); + + 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: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + _theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + renderer.draw_pipeline_primitive( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, Message, Renderer, P> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: pipeline::Renderer, + P: Program + 'a, +{ + fn from(custom: Shader) -> Element<'a, Message, Renderer> { + Element::new(custom) + } +} + +impl Program for &T +where + T: Program, +{ + type State = T::State; + type Primitive = T::Primitive; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + T::update(self, state, event, bounds, cursor, shell) + } + + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + T::draw(self, state, cursor, bounds) + } + + fn mouse_interaction( + &self, + state: &Self::State, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> mouse::Interaction { + T::mouse_interaction(self, state, bounds, cursor) + } +} diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs new file mode 100644 index 00000000..e4d2b03d --- /dev/null +++ b/widget/src/shader/event.rs @@ -0,0 +1,26 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +use std::time::Instant; + +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), + + /// A window requested a redraw. + RedrawRequested(Instant), +} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs new file mode 100644 index 00000000..0319844d --- /dev/null +++ b/widget/src/shader/program.rs @@ -0,0 +1,62 @@ +use crate::core::event; +use crate::core::mouse; +use crate::core::{Rectangle, Shell}; +use crate::renderer::wgpu::primitive::pipeline; +use crate::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 { + /// The internal state of the [`Program`]. + type State: Default + 'static; + + /// The type of primitive this [`Program`] can draw. + type Primitive: pipeline::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) { + (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() + } +} -- cgit From 882ae304ac8da899d7d83aed433b7dd3311a0496 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:51:08 +0100 Subject: Enable `iced_renderer/wgpu` feature in `iced_widget` --- widget/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 032f801c..e8e363c4 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -20,7 +20,7 @@ image = ["iced_renderer/image"] svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] -wgpu = [] +wgpu = ["iced_renderer/wgpu"] [dependencies] iced_renderer.workspace = true -- cgit From c2baf18cbff721e25c3103b6235292530b419c54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:52:03 +0100 Subject: Use `Instant` from `iced_core` instead of `std` This is needed for Wasm compatibility. --- widget/src/shader/event.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index e4d2b03d..a5a7acaa 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -1,10 +1,9 @@ //! Handle events of a custom shader widget. use crate::core::keyboard; use crate::core::mouse; +use crate::core::time::Instant; use crate::core::touch; -use std::time::Instant; - pub use crate::core::event::Status; /// A [`Shader`] event. -- cgit From 280d3736d59b62c4087fe980c187953cc2be83d2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 13:23:28 +0100 Subject: Fix broken intra-doc links --- wgpu/src/primitive/pipeline.rs | 4 ++-- widget/src/shader/event.rs | 2 +- widget/src/shader/program.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index b59aeff1..5dbd6697 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -10,10 +10,10 @@ use std::sync::Arc; #[derive(Clone, Debug)] /// A custom primitive which can be used to render primitives associated with a custom pipeline. pub struct Pipeline { - /// The bounds of the [`Shader`]. + /// The bounds of the [`Pipeline`]. pub bounds: Rectangle, - /// The [`custom::Primitive`] to render. + /// The [`Primitive`] to render. pub primitive: Arc, } diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index a5a7acaa..1cc484fb 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -8,7 +8,7 @@ pub use crate::core::event::Status; /// A [`Shader`] event. /// -/// [`Shader`]: crate::widget::shader::Shader; +/// [`Shader`]: crate::Shader #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { /// A mouse event. diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 0319844d..6dd50404 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -9,7 +9,7 @@ use crate::shader; /// A [`Program`] can mutate the internal state of a [`Shader`] widget /// and produce messages for an application. /// -/// [`Shader`]: crate::widget::shader::Shader +/// [`Shader`]: crate::Shader pub trait Program { /// The internal state of the [`Program`]. type State: Default + 'static; @@ -50,7 +50,7 @@ pub trait 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 + /// [`Shader`]: crate::Shader fn mouse_interaction( &self, _state: &Self::State, -- cgit From 91d7df52cdedd1b5c431fdb51a6356e827765b60 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 13:25:49 +0100 Subject: Create `shader` function helper in `iced_widget` --- examples/custom_shader/src/main.rs | 7 +++---- widget/src/helpers.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index f6370f46..e9b6776f 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -10,7 +10,7 @@ use crate::pipeline::Pipeline; use iced::executor; use iced::time::Instant; use iced::widget::{ - checkbox, column, container, row, slider, text, vertical_space, Shader, + checkbox, column, container, row, shader, slider, text, vertical_space, }; use iced::window; use iced::{ @@ -150,9 +150,8 @@ impl Application for IcedCubes { .spacing(10) .align_items(Alignment::Center); - let shader = Shader::new(&self.cubes) - .width(Length::Fill) - .height(Length::Fill); + let shader = + shader(&self.cubes).width(Length::Fill).height(Length::Fill); container( column![shader, controls, vertical_space(20),] diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e0b58722..115198fb 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -385,6 +385,17 @@ where crate::Canvas::new(program) } +/// Creates a new [`Shader`]. +/// +/// [`Shader`]: crate::Shader +#[cfg(feature = "wgpu")] +pub fn shader(program: P) -> crate::Shader +where + P: crate::shader::Program, +{ + crate::Shader::new(program) +} + /// Focuses the previous focusable widget. pub fn focus_previous() -> Command where -- cgit From 63f36b04638f14af3455ead8b82d581a438a28a3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:04:54 +0100 Subject: Export `wgpu` crate in `shader` module in `iced_widget` --- examples/custom_shader/Cargo.toml | 1 - examples/custom_shader/src/main.rs | 1 + examples/custom_shader/src/pipeline.rs | 4 +++- examples/custom_shader/src/primitive.rs | 1 + examples/custom_shader/src/primitive/buffer.rs | 2 ++ examples/custom_shader/src/primitive/cube.rs | 2 ++ examples/custom_shader/src/primitive/vertex.rs | 2 ++ widget/src/shader.rs | 1 + 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml index 0b8466a9..b602f98d 100644 --- a/examples/custom_shader/Cargo.toml +++ b/examples/custom_shader/Cargo.toml @@ -9,7 +9,6 @@ iced.workspace = true iced.features = ["debug", "advanced"] image.workspace = true -wgpu.workspace = true bytemuck.workspace = true glam.workspace = true diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index e9b6776f..e7b07d78 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -9,6 +9,7 @@ use crate::pipeline::Pipeline; use iced::executor; use iced::time::Instant; +use iced::widget::shader::wgpu; use iced::widget::{ checkbox, column, container, row, shader, slider, text, vertical_space, }; diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index 44ad17a2..9343e5e0 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -1,8 +1,10 @@ use crate::primitive; use crate::primitive::cube; use crate::primitive::{Buffer, Uniforms}; +use crate::wgpu; +use crate::wgpu::util::DeviceExt; + use iced::{Rectangle, Size}; -use wgpu::util::DeviceExt; const SKY_TEXTURE_SIZE: u32 = 128; diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index 520ceb8e..f5862ab3 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -9,6 +9,7 @@ pub use cube::Cube; pub use uniforms::Uniforms; pub use vertex::Vertex; +use crate::wgpu; use crate::Camera; use crate::Pipeline; diff --git a/examples/custom_shader/src/primitive/buffer.rs b/examples/custom_shader/src/primitive/buffer.rs index 377ce1bb..ef4c41c9 100644 --- a/examples/custom_shader/src/primitive/buffer.rs +++ b/examples/custom_shader/src/primitive/buffer.rs @@ -1,3 +1,5 @@ +use crate::wgpu; + // A custom buffer container for dynamic resizing. pub struct Buffer { pub raw: wgpu::Buffer, diff --git a/examples/custom_shader/src/primitive/cube.rs b/examples/custom_shader/src/primitive/cube.rs index c23f2132..7ece685d 100644 --- a/examples/custom_shader/src/primitive/cube.rs +++ b/examples/custom_shader/src/primitive/cube.rs @@ -1,4 +1,6 @@ use crate::primitive::Vertex; +use crate::wgpu; + use glam::{vec2, vec3, Vec3}; use rand::{thread_rng, Rng}; diff --git a/examples/custom_shader/src/primitive/vertex.rs b/examples/custom_shader/src/primitive/vertex.rs index 6d17aa0f..e64cd926 100644 --- a/examples/custom_shader/src/primitive/vertex.rs +++ b/examples/custom_shader/src/primitive/vertex.rs @@ -1,3 +1,5 @@ +use crate::wgpu; + #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] pub struct Vertex { diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 9d482537..fe6214db 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -17,6 +17,7 @@ use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; +pub use crate::renderer::wgpu::wgpu; pub use pipeline::{Primitive, Storage}; /// A widget which can render custom shaders with Iced's `wgpu` backend. -- cgit From 78a06384b1e6fc5e0c472dc19169fcaf11fe27b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:36:38 +0100 Subject: Use a single source for amount of cubes in `custom_shader` example --- examples/custom_shader/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index e7b07d78..3336e7f5 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -26,7 +26,6 @@ fn main() -> iced::Result { struct IcedCubes { start: Instant, cubes: Cubes, - num_cubes_slider: u32, } impl Default for IcedCubes { @@ -34,7 +33,6 @@ impl Default for IcedCubes { Self { start: Instant::now(), cubes: Cubes::new(), - num_cubes_slider: cubes::MAX, } } } @@ -65,7 +63,6 @@ impl Application for IcedCubes { fn update(&mut self, message: Self::Message) -> Command { match message { Message::CubeAmountChanged(num) => { - self.num_cubes_slider = num; self.cubes.adjust_num_cubes(num); } Message::CubeSizeChanged(size) => { @@ -91,7 +88,7 @@ impl Application for IcedCubes { "Amount", slider( 1..=cubes::MAX, - self.num_cubes_slider, + self.cubes.cubes.len() as u32, Message::CubeAmountChanged ) .width(100) -- cgit From 9ddfaf3ee73cef3e7bd122f4d11893f77647c2c3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:41:48 +0100 Subject: Rename `cubes` to `scene` in `custom_shader` example --- examples/custom_shader/src/cubes.rs | 99 ------------------------------------- examples/custom_shader/src/main.rs | 42 ++++++++-------- examples/custom_shader/src/scene.rs | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 120 deletions(-) delete mode 100644 examples/custom_shader/src/cubes.rs create mode 100644 examples/custom_shader/src/scene.rs diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs deleted file mode 100644 index 00e608e3..00000000 --- a/examples/custom_shader/src/cubes.rs +++ /dev/null @@ -1,99 +0,0 @@ -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, - 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); - } - Ordering::Equal => {} - } - } -} - -impl shader::Program 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 index 3336e7f5..f90061d7 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -1,11 +1,11 @@ mod camera; -mod cubes; mod pipeline; mod primitive; +mod scene; use crate::camera::Camera; -use crate::cubes::Cubes; use crate::pipeline::Pipeline; +use crate::scene::Scene; use iced::executor; use iced::time::Instant; @@ -25,14 +25,14 @@ fn main() -> iced::Result { struct IcedCubes { start: Instant, - cubes: Cubes, + scene: Scene, } impl Default for IcedCubes { fn default() -> Self { Self { start: Instant::now(), - cubes: Cubes::new(), + scene: Scene::new(), } } } @@ -62,20 +62,20 @@ impl Application for IcedCubes { fn update(&mut self, message: Self::Message) -> Command { match message { - Message::CubeAmountChanged(num) => { - self.cubes.adjust_num_cubes(num); + Message::CubeAmountChanged(amount) => { + self.scene.change_amount(amount); } Message::CubeSizeChanged(size) => { - self.cubes.size = size; + self.scene.size = size; } Message::Tick(time) => { - self.cubes.update(time - self.start); + self.scene.update(time - self.start); } Message::ShowDepthBuffer(show) => { - self.cubes.show_depth_buffer = show; + self.scene.show_depth_buffer = show; } Message::LightColorChanged(color) => { - self.cubes.light_color = color; + self.scene.light_color = color; } } @@ -87,21 +87,21 @@ impl Application for IcedCubes { control( "Amount", slider( - 1..=cubes::MAX, - self.cubes.cubes.len() as u32, + 1..=scene::MAX, + self.scene.cubes.len() as u32, Message::CubeAmountChanged ) .width(100) ), control( "Size", - slider(0.1..=0.25, self.cubes.size, Message::CubeSizeChanged) + slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged) .step(0.01) .width(100), ), checkbox( "Show Depth Buffer", - self.cubes.show_depth_buffer, + self.scene.show_depth_buffer, Message::ShowDepthBuffer ), ] @@ -110,10 +110,10 @@ impl Application for IcedCubes { let bottom_controls = row![ control( "R", - slider(0.0..=1.0, self.cubes.light_color.r, move |r| { + slider(0.0..=1.0, self.scene.light_color.r, move |r| { Message::LightColorChanged(Color { r, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -121,10 +121,10 @@ impl Application for IcedCubes { ), control( "G", - slider(0.0..=1.0, self.cubes.light_color.g, move |g| { + slider(0.0..=1.0, self.scene.light_color.g, move |g| { Message::LightColorChanged(Color { g, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -132,10 +132,10 @@ impl Application for IcedCubes { ), control( "B", - slider(0.0..=1.0, self.cubes.light_color.b, move |b| { + slider(0.0..=1.0, self.scene.light_color.b, move |b| { Message::LightColorChanged(Color { b, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -149,7 +149,7 @@ impl Application for IcedCubes { .align_items(Alignment::Center); let shader = - shader(&self.cubes).width(Length::Fill).height(Length::Fill); + shader(&self.scene).width(Length::Fill).height(Length::Fill); container( column![shader, controls, vertical_space(20),] diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs new file mode 100644 index 00000000..ab923093 --- /dev/null +++ b/examples/custom_shader/src/scene.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 Scene { + pub size: f32, + pub cubes: Vec, + pub camera: Camera, + pub show_depth_buffer: bool, + pub light_color: Color, +} + +impl Scene { + pub fn new() -> Self { + let mut scene = Self { + size: 0.2, + cubes: vec![], + camera: Camera::default(), + show_depth_buffer: false, + light_color: Color::WHITE, + }; + + scene.change_amount(MAX); + + scene + } + + pub fn update(&mut self, time: Duration) { + for cube in self.cubes.iter_mut() { + cube.update(self.size, time.as_secs_f32()); + } + } + + pub fn change_amount(&mut self, amount: u32) { + let curr_cubes = self.cubes.len() as u32; + + match amount.cmp(&curr_cubes) { + Ordering::Greater => { + // spawn + let cubes_2_spawn = (amount - 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 - amount; + let new_len = self.cubes.len() - cubes_2_cut as usize; + self.cubes.truncate(new_len); + } + Ordering::Equal => {} + } + } +} + +impl shader::Program for Scene { + 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), + ) +} -- cgit From 34b5cb75ef9f97076dd9e306d8afb68058176883 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:43:02 +0100 Subject: Remove `Default` implementation in `custom_shader` example --- examples/custom_shader/src/main.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index f90061d7..f4853507 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -28,15 +28,6 @@ struct IcedCubes { scene: Scene, } -impl Default for IcedCubes { - fn default() -> Self { - Self { - start: Instant::now(), - scene: Scene::new(), - } - } -} - #[derive(Debug, Clone)] enum Message { CubeAmountChanged(u32), @@ -53,7 +44,13 @@ impl Application for IcedCubes { type Flags = (); fn new(_flags: Self::Flags) -> (Self, Command) { - (IcedCubes::default(), Command::none()) + ( + Self { + start: Instant::now(), + scene: Scene::new(), + }, + Command::none(), + ) } fn title(&self) -> String { -- cgit From fee3bf0df4e3099ea74def738be8743b2b72d78a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:47:29 +0100 Subject: Kill current render pass only when custom pipelines are present in layer --- wgpu/src/backend.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index f89bcee1..91ae777b 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -323,10 +323,9 @@ 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.pipelines.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + for pipeline in &layer.pipelines { let bounds = (pipeline.bounds * scale_factor).snap(); @@ -342,27 +341,26 @@ impl Backend { 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: wgpu::StoreOp::Store, + 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: wgpu::StoreOp::Store, + }, }, - }, - )], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }, - )); + )], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }, + )); + } } let _ = ManuallyDrop::into_inner(render_pass); -- cgit From b1b2467b45e16185cc17df00b4c75700435cd46e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:50:57 +0100 Subject: Fix render pass label in `iced_wgpu` --- wgpu/src/backend.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 91ae777b..88caad06 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -222,7 +222,7 @@ impl Backend { let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: target, resolve_target: None, @@ -285,7 +285,7 @@ impl Backend { render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, @@ -344,7 +344,7 @@ impl Backend { render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, -- cgit From 811aa673e9e832ebe38bf56a087f32fdc7aba59c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:48:01 +0100 Subject: Improve module hierarchy of `custom_shader` example --- examples/custom_shader/src/camera.rs | 53 -- examples/custom_shader/src/main.rs | 7 +- examples/custom_shader/src/pipeline.rs | 606 -------------------- examples/custom_shader/src/primitive.rs | 97 ---- examples/custom_shader/src/primitive/buffer.rs | 41 -- examples/custom_shader/src/primitive/cube.rs | 326 ----------- examples/custom_shader/src/primitive/uniforms.rs | 22 - examples/custom_shader/src/primitive/vertex.rs | 31 -- examples/custom_shader/src/scene.rs | 103 +++- examples/custom_shader/src/scene/camera.rs | 53 ++ examples/custom_shader/src/scene/pipeline.rs | 615 +++++++++++++++++++++ .../custom_shader/src/scene/pipeline/buffer.rs | 41 ++ examples/custom_shader/src/scene/pipeline/cube.rs | 326 +++++++++++ .../custom_shader/src/scene/pipeline/uniforms.rs | 23 + .../custom_shader/src/scene/pipeline/vertex.rs | 31 ++ widget/src/shader.rs | 1 + 16 files changed, 1186 insertions(+), 1190 deletions(-) delete mode 100644 examples/custom_shader/src/camera.rs delete mode 100644 examples/custom_shader/src/pipeline.rs delete mode 100644 examples/custom_shader/src/primitive.rs delete mode 100644 examples/custom_shader/src/primitive/buffer.rs delete mode 100644 examples/custom_shader/src/primitive/cube.rs delete mode 100644 examples/custom_shader/src/primitive/uniforms.rs delete mode 100644 examples/custom_shader/src/primitive/vertex.rs create mode 100644 examples/custom_shader/src/scene/camera.rs create mode 100644 examples/custom_shader/src/scene/pipeline.rs create mode 100644 examples/custom_shader/src/scene/pipeline/buffer.rs create mode 100644 examples/custom_shader/src/scene/pipeline/cube.rs create mode 100644 examples/custom_shader/src/scene/pipeline/uniforms.rs create mode 100644 examples/custom_shader/src/scene/pipeline/vertex.rs diff --git a/examples/custom_shader/src/camera.rs b/examples/custom_shader/src/camera.rs deleted file mode 100644 index 2a49c102..00000000 --- a/examples/custom_shader/src/camera.rs +++ /dev/null @@ -1,53 +0,0 @@ -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/main.rs b/examples/custom_shader/src/main.rs index f4853507..2eb1ac4a 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -1,11 +1,6 @@ -mod camera; -mod pipeline; -mod primitive; mod scene; -use crate::camera::Camera; -use crate::pipeline::Pipeline; -use crate::scene::Scene; +use scene::Scene; use iced::executor; use iced::time::Instant; diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs deleted file mode 100644 index 9343e5e0..00000000 --- a/examples/custom_shader/src/pipeline.rs +++ /dev/null @@ -1,606 +0,0 @@ -use crate::primitive; -use crate::primitive::cube; -use crate::primitive::{Buffer, Uniforms}; -use crate::wgpu; -use crate::wgpu::util::DeviceExt; - -use iced::{Rectangle, Size}; - -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, - depth_view: wgpu::TextureView, - depth_pipeline: DepthPipeline, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - target_size: Size, - ) -> 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::() 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::() 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) { - 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, - 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::(); - 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, - 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: wgpu::StoreOp::Store, - }, - }, - )], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }, - ), - timestamp_writes: None, - occlusion_query_set: 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: wgpu::PrimitiveState::default(), - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState::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, - ) { - 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: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: None, - stencil_ops: None, - }, - ), - timestamp_writes: None, - occlusion_query_set: 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 { - 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 { - 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 deleted file mode 100644 index f5862ab3..00000000 --- a/examples/custom_shader/src/primitive.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod cube; -pub mod vertex; - -mod buffer; -mod uniforms; - -pub use buffer::Buffer; -pub use cube::Cube; -pub use uniforms::Uniforms; -pub use vertex::Vertex; - -use crate::wgpu; -use crate::Camera; -use crate::Pipeline; - -use iced::advanced::graphics::Transformation; -use iced::widget::shader; -use iced::{Color, Rectangle, Size}; - -/// A collection of `Cube`s that can be rendered. -#[derive(Debug)] -pub struct Primitive { - cubes: Vec, - 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::>(), - uniforms, - show_depth_buffer, - } - } -} - -impl shader::Primitive for Primitive { - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - target_size: Size, - _scale_factor: f32, - _transform: Transformation, - storage: &mut shader::Storage, - ) { - if !storage.has::() { - storage.store(Pipeline::new(device, queue, format, target_size)); - } - - let pipeline = storage.get_mut::().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, - target: &wgpu::TextureView, - _target_size: Size, - encoder: &mut wgpu::CommandEncoder, - ) { - //at this point our pipeline should always be initialized - let pipeline = storage.get::().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 deleted file mode 100644 index ef4c41c9..00000000 --- a/examples/custom_shader/src/primitive/buffer.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::wgpu; - -// 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 deleted file mode 100644 index 7ece685d..00000000 --- a/examples/custom_shader/src/primitive/cube.rs +++ /dev/null @@ -1,326 +0,0 @@ -use crate::primitive::Vertex; -use crate::wgpu; - -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::() 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 deleted file mode 100644 index 4fcb413b..00000000 --- a/examples/custom_shader/src/primitive/uniforms.rs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index e64cd926..00000000 --- a/examples/custom_shader/src/primitive/vertex.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::wgpu; - -#[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::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBS, - } - } -} diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs index ab923093..3b291ce2 100644 --- a/examples/custom_shader/src/scene.rs +++ b/examples/custom_shader/src/scene.rs @@ -1,13 +1,21 @@ -use crate::camera::Camera; -use crate::primitive; -use crate::primitive::cube::Cube; -use glam::Vec3; +mod camera; +mod pipeline; + +use camera::Camera; +use pipeline::Pipeline; + +use crate::wgpu; +use pipeline::cube::{self, Cube}; + +use iced::mouse; +use iced::time::Duration; use iced::widget::shader; -use iced::{mouse, Color, Rectangle}; +use iced::{Color, Rectangle, Size}; + +use glam::Vec3; use rand::Rng; use std::cmp::Ordering; use std::iter; -use std::time::Duration; pub const MAX: u32 = 500; @@ -72,7 +80,7 @@ impl Scene { impl shader::Program for Scene { type State = (); - type Primitive = primitive::Primitive; + type Primitive = Primitive; fn draw( &self, @@ -80,7 +88,7 @@ impl shader::Program for Scene { _cursor: mouse::Cursor, bounds: Rectangle, ) -> Self::Primitive { - primitive::Primitive::new( + Primitive::new( &self.cubes, &self.camera, bounds, @@ -90,6 +98,85 @@ impl shader::Program for Scene { } } +/// A collection of `Cube`s that can be rendered. +#[derive(Debug)] +pub struct Primitive { + cubes: Vec, + uniforms: pipeline::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 = pipeline::Uniforms::new(camera, bounds, light_color); + + Self { + cubes: cubes + .iter() + .map(cube::Raw::from_cube) + .collect::>(), + uniforms, + show_depth_buffer, + } + } +} + +impl shader::Primitive for Primitive { + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + _scale_factor: f32, + _transform: shader::Transformation, + storage: &mut shader::Storage, + ) { + if !storage.has::() { + storage.store(Pipeline::new(device, queue, format, target_size)); + } + + let pipeline = storage.get_mut::().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, + target: &wgpu::TextureView, + _target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ) { + //at this point our pipeline should always be initialized + let pipeline = storage.get::().unwrap(); + + //render primitive + pipeline.render( + target, + encoder, + bounds, + self.cubes.len() as u32, + self.show_depth_buffer, + ); + } +} + fn rnd_origin() -> Vec3 { Vec3::new( rand::thread_rng().gen_range(-4.0..4.0), diff --git a/examples/custom_shader/src/scene/camera.rs b/examples/custom_shader/src/scene/camera.rs new file mode 100644 index 00000000..2a49c102 --- /dev/null +++ b/examples/custom_shader/src/scene/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/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs new file mode 100644 index 00000000..0967e139 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -0,0 +1,615 @@ +pub mod cube; + +mod buffer; +mod uniforms; +mod vertex; + +pub use cube::Cube; +pub use uniforms::Uniforms; + +use buffer::Buffer; +use vertex::Vertex; + +use crate::wgpu; +use crate::wgpu::util::DeviceExt; + +use iced::{Rectangle, Size}; + +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, + depth_view: wgpu::TextureView, + depth_pipeline: DepthPipeline, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + target_size: Size, + ) -> 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::() 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::() 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: &[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) { + 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, + 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::(); + 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, + 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: wgpu::StoreOp::Store, + }, + }, + )], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }, + ), + timestamp_writes: None, + occlusion_query_set: 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: wgpu::PrimitiveState::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState::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, + ) { + 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: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: None, + stencil_ops: None, + }, + ), + timestamp_writes: None, + occlusion_query_set: 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 { + 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 { + 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/scene/pipeline/buffer.rs b/examples/custom_shader/src/scene/pipeline/buffer.rs new file mode 100644 index 00000000..ef4c41c9 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/buffer.rs @@ -0,0 +1,41 @@ +use crate::wgpu; + +// 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/scene/pipeline/cube.rs b/examples/custom_shader/src/scene/pipeline/cube.rs new file mode 100644 index 00000000..de8bad6c --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/cube.rs @@ -0,0 +1,326 @@ +use crate::scene::pipeline::Vertex; +use crate::wgpu; + +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::() 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/scene/pipeline/uniforms.rs b/examples/custom_shader/src/scene/pipeline/uniforms.rs new file mode 100644 index 00000000..1eac8292 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/uniforms.rs @@ -0,0 +1,23 @@ +use crate::scene::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/scene/pipeline/vertex.rs b/examples/custom_shader/src/scene/pipeline/vertex.rs new file mode 100644 index 00000000..e64cd926 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/vertex.rs @@ -0,0 +1,31 @@ +use crate::wgpu; + +#[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::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +} diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fe6214db..ca140627 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -17,6 +17,7 @@ use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; +pub use crate::graphics::Transformation; pub use crate::renderer::wgpu::wgpu; pub use pipeline::{Primitive, Storage}; -- cgit From 77dfa60c9640236df8b56084a6b1c58826b51e7e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:49:09 +0100 Subject: Move `textures` directory outside of `src` in `custom_shader` example --- examples/custom_shader/src/scene/pipeline.rs | 14 +++++++------- .../src/textures/ice_cube_normal_map.png | Bin 1773656 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_x.jpg | Bin 7549 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_y.jpg | Bin 2722 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_z.jpg | Bin 3986 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_x.jpg | Bin 5522 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_y.jpg | Bin 3382 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_z.jpg | Bin 5205 -> 0 bytes .../custom_shader/textures/ice_cube_normal_map.png | Bin 0 -> 1773656 bytes examples/custom_shader/textures/skybox/neg_x.jpg | Bin 0 -> 7549 bytes examples/custom_shader/textures/skybox/neg_y.jpg | Bin 0 -> 2722 bytes examples/custom_shader/textures/skybox/neg_z.jpg | Bin 0 -> 3986 bytes examples/custom_shader/textures/skybox/pos_x.jpg | Bin 0 -> 5522 bytes examples/custom_shader/textures/skybox/pos_y.jpg | Bin 0 -> 3382 bytes examples/custom_shader/textures/skybox/pos_z.jpg | Bin 0 -> 5205 bytes 15 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 examples/custom_shader/src/textures/ice_cube_normal_map.png delete mode 100644 examples/custom_shader/src/textures/skybox/neg_x.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/neg_y.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/neg_z.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_x.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_y.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_z.jpg create mode 100644 examples/custom_shader/textures/ice_cube_normal_map.png create mode 100644 examples/custom_shader/textures/skybox/neg_x.jpg create mode 100644 examples/custom_shader/textures/skybox/neg_y.jpg create mode 100644 examples/custom_shader/textures/skybox/neg_z.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_x.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_y.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_z.jpg diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 0967e139..3956c12e 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -582,12 +582,12 @@ impl DepthPipeline { } fn load_skybox_data() -> Vec { - 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 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]; @@ -606,7 +606,7 @@ fn load_skybox_data() -> Vec { } fn load_normal_map_data() -> Vec { - let bytes: &[u8] = include_bytes!("../textures/ice_cube_normal_map.png"); + let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png"); image::load_from_memory_with_format(bytes, image::ImageFormat::Png) .unwrap() diff --git a/examples/custom_shader/src/textures/ice_cube_normal_map.png b/examples/custom_shader/src/textures/ice_cube_normal_map.png deleted file mode 100644 index 7b4b7228..00000000 Binary files a/examples/custom_shader/src/textures/ice_cube_normal_map.png and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_x.jpg b/examples/custom_shader/src/textures/skybox/neg_x.jpg deleted file mode 100644 index 00cc783d..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_x.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_y.jpg b/examples/custom_shader/src/textures/skybox/neg_y.jpg deleted file mode 100644 index 548f6445..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_y.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_z.jpg b/examples/custom_shader/src/textures/skybox/neg_z.jpg deleted file mode 100644 index 5698512e..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_z.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_x.jpg b/examples/custom_shader/src/textures/skybox/pos_x.jpg deleted file mode 100644 index dddecba7..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_x.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_y.jpg b/examples/custom_shader/src/textures/skybox/pos_y.jpg deleted file mode 100644 index 361427fd..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_y.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_z.jpg b/examples/custom_shader/src/textures/skybox/pos_z.jpg deleted file mode 100644 index 0085a49e..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_z.jpg and /dev/null differ diff --git a/examples/custom_shader/textures/ice_cube_normal_map.png b/examples/custom_shader/textures/ice_cube_normal_map.png new file mode 100644 index 00000000..7b4b7228 Binary files /dev/null and b/examples/custom_shader/textures/ice_cube_normal_map.png differ diff --git a/examples/custom_shader/textures/skybox/neg_x.jpg b/examples/custom_shader/textures/skybox/neg_x.jpg new file mode 100644 index 00000000..00cc783d Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_x.jpg differ diff --git a/examples/custom_shader/textures/skybox/neg_y.jpg b/examples/custom_shader/textures/skybox/neg_y.jpg new file mode 100644 index 00000000..548f6445 Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_y.jpg differ diff --git a/examples/custom_shader/textures/skybox/neg_z.jpg b/examples/custom_shader/textures/skybox/neg_z.jpg new file mode 100644 index 00000000..5698512e Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_z.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_x.jpg b/examples/custom_shader/textures/skybox/pos_x.jpg new file mode 100644 index 00000000..dddecba7 Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_x.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_y.jpg b/examples/custom_shader/textures/skybox/pos_y.jpg new file mode 100644 index 00000000..361427fd Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_y.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_z.jpg b/examples/custom_shader/textures/skybox/pos_z.jpg new file mode 100644 index 00000000..0085a49e Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_z.jpg differ -- cgit From 74b920a708afc2407cb495a2953e97114f7773b1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:52:55 +0100 Subject: Remove unnecessary `self` in `iced_style::theme` --- style/src/theme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style/src/theme.rs b/style/src/theme.rs index cc31d72d..47010728 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 self::palette::Palette; +pub use palette::Palette; use crate::application; use crate::button; -- cgit From 8f384c83be242f9318685530ee52dd6c27b2bb62 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:54:10 +0100 Subject: Remove unsused `custom.rs` file in `iced_wgpu` --- wgpu/src/custom.rs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 wgpu/src/custom.rs diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs deleted file mode 100644 index 98e2b396..00000000 --- a/wgpu/src/custom.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Draw custom primitives. -use crate::core::{Rectangle, Size}; -use crate::graphics::Transformation; -use crate::primitive; - -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt::Debug; -- cgit From 0968c5b64a528ff92a5a93f6586eef557546da25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:58:32 +0100 Subject: Remove unused import in `custom_shader` example --- examples/custom_shader/src/scene/pipeline.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 3956c12e..94c6c562 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -4,7 +4,6 @@ mod buffer; mod uniforms; mod vertex; -pub use cube::Cube; pub use uniforms::Uniforms; use buffer::Buffer; -- cgit