Skip to content

Commit

Permalink
feat(gpu): 🎸 linear gradient
Browse files Browse the repository at this point in the history
  • Loading branch information
wjian23 committed Sep 18, 2023
1 parent 561cde5 commit 0f7b8ab
Show file tree
Hide file tree
Showing 14 changed files with 741 additions and 117 deletions.
171 changes: 151 additions & 20 deletions gpu/src/gpu_backend.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -21,8 +21,11 @@ pub struct GPUBackend<Impl: GPUBackendImpl> {
color_vertices_buffer: VertexBuffers<ColorAttr>,
img_vertices_buffer: VertexBuffers<u32>,
radial_gradient_vertices_buffer: VertexBuffers<RadialGradientAttr>,
gradient_stops: Vec<GradientStopPrimitive>,
radial_gradient_stops: Vec<GradientStopPrimitive>,
radial_gradient_prims: Vec<RadialGradientPrimitive>,
linear_gradient_prims: Vec<LinearGradientPrimitive>,
linear_gradient_stops: Vec<GradientStopPrimitive>,
linear_gradient_vertices_buffer: VertexBuffers<LinearGradientAttr>,
img_prims: Vec<ImgPrimitive>,
draw_indices: Vec<DrawIndices>,
tex_ids_map: TextureIdxMap,
Expand All @@ -37,6 +40,7 @@ enum DrawIndices {
Color(Range<u32>),
Img(Range<u32>),
RadialGradient(Range<u32>),
LinearGradient(Range<u32>),
}

struct ClipLayer {
Expand Down Expand Up @@ -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);
Expand All @@ -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(),
Expand Down Expand Up @@ -224,15 +242,15 @@ where
start_radius,
end,
end_radius,
spread,
spread_method: spread,
transform,
} => {
let ts = transform.then(&path.transform);
if let Some((rect, mask_head)) = self.new_mask_layer(path) {
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,
Expand All @@ -241,23 +259,64 @@ 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;
let attr = RadialGradientAttr { prim_idx };
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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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())
}
});
}
}
Expand Down Expand Up @@ -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)) }

Expand Down Expand Up @@ -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
}
}
72 changes: 55 additions & 17 deletions gpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RadialGradientAttr>);

/// 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<LinearGradientAttr>);
/// 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(
Expand All @@ -134,6 +143,15 @@ pub trait GPUBackendImpl {
clear: Option<Color>,
);

/// 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<u32>,
clear: Option<Color>,
);

fn copy_texture_from_texture(
&mut self,
dist_tex: &mut Self::Texture,
Expand All @@ -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 {
Expand All @@ -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,
}

Expand Down
Loading

0 comments on commit 0f7b8ab

Please sign in to comment.