diff --git a/gpu/src/gpu_backend.rs b/gpu/src/gpu_backend.rs index 7549750aa..20e30fdc7 100644 --- a/gpu/src/gpu_backend.rs +++ b/gpu/src/gpu_backend.rs @@ -1,7 +1,7 @@ use self::textures_mgr::{TextureID, TexturesMgr}; use crate::{ - ColorAttr, GPUBackendImpl, GradientStopPrimitive, ImgPrimitive, MaskLayer, RadialGradientAttr, - RadialGradientPrimitive, + ColorAttr, GPUBackendImpl, GradientStopPrimitive, ImgPrimitive, LinearGradientAttr, + LinearGradientPrimitive, MaskLayer, RadialGradientAttr, RadialGradientPrimitive, }; use ribir_geom::{rect_corners, DeviceRect, DeviceSize, Point}; use ribir_painter::{ @@ -21,8 +21,11 @@ pub struct GPUBackend { color_vertices_buffer: VertexBuffers, img_vertices_buffer: VertexBuffers, radial_gradient_vertices_buffer: VertexBuffers, - gradient_stops: Vec, + radial_gradient_stops: Vec, radial_gradient_prims: Vec, + linear_gradient_prims: Vec, + linear_gradient_stops: Vec, + linear_gradient_vertices_buffer: VertexBuffers, img_prims: Vec, draw_indices: Vec, tex_ids_map: TextureIdxMap, @@ -37,6 +40,7 @@ enum DrawIndices { Color(Range), Img(Range), RadialGradient(Range), + LinearGradient(Range), } struct ClipLayer { @@ -137,11 +141,22 @@ where .load_radial_gradient_primitives(&self.radial_gradient_prims); self .gpu_impl - .load_radial_gradient_stops(&self.gradient_stops); + .load_radial_gradient_stops(&self.radial_gradient_stops); self .gpu_impl .load_radial_gradient_vertices(&self.radial_gradient_vertices_buffer); } + if !self.linear_gradient_vertices_buffer.indices.is_empty() { + self + .gpu_impl + .load_linear_gradient_primitives(&self.linear_gradient_prims); + self + .gpu_impl + .load_linear_gradient_stops(&self.linear_gradient_stops); + self + .gpu_impl + .load_linear_gradient_vertices(&self.linear_gradient_vertices_buffer); + } self.tex_mgr.submit(&mut self.gpu_impl); self.layers_submit(output, surface); @@ -164,12 +179,15 @@ where tex_ids_map: <_>::default(), mask_layers: vec![], clip_layer_stack: vec![], - radial_gradient_prims: vec![], skip_clip_cnt: 0, color_vertices_buffer: VertexBuffers::with_capacity(256, 512), img_vertices_buffer: VertexBuffers::with_capacity(256, 512), radial_gradient_vertices_buffer: VertexBuffers::with_capacity(256, 512), - gradient_stops: vec![], + radial_gradient_prims: vec![], + radial_gradient_stops: vec![], + linear_gradient_vertices_buffer: VertexBuffers::with_capacity(256, 512), + linear_gradient_stops: vec![], + linear_gradient_prims: vec![], img_prims: vec![], draw_indices: vec![], viewport: DeviceRect::zero(), @@ -224,7 +242,7 @@ where start_radius, end, end_radius, - spread, + spread_method: spread, transform, } => { let ts = transform.then(&path.transform); @@ -232,7 +250,7 @@ where self.update_to_radial_gradient_indices(); let prim: RadialGradientPrimitive = RadialGradientPrimitive { transform: ts.inverse().unwrap().to_array(), - stop_start: self.gradient_stops.len() as u32, + stop_start: self.radial_gradient_stops.len() as u32, stop_cnt: stops.len() as u32, start_center: start.to_array(), start_radius, @@ -241,16 +259,18 @@ where mask_head, spread: spread as u32, }; - self.gradient_stops.extend(stops.into_iter().map(|stop| { - let color = stop.color.into_f32_components(); - GradientStopPrimitive { - red: color[0], - green: color[1], - blue: color[2], - alpha: color[3], - offset: stop.offset, - } - })); + self + .radial_gradient_stops + .extend(stops.into_iter().map(|stop| { + let color = stop.color.into_f32_components(); + GradientStopPrimitive { + red: color[0], + green: color[1], + blue: color[2], + alpha: color[3], + offset: stop.offset, + } + })); let prim_idx = self.radial_gradient_prims.len() as u32; self.radial_gradient_prims.push(prim); let buffer = &mut self.radial_gradient_vertices_buffer; @@ -258,6 +278,45 @@ where add_draw_rect_vertices(rect, output_tex_size, attr, buffer); } } + PaintCommand::LinearGradient { + path, + stops, + start, + end, + spread_method: spread, + transform, + } => { + let ts = transform.then(&path.transform); + if let Some((rect, mask_head)) = self.new_mask_layer(path) { + self.update_to_linear_gradient_indices(); + let prim: LinearGradientPrimitive = LinearGradientPrimitive { + transform: ts.inverse().unwrap().to_array(), + stop_start: self.radial_gradient_stops.len() as u32, + stop_cnt: stops.len() as u32, + start_position: start.to_array(), + end_position: end.to_array(), + mask_head, + spread: spread as u32, + }; + self + .linear_gradient_stops + .extend(stops.into_iter().map(|stop| { + let color = stop.color.into_f32_components(); + GradientStopPrimitive { + red: color[0], + green: color[1], + blue: color[2], + alpha: color[3], + offset: stop.offset, + } + })); + let prim_idx = self.linear_gradient_prims.len() as u32; + self.linear_gradient_prims.push(prim); + let buffer = &mut self.linear_gradient_vertices_buffer; + let attr = LinearGradientAttr { prim_idx }; + add_draw_rect_vertices(rect, output_tex_size, attr, buffer); + } + } PaintCommand::Clip(path) => { if self.skip_clip_cnt == 0 { if let Some(viewport) = path @@ -300,7 +359,10 @@ where self.radial_gradient_vertices_buffer.indices.clear(); self.radial_gradient_vertices_buffer.vertices.clear(); self.radial_gradient_prims.clear(); - self.gradient_stops.clear(); + self.radial_gradient_stops.clear(); + self.linear_gradient_prims.clear(); + self.linear_gradient_vertices_buffer.indices.clear(); + self.linear_gradient_stops.clear(); } fn update_to_color_indices(&mut self) { @@ -332,6 +394,19 @@ where } } + fn update_to_linear_gradient_indices(&mut self) { + if !matches!( + self.draw_indices.last(), + Some(DrawIndices::LinearGradient(_)) + ) { + self.expand_indices_range(); + let start = self.linear_gradient_vertices_buffer.indices.len() as u32; + self + .draw_indices + .push(DrawIndices::LinearGradient(start..start)); + } + } + fn expand_indices_range(&mut self) -> Option<&DrawIndices> { let cmd = self.draw_indices.last_mut()?; match cmd { @@ -340,6 +415,9 @@ where DrawIndices::RadialGradient(rg) => { rg.end = self.radial_gradient_vertices_buffer.indices.len() as u32 } + DrawIndices::LinearGradient(rg) => { + rg.end = self.linear_gradient_vertices_buffer.indices.len() as u32 + } }; Some(&*cmd) @@ -408,6 +486,11 @@ where .gpu_impl .draw_radial_gradient_triangles(output, rg, color.take()) } + DrawIndices::LinearGradient(rg) => { + self + .gpu_impl + .draw_linear_gradient_triangles(output, rg, color.take()) + } }); } } @@ -466,7 +549,10 @@ mod tests { use ribir_algo::ShareResource; use ribir_dev_helper::*; use ribir_geom::*; - use ribir_painter::{Brush, Color, Painter, Path, PixelImage}; + use ribir_painter::{ + color::{LinearGradient, RadialGradient}, + Brush, Color, GradientStop, Painter, Path, PixelImage, + }; fn painter(bounds: Size) -> Painter { Painter::new(Rect::from_size(bounds)) } @@ -603,4 +689,49 @@ mod tests { painter } + + painter_backend_eq_image_test!(draw_radial_gradient); + fn draw_radial_gradient() -> Painter { + let mut painter = painter(Size::new(64., 64.)); + let brush = Brush::RadialGradient(RadialGradient { + start_center: Point::new(16., 32.), + start_radius: 0., + end_center: Point::new(32., 32.), + end_radius: 32., + stops: vec![ + GradientStop::new(Color::RED, 0.), + GradientStop::new(Color::BLUE, 1.), + ], + transform: Transform::translation(16., 0.), + ..Default::default() + }); + + painter + .set_brush(brush) + .rect(&Rect::from_size(Size::new(64., 64.))) + .fill(); + + painter + } + + painter_backend_eq_image_test!(draw_linear_gradient); + fn draw_linear_gradient() -> Painter { + let mut painter = painter(Size::new(32., 32.)); + let brush = Brush::LinearGradient(LinearGradient { + start: Point::new(0., 0.), + end: Point::new(16., 16.), + stops: vec![ + GradientStop::new(Color::RED, 0.), + GradientStop::new(Color::BLUE, 1.), + ], + ..Default::default() + }); + + painter + .set_brush(brush) + .rect(&Rect::from_size(Size::new(64., 64.))) + .fill(); + + painter + } } diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index e8035ca6a..807a62a68 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -109,6 +109,15 @@ pub trait GPUBackendImpl { /// Load the vertices and indices buffer that `draw_radial_gradient_triangles` /// will use. fn load_radial_gradient_vertices(&mut self, buffers: &VertexBuffers); + + /// Load the primitives that `draw_linear_gradient_triangles` will use. + fn load_linear_gradient_primitives(&mut self, primitives: &[LinearGradientPrimitive]); + /// Load the gradient color stops that `draw_linear_gradient_triangles` will + /// use. + fn load_linear_gradient_stops(&mut self, stops: &[GradientStopPrimitive]); + /// Load the vertices and indices buffer that `draw_linear_gradient_triangles` + /// will use. + fn load_linear_gradient_vertices(&mut self, buffers: &VertexBuffers); /// Draw pure color triangles in the texture. And use the clear color clear /// the texture first if it's a Some-Value fn draw_color_triangles( @@ -134,6 +143,15 @@ pub trait GPUBackendImpl { clear: Option, ); + /// Draw triangles fill with color linear gradient. And use the clear color + /// clear the texture first if it's a Some-Value + fn draw_linear_gradient_triangles( + &mut self, + texture: &mut Self::Texture, + indices: Range, + clear: Option, + ); + fn copy_texture_from_texture( &mut self, dist_tex: &mut Self::Texture, @@ -160,6 +178,12 @@ pub struct RadialGradientAttr { pub prim_idx: u32, } +#[repr(packed)] +#[derive(AsBytes, PartialEq, Clone, Copy, Debug)] +pub struct LinearGradientAttr { + pub prim_idx: u32, +} + #[repr(packed)] #[derive(AsBytes, PartialEq, Clone, Copy, Debug)] pub struct GradientStopPrimitive { @@ -170,35 +194,49 @@ pub struct GradientStopPrimitive { pub offset: f32, } -#[repr(u32)] -enum SpreadMethod { - Pad, - Reflect, - Repeat, -} - #[repr(packed)] -#[derive(AsBytes, PartialEq, Clone, Copy)] +#[derive(AsBytes, PartialEq, Clone, Copy, Debug)] pub struct RadialGradientPrimitive { - /// A 2x3 column-major matrix, transform a vertex position to the radial path + /// A 2x3 column-major matrix, transform a vertex position to the texture /// position pub transform: [f32; 6], - /// The origin of the image placed in texture. + /// The color stop's start index pub stop_start: u32, - /// The size of the image image. + /// The size of the color stop pub stop_cnt: u32, - /// The index of texture, `load_color_primitives` method provide all textures - /// a draw phase need. + /// position of the start center pub start_center: [f32; 2], - /// The index of the head mask layer. + /// position of the end center pub end_center: [f32; 2], - + /// the radius of the start circle. pub start_radius: f32, - + /// the radius of the end circle. pub end_radius: f32, - + /// The index of the head mask layer. pub mask_head: i32, + /// the spread method of the gradient. 0 for pad, 1 for reflect and 2 + /// for repeat + pub spread: u32, +} +#[repr(packed)] +#[derive(AsBytes, PartialEq, Clone, Copy, Debug)] +pub struct LinearGradientPrimitive { + /// A 2x3 column-major matrix, transform a vertex position to the texture + /// position + pub transform: [f32; 6], + /// The color stop's start index + pub stop_start: u32, + /// The size of the color stop + pub stop_cnt: u32, + /// position of the start center + pub start_position: [f32; 2], + /// position of the end center + pub end_position: [f32; 2], + /// The index of the head mask layer. + pub mask_head: i32, + /// the spread method of the gradient. 0 for pad, 1 for reflect and 2 + /// for repeat pub spread: u32, } diff --git a/gpu/src/wgpu_impl.rs b/gpu/src/wgpu_impl.rs index 4bafa333e..0e06a8997 100644 --- a/gpu/src/wgpu_impl.rs +++ b/gpu/src/wgpu_impl.rs @@ -1,12 +1,14 @@ use self::{ draw_alpha_triangles_pass::DrawAlphaTrianglesPass, draw_color_triangles_pass::DrawColorTrianglesPass, draw_img_triangles_pass::DrawImgTrianglesPass, + draw_linear_gradient_pass::DrawLinearGradientTrianglesPass, draw_radial_gradient_pass::DrawRadialGradientTrianglesPass, draw_texture_pass::DrawTexturePass, storage::Storage, }; use crate::{ - gpu_backend::Texture, ColorAttr, GPUBackendImpl, GradientStopPrimitive, ImgPrimitive, MaskLayer, - RadialGradientAttr, RadialGradientPrimitive, + gpu_backend::Texture, ColorAttr, GPUBackendImpl, GradientStopPrimitive, ImgPrimitive, + LinearGradientAttr, LinearGradientPrimitive, MaskLayer, RadialGradientAttr, + RadialGradientPrimitive, }; use futures::{channel::oneshot, Future}; use ribir_geom::{DevicePoint, DeviceRect, DeviceSize}; @@ -19,6 +21,7 @@ mod vertex_buffer; mod draw_alpha_triangles_pass; mod draw_color_triangles_pass; mod draw_img_triangles_pass; +mod draw_linear_gradient_pass; mod draw_radial_gradient_pass; mod draw_texture_pass; @@ -34,7 +37,8 @@ pub struct WgpuImpl { draw_alpha_triangles_pass: DrawAlphaTrianglesPass, draw_color_triangles_pass: DrawColorTrianglesPass, draw_img_triangles_pass: DrawImgTrianglesPass, - draw_radial_gradient_pass: draw_radial_gradient_pass::DrawRadialGradientTrianglesPass, + draw_radial_gradient_pass: DrawRadialGradientTrianglesPass, + draw_linear_gradient_pass: DrawLinearGradientTrianglesPass, textures_bind: TexturesBind, mask_layers_storage: Storage, @@ -136,19 +140,37 @@ impl GPUBackendImpl for WgpuImpl { fn load_radial_gradient_primitives(&mut self, primitives: &[RadialGradientPrimitive]) { self .draw_radial_gradient_pass - .load_radial_gradient_primitives(&self.device, &mut self.queue, primitives); + .load_radial_gradient_primitives(&self.device, &self.queue, primitives); } fn load_radial_gradient_stops(&mut self, stops: &[GradientStopPrimitive]) { self .draw_radial_gradient_pass - .load_gradient_stops(&self.device, &mut self.queue, stops); + .load_gradient_stops(&self.device, &self.queue, stops); } fn load_radial_gradient_vertices(&mut self, buffers: &VertexBuffers) { self .draw_radial_gradient_pass - .load_triangles_vertices(buffers, &self.device, &mut self.queue); + .load_triangles_vertices(buffers, &self.device, &self.queue); + } + + fn load_linear_gradient_primitives(&mut self, primitives: &[LinearGradientPrimitive]) { + self + .draw_linear_gradient_pass + .load_linear_gradient_primitives(&self.device, &self.queue, primitives); + } + + fn load_linear_gradient_stops(&mut self, stops: &[GradientStopPrimitive]) { + self + .draw_linear_gradient_pass + .load_gradient_stops(&self.device, &self.queue, stops); + } + + fn load_linear_gradient_vertices(&mut self, buffers: &VertexBuffers) { + self + .draw_linear_gradient_pass + .load_triangles_vertices(buffers, &self.device, &self.queue); } fn load_mask_layers(&mut self, layers: &[crate::MaskLayer]) { @@ -187,6 +209,25 @@ impl GPUBackendImpl for WgpuImpl { ); } + fn draw_linear_gradient_triangles( + &mut self, + texture: &mut Self::Texture, + indices: Range, + clear: Option, + ) { + let encoder = command_encoder!(self); + + self.draw_linear_gradient_pass.draw_triangles( + texture, + indices, + clear, + &self.device, + encoder, + &self.textures_bind, + &self.mask_layers_storage, + ); + } + fn draw_alpha_triangles_with_scissor( &mut self, indices: &Range, @@ -582,7 +623,8 @@ impl WgpuImpl { let draw_color_triangles_pass = DrawColorTrianglesPass::new(&device); let draw_img_triangles_pass = DrawImgTrianglesPass::new(&device); - let radial_gradient_pass = DrawRadialGradientTrianglesPass::new(&device); + let draw_radial_gradient_pass = DrawRadialGradientTrianglesPass::new(&device); + let draw_linear_gradient_pass = DrawLinearGradientTrianglesPass::new(&device); let mask_layers_storage = Storage::new(&device, wgpu::ShaderStages::FRAGMENT, 512); WgpuImpl { device, @@ -595,7 +637,8 @@ impl WgpuImpl { draw_alpha_triangles_pass, draw_color_triangles_pass, draw_img_triangles_pass, - draw_radial_gradient_pass: radial_gradient_pass, + draw_radial_gradient_pass, + draw_linear_gradient_pass, textures_bind: TexturesBind::default(), mask_layers_storage, } diff --git a/gpu/src/wgpu_impl/draw_linear_gradient_pass.rs b/gpu/src/wgpu_impl/draw_linear_gradient_pass.rs new file mode 100644 index 000000000..5b175a8d5 --- /dev/null +++ b/gpu/src/wgpu_impl/draw_linear_gradient_pass.rs @@ -0,0 +1,200 @@ +use super::{storage::Storage, vertex_buffer::VerticesBuffer}; +use crate::{ + GradientStopPrimitive, LinearGradientAttr, LinearGradientPrimitive, MaskLayer, TexturesBind, + WgpuTexture, +}; +use ribir_painter::{AntiAliasing, Color, Vertex, VertexBuffers}; +use std::{mem::size_of, ops::Range}; + +pub struct DrawLinearGradientTrianglesPass { + label: &'static str, + vertices_buffer: VerticesBuffer, + pipeline: Option, + shader: wgpu::ShaderModule, + format: Option, + prims_storage: Storage, + stops_storage: Storage, + textures_count: usize, + anti_aliasing: AntiAliasing, +} + +impl DrawLinearGradientTrianglesPass { + pub fn new(device: &wgpu::Device) -> Self { + let vertices_buffer = VerticesBuffer::new(512, 1024, device); + let label = "linear gradient triangles pass"; + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(label), + source: wgpu::ShaderSource::Wgsl( + include_str!("./shaders/linear_gradient_triangles.wgsl").into(), + ), + }); + let prims_storage = Storage::new(device, wgpu::ShaderStages::FRAGMENT, 64); + let stops_storage = Storage::new(device, wgpu::ShaderStages::FRAGMENT, 64); + + Self { + label, + vertices_buffer, + pipeline: None, + shader, + format: None, + textures_count: 0, + prims_storage, + stops_storage, + anti_aliasing: AntiAliasing::None, + } + } + + pub fn load_triangles_vertices( + &mut self, + buffers: &VertexBuffers, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + self.vertices_buffer.write_buffer(buffers, device, queue); + } + + pub fn load_linear_gradient_primitives( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + primitives: &[LinearGradientPrimitive], + ) { + self.prims_storage.write_buffer(device, queue, primitives); + } + + pub fn load_gradient_stops( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + stops: &[GradientStopPrimitive], + ) { + self.stops_storage.write_buffer(device, queue, stops); + } + + #[allow(clippy::too_many_arguments)] + pub fn draw_triangles( + &mut self, + texture: &WgpuTexture, + indices: Range, + clear: Option, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + textures_bind: &TexturesBind, + mask_layer_storage: &Storage, + ) { + self.update( + texture.format(), + texture.anti_aliasing, + device, + textures_bind, + mask_layer_storage.layout(), + ); + let pipeline = self.pipeline.as_ref().unwrap(); + + let color_attachments = texture.color_attachments(clear); + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(self.label), + color_attachments: &[Some(color_attachments)], + depth_stencil_attachment: None, + }); + + rpass.set_vertex_buffer(0, self.vertices_buffer.vertices().slice(..)); + rpass.set_index_buffer( + self.vertices_buffer.indices().slice(..), + wgpu::IndexFormat::Uint32, + ); + rpass.set_bind_group(0, mask_layer_storage.bind_group(), &[]); + rpass.set_bind_group(1, self.stops_storage.bind_group(), &[]); + rpass.set_bind_group(2, self.prims_storage.bind_group(), &[]); + rpass.set_bind_group(3, textures_bind.assert_bind(), &[]); + + rpass.set_pipeline(pipeline); + rpass.draw_indexed(indices, 0, 0..1); + } + + fn update( + &mut self, + format: wgpu::TextureFormat, + anti_aliasing: AntiAliasing, + device: &wgpu::Device, + textures_bind: &TexturesBind, + mask_bind_layout: &wgpu::BindGroupLayout, + ) { + if self.format != Some(format) + || textures_bind.textures_count() != self.textures_count + || anti_aliasing != self.anti_aliasing + { + self.pipeline.take(); + self.format = Some(format); + self.textures_count = textures_bind.textures_count(); + self.anti_aliasing = anti_aliasing; + } + + if self.pipeline.is_none() { + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("update triangles pipeline layout"), + bind_group_layouts: &[ + mask_bind_layout, + self.stops_storage.layout(), + self.prims_storage.layout(), + textures_bind.assert_layout(), + ], + push_constant_ranges: &[], + }); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(self.label), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &self.shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: size_of::>() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + // prim_idx + wgpu::VertexAttribute { + offset: 8, + shader_location: 1, + format: wgpu::VertexFormat::Uint32, + }, + ], + }], + }, + fragment: Some(wgpu::FragmentState { + module: &self.shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::all(), + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + // Always draw rect with transform, there is no distinction between front and back, + // everything needs to be drawn. + cull_mode: None, + unclipped_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: anti_aliasing as u32, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + self.pipeline = Some(pipeline); + } + } +} diff --git a/gpu/src/wgpu_impl/draw_radial_gradient_pass.rs b/gpu/src/wgpu_impl/draw_radial_gradient_pass.rs index 1cb96f91f..ba6c939a7 100644 --- a/gpu/src/wgpu_impl/draw_radial_gradient_pass.rs +++ b/gpu/src/wgpu_impl/draw_radial_gradient_pass.rs @@ -48,7 +48,7 @@ impl DrawRadialGradientTrianglesPass { &mut self, buffers: &VertexBuffers, device: &wgpu::Device, - queue: &mut wgpu::Queue, + queue: &wgpu::Queue, ) { self.vertices_buffer.write_buffer(buffers, device, queue); } @@ -56,7 +56,7 @@ impl DrawRadialGradientTrianglesPass { pub fn load_radial_gradient_primitives( &mut self, device: &wgpu::Device, - queue: &mut wgpu::Queue, + queue: &wgpu::Queue, primitives: &[RadialGradientPrimitive], ) { self.prims_storage.write_buffer(device, queue, primitives); @@ -65,7 +65,7 @@ impl DrawRadialGradientTrianglesPass { pub fn load_gradient_stops( &mut self, device: &wgpu::Device, - queue: &mut wgpu::Queue, + queue: &wgpu::Queue, stops: &[GradientStopPrimitive], ) { self.stops_storage.write_buffer(device, queue, stops); @@ -74,7 +74,7 @@ impl DrawRadialGradientTrianglesPass { #[allow(clippy::too_many_arguments)] pub fn draw_triangles( &mut self, - texture: &mut WgpuTexture, + texture: &WgpuTexture, indices: Range, clear: Option, device: &wgpu::Device, diff --git a/gpu/src/wgpu_impl/shaders/linear_gradient_triangles.wgsl b/gpu/src/wgpu_impl/shaders/linear_gradient_triangles.wgsl new file mode 100644 index 000000000..b201a0371 --- /dev/null +++ b/gpu/src/wgpu_impl/shaders/linear_gradient_triangles.wgsl @@ -0,0 +1,138 @@ +struct Vertex { + @location(0) pos: vec2, + @location(1) prim_idx: u32, +}; + +struct FragInput { + @builtin(position) pos: vec4, + @location(0) prim_idx: u32, +} + +@vertex +fn vs_main(v: Vertex) -> FragInput { + var input: FragInput; + // convert from gpu-backend coords(0..1) to wgpu corrds(-1..1) + let pos = v.pos * vec2(2., -2.) + vec2(-1., 1.); + input.pos = vec4(pos, 0.0, 1.0); + input.prim_idx = v.prim_idx; + return input; +} + + +struct MaskLayer { + transform: mat3x2, + min: vec2, + max: vec2, + mask_tex_idx: u32, + prev_mask_idx: i32, +} + +struct Stop { + red: f32, + green: f32, + blue: f32, + alpha: f32, + offset: f32, +} + +struct Primitive { + transform: mat3x2, + stop_start: i32, + stop_cnt: i32, + start_position: vec2, + end_position: vec2, + mask_head: i32, + spread: u32, // 0 for pad, 1 for reflect, 2 for repeat +} + +@group(0) @binding(0) +var mask_layers: array; + +@group(1) @binding(0) +var stops: array; + +@group(2) @binding(0) +var prims: array; + +@group(3) @binding(0) +var textures: binding_array>; +@group(3) @binding(1) +var samplers: binding_array; + +fn calc_mask_alpha(pos: vec2, mask_idx: i32) -> f32 { + var alpha = 1.; + var mask_idx = mask_idx; + loop { + if mask_idx < 0 { + break; + } + let mask = mask_layers[u32(mask_idx)]; + + var mask_pos = mask.transform * vec3(pos, 1.); + if any(mask_pos < mask.min) || any(mask.max < mask_pos) { + alpha = 0.; + break; + } + + let mask_tex_idx = mask.mask_tex_idx; + let texture = textures[mask_tex_idx]; + let s_sampler = samplers[mask_tex_idx]; + + let tex_size = textureDimensions(texture); + mask_pos = mask_pos / vec2(f32(tex_size.x), f32(tex_size.y)); + let a = textureSample(texture, s_sampler, mask_pos).r; + alpha = alpha * a; + if alpha == 0. { + break; + } + mask_idx = mask.prev_mask_idx; + } + return alpha; +} + +fn calc_offset(x: f32, y: f32, x_0: f32, y_0: f32, x_1: f32, y_1: f32) -> f32 { + let dx_0 = x - x_0; + let dy_0 = y - y_0; + let dx_1_0 = x_1 - x_0; + let dy_1_0 = y_1 - y_0; + + return (dx_0 * dx_1_0 + dy_0 * dy_1_0) / (dx_1_0 * dx_1_0 + dy_1_0 * dy_1_0); +} + +@fragment +fn fs_main(input: FragInput) -> @location(0) vec4 { + let prim = prims[input.prim_idx]; + let alpha = calc_mask_alpha(input.pos.xy, prim.mask_head); + let pos = prim.transform * vec3(input.pos.xy, 1.); + + if (prim.start_position.x == prim.end_position.x && + prim.start_position.y == prim.end_position.y) { + discard; + } + var offset = calc_offset(pos.x, pos.y, prim.start_position.x, prim.start_position.y, prim.end_position.x, prim.end_position.y); + + if (prim.spread == 0u) { + // pad + offset = min(1., max(0., offset)); + } else if (prim.spread == 1u) { + //reflect + offset = 1. - abs(fract(offset / 2.) - 0.5) * 2.; + } else { + //repeat + offset = fract(offset); + } + + var prev = stops[prim.stop_start]; + var next = stops[prim.stop_start + 1]; + for (var i = 2; i < prim.stop_cnt && next.offset < offset; i++) { + prev = next; + next = stops[prim.stop_start + i]; + } + + offset = max(prev.offset, min(next.offset, offset)); + let weight1 = (next.offset - offset) / (next.offset - prev.offset); + let weight2 = 1. - weight1; + let prev_color = vec4(prev.red, prev.green, prev.blue, prev.alpha); + let next_color = vec4(next.red, next.green, next.blue, next.alpha); + return (prev_color * weight1 + next_color * weight2) * alpha; +} diff --git a/gpu/src/wgpu_impl/shaders/radial_gradient_triangles.wgsl b/gpu/src/wgpu_impl/shaders/radial_gradient_triangles.wgsl index 3ff833276..a5f7bb325 100644 --- a/gpu/src/wgpu_impl/shaders/radial_gradient_triangles.wgsl +++ b/gpu/src/wgpu_impl/shaders/radial_gradient_triangles.wgsl @@ -94,7 +94,8 @@ fn calc_mask_alpha(pos: vec2, mask_idx: i32) -> f32 { } -fn calc_offset(x: f32, y: f32, x_0: f32, y_0: f32, r_0: f32, x_1: f32, y_1: f32, r_1: f32) -> f32 { +// input the center and radius of the circles, return the tag of resolvable (1. mean resolvable and -1. unresolvable) and the offset if tag is resolvable. +fn calc_offset(x: f32, y: f32, x_0: f32, y_0: f32, r_0: f32, x_1: f32, y_1: f32, r_1: f32) -> vec2 { /* see definition at https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient with offset ω, Radial gradients must be rendered by following these steps: @@ -138,12 +139,12 @@ fn calc_offset(x: f32, y: f32, x_0: f32, y_0: f32, r_0: f32, x_1: f32, y_1: f32, if (abs(a) < 0.1) { if (abs(b) < 0.1) { - return -1.; + return vec2(-1., 0.); } else { - return -c / b; + return vec2(1., -c / b); } } else if (delta < 0.) { - return -1.; + return vec2(-1., 0.); } let sqrt_delta = sqrt(delta); @@ -151,7 +152,7 @@ fn calc_offset(x: f32, y: f32, x_0: f32, y_0: f32, r_0: f32, x_1: f32, y_1: f32, let w1 = (-b + sqrt_delta) / _2a; let w2 = (-b - sqrt_delta) / _2a; - return max(w1, w2); + return vec2(1., max(w1, w2)); } @fragment @@ -160,11 +161,14 @@ fn fs_main(input: FragInput) -> @location(0) vec4 { let alpha = calc_mask_alpha(input.pos.xy, prim.mask_head); let pos = prim.transform * vec3(input.pos.xy, 1.); - var offset = calc_offset(pos.x, pos.y, prim.start_center.x, prim.start_center.y, prim.start_radius, prim.end_center.x, prim.end_center.y, prim.end_radius); - if (offset < 0.) { + let res = calc_offset(pos.x, pos.y, prim.start_center.x, prim.start_center.y, prim.start_radius, prim.end_center.x, prim.end_center.y, prim.end_radius); + + if (res[0] < 0. || ( + prim.start_radius != prim.end_radius && + res[1] < (prim.start_radius / (prim.start_radius - prim.end_radius)))) { discard; } - + var offset = res[1]; if (prim.spread == 0u) { // pad offset = min(1., max(0., offset)); diff --git a/painter/src/color.rs b/painter/src/color.rs index b4a875e94..3144f9d2a 100644 --- a/painter/src/color.rs +++ b/painter/src/color.rs @@ -2,6 +2,8 @@ use material_color_utilities_rs::htc; use ribir_geom::{Point, Transform}; use serde::{Deserialize, Serialize}; +use crate::SpreadMethod; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct Color { pub red: u8, @@ -15,7 +17,13 @@ pub struct GradientStop { pub color: Color, pub offset: f32, } -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] + +impl GradientStop { + #[inline] + pub fn new(color: Color, offset: f32) -> Self { Self { color, offset } } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] pub struct RadialGradient { pub start_center: Point, pub start_radius: f32, @@ -23,6 +31,16 @@ pub struct RadialGradient { pub end_radius: f32, pub stops: Vec, pub transform: Transform, + pub spread_method: SpreadMethod, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct LinearGradient { + pub start: Point, + pub end: Point, + pub stops: Vec, + pub transform: Transform, + pub spread_method: SpreadMethod, } /// Describe the light tone of a color, should between [0, 1.0], 0.0 gives diff --git a/painter/src/painter.rs b/painter/src/painter.rs index b78ad9693..543134b50 100644 --- a/painter/src/painter.rs +++ b/painter/src/painter.rs @@ -75,13 +75,24 @@ pub struct PaintPath { } #[repr(u32)] -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq)] pub enum SpreadMethod { + #[default] Pad, Reflect, Repeat, } +impl From for SpreadMethod { + fn from(value: usvg::SpreadMethod) -> Self { + match value { + usvg::SpreadMethod::Pad => SpreadMethod::Pad, + usvg::SpreadMethod::Reflect => SpreadMethod::Reflect, + usvg::SpreadMethod::Repeat => SpreadMethod::Repeat, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaintCommand { ColorPath { @@ -95,13 +106,21 @@ pub enum PaintCommand { }, RadialGradient { path: PaintPath, - stops: Vec, start: Point, start_radius: f32, end: Point, end_radius: f32, - spread: SpreadMethod, + stops: Vec, + transform: Transform, + spread_method: SpreadMethod, + }, + LinearGradient { + path: PaintPath, + start: Point, + end: Point, + stops: Vec, transform: Transform, + spread_method: SpreadMethod, }, // Todo: keep rectangle clip. Clip(PaintPath), @@ -309,14 +328,22 @@ impl Painter { }, Brush::Image(img) => PaintCommand::ImgPath { path, img, opacity }, Brush::RadialGradient(radial_gradient) => PaintCommand::RadialGradient { - path: path, + path, stops: radial_gradient.stops, start: radial_gradient.start_center, start_radius: radial_gradient.start_radius, end: radial_gradient.end_center, end_radius: radial_gradient.end_radius, transform: radial_gradient.transform, - spread: SpreadMethod::Pad, + spread_method: SpreadMethod::Pad, + }, + Brush::LinearGradient(linear) => PaintCommand::LinearGradient { + path, + start: linear.start, + end: linear.end, + stops: linear.stops, + transform: linear.transform, + spread_method: SpreadMethod::Pad, }, }; self.commands.push(cmd); diff --git a/painter/src/style.rs b/painter/src/style.rs index 9b6b5f107..b576ad209 100644 --- a/painter/src/style.rs +++ b/painter/src/style.rs @@ -1,4 +1,7 @@ -use crate::{color::RadialGradient, Color, PixelImage}; +use crate::{ + color::{LinearGradient, RadialGradient}, + Color, PixelImage, +}; use ribir_algo::ShareResource; use serde::{Deserialize, Serialize}; @@ -8,6 +11,7 @@ pub enum Brush { /// Image brush always use a repeat mode to brush the path. Image(ShareResource), RadialGradient(RadialGradient), + LinearGradient(LinearGradient), } impl Brush { diff --git a/painter/src/svg.rs b/painter/src/svg.rs index 541a728ed..b50eb78bf 100644 --- a/painter/src/svg.rs +++ b/painter/src/svg.rs @@ -1,11 +1,11 @@ use crate::{ - color::RadialGradient, Brush, Color, GradientStop, LineCap, LineJoin, Path, PathPaintStyle, - StrokeOptions, + color::{LinearGradient, RadialGradient}, + Brush, Color, GradientStop, LineCap, LineJoin, Path, PathPaintStyle, StrokeOptions, }; use ribir_geom::{Point, Size, Transform, Vector}; use serde::{Deserialize, Serialize}; -use std::{error::Error, io::Read, rc::Rc, time::Instant}; -use usvg::{Options, Tree, TreeParsing}; +use std::{error::Error, io::Read}; +use usvg::{Options, Stop, Tree, TreeParsing}; #[derive(Serialize, Deserialize, Clone)] pub struct Svg { @@ -21,18 +21,16 @@ pub struct SvgPath { pub style: PathPaintStyle, } -/// Fits size into a viewbox. -fn fit_view_box(s: usvg::Size, vb: &usvg::ViewBox) -> usvg::Size { +/// Fits size into a viewbox. copy from resvg +fn fit_view_box(size: usvg::Size, vb: &usvg::ViewBox) -> usvg::Size { let s = vb.rect.size(); if vb.aspect.align == usvg::Align::None { s + } else if vb.aspect.slice { + size.expand_to(s) } else { - if vb.aspect.slice { - s.expand_to(s) - } else { - s.scale_to(s) - } + size.scale_to(s) } } @@ -50,7 +48,7 @@ impl Svg { size.height() / fit_size.height(), ) .to_f32(); - let t = Transform::translation(-view_rect.x() as f32, -view_rect.y() as f32); + let t = Transform::translation(-view_rect.x(), -view_rect.y()); let mut t_stack = TransformStack::new(t); let mut paths = vec![]; @@ -86,10 +84,10 @@ impl Svg { usvg::LineJoin::Round => LineJoin::Round, }; let options = StrokeOptions { - width: stroke.width.get() as f32, + width: stroke.width.get(), line_cap: cap, line_join: join, - miter_limit: stroke.miterlimit.get() as f32, + miter_limit: stroke.miterlimit.get(), }; let brush = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size); @@ -131,7 +129,7 @@ impl Svg { }); Ok(Svg { - size: Size::new(size.width() as f32, size.height() as f32), + size: Size::new(size.width(), size.height()), paths: paths.into_boxed_slice(), view_scale, }) @@ -187,52 +185,45 @@ fn matrix_convert(t: usvg::Transform) -> Transform { fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usvg::Size) -> Brush { match paint { usvg::Paint::Color(usvg::Color { red, green, blue }) => Color::from_rgb(*red, *green, *blue) - .with_alpha(opacity.get() as f32) + .with_alpha(opacity.get()) .into(), - usvg::Paint::LinearGradient(gradient) => { - let mut stops = gradient.stops.clone(); - stops.sort_by(|s1, s2| s1.offset.cmp(&s2.offset)); - let mut offset = 0.; - let mut red = 0.; - let mut green = 0.; - let mut blue = 0.; - for stop in stops.iter() { - let color = stop.color; - let weight = stop.offset.get() - offset; - offset = stop.offset.get(); - red += (color.red as f32 - red) * weight; - blue += (color.blue as f32 - blue) * weight; - green += (color.green as f32 - green) * weight; - } - Color::from_rgb(red as u8, green as u8, blue as u8).into() + usvg::Paint::LinearGradient(linear) => { + let stops = convert_to_gradient_stops(&linear.stops); + let size_scale = match linear.units { + usvg::Units::UserSpaceOnUse => (1., 1.), + usvg::Units::ObjectBoundingBox => (size.width(), size.height()), + }; + let gradient = LinearGradient { + start: Point::new(linear.x1 * size_scale.0, linear.y1 * size_scale.1), + end: Point::new(linear.y2 * size_scale.0, linear.y2 * size_scale.1), + stops, + transform: matrix_convert(linear.transform), + spread_method: linear.spread_method.into(), + }; + Brush::LinearGradient(gradient) } - usvg::Paint::RadialGradient(gradient) => { - let mut stops = gradient.stops.clone(); - stops.sort_by(|s1, s2| s1.offset.cmp(&s2.offset)); - - let stops = stops - .iter() - .map(|stop| { - let usvg::Color { red, green, blue } = stop.color; - GradientStop { - offset: stop.offset.get(), - color: Color::from_rgb(red, green, blue), - } - }) - .collect(); - let size_scale = match gradient.units { + usvg::Paint::RadialGradient(radial_gradient) => { + let stops = convert_to_gradient_stops(&radial_gradient.stops); + let size_scale = match radial_gradient.units { usvg::Units::UserSpaceOnUse => (1., 1.), usvg::Units::ObjectBoundingBox => (size.width(), size.height()), }; - let radial = RadialGradient { - start_center: Point::new(gradient.fx * size_scale.0, gradient.fy * size_scale.1), + let gradient = RadialGradient { + start_center: Point::new( + radial_gradient.fx * size_scale.0, + radial_gradient.fy * size_scale.1, + ), start_radius: 0., - end_center: Point::new(gradient.cx * size_scale.0, gradient.cy * size_scale.1), - end_radius: gradient.r.get() * size_scale.0, + end_center: Point::new( + radial_gradient.cx * size_scale.0, + radial_gradient.cy * size_scale.1, + ), + end_radius: radial_gradient.r.get() * size_scale.0, stops, - transform: matrix_convert(gradient.transform), + transform: matrix_convert(radial_gradient.transform), + spread_method: radial_gradient.spread_method.into(), }; - Brush::RadialGradient(radial) + Brush::RadialGradient(gradient) } paint => { log::warn!("[painter]: not support `{paint:?}` in svg, use black instead!"); @@ -241,6 +232,35 @@ fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usv } } +fn convert_to_gradient_stops(stops: &Vec) -> Vec { + assert!(!stops.is_empty()); + + let mut stops: Vec<_> = stops + .iter() + .map(|stop| { + let usvg::Color { red, green, blue } = stop.color; + GradientStop { + offset: stop.offset.get(), + color: Color::from_rgb(red, green, blue), + } + }) + .collect(); + + stops.sort_by(|s1, s2| s1.offset.partial_cmp(&s2.offset).unwrap()); + + if let Some(first) = stops.first() { + if first.offset != 0. { + stops.insert(0, GradientStop { offset: 0., color: first.color }); + } + } + if let Some(last) = stops.last() { + if last.offset < 1. { + stops.push(GradientStop { offset: 1., color: last.color }); + } + } + stops +} + struct TransformStack { stack: Vec, } diff --git a/ribir/src/winit_shell_wnd.rs b/ribir/src/winit_shell_wnd.rs index 69eb18918..b40d8a444 100644 --- a/ribir/src/winit_shell_wnd.rs +++ b/ribir/src/winit_shell_wnd.rs @@ -118,6 +118,7 @@ impl ShellWindow for WinitShellWnd { PaintCommand::ColorPath { path, .. } | PaintCommand::ImgPath { path, .. } | PaintCommand::RadialGradient { path, .. } + | PaintCommand::LinearGradient { path, .. } | PaintCommand::Clip(path) => path.scale(self.winit_wnd.scale_factor() as f32), PaintCommand::PopClip => {} }); diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png new file mode 100644 index 000000000..c1d472eac Binary files /dev/null and b/test_cases/ribir_gpu/gpu_backend/tests/draw_linear_gradient_wgpu.png differ diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png new file mode 100644 index 000000000..f9922f889 Binary files /dev/null and b/test_cases/ribir_gpu/gpu_backend/tests/draw_radial_gradient_wgpu.png differ