use crate::render_backend::CpuBuffers;
use crate::Box2D;
use crate::Point2D;
use crate::RenderContext;
use crate::Transform2D;
use lyon::lyon_tessellation::BuffersBuilder;
use lyon::lyon_tessellation::FillOptions;
use lyon::lyon_tessellation::FillTessellator;
use lyon::lyon_tessellation::FillVertex;
use lyon::lyon_tessellation::VertexBuffers;
use lyon::path::Path;
use crate::render_backend::data::GpuColor;
use crate::render_backend::data::GpuGradient;
use crate::render_backend::data::GpuPrimitive;
use crate::render_backend::data::GpuTransform;
use crate::render_backend::data::GpuVertex;
use crate::render_backend::RenderBackend;
pub struct WgpuRenderer {
buffers: CpuBuffers,
render_backend: RenderBackend,
transform_stack: Vec<Transform2D>,
clipping_stack: Vec<u32>,
fill_tessellator: FillTessellator,
tolerance: f32,
}
const IDENTITY: Transform2D = Transform2D::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
impl WgpuRenderer {
pub fn new(render_backend: RenderBackend) -> Self {
let geometry: VertexBuffers<GpuVertex, u16> = VertexBuffers::new();
let fill_tessellator = FillTessellator::new();
let default_clipp = GpuTransform {
transform: [[0.0; 2]; 3],
_pad: 0,
_pad2: 0,
};
Self {
render_backend,
transform_stack: Vec::new(),
buffers: CpuBuffers {
geometry,
primitives: Vec::new(),
colors: Vec::new(),
gradients: Vec::new(),
stencils: vec![default_clipp],
},
clipping_stack: vec![0],
fill_tessellator,
tolerance: 1.0, }
}
pub fn rect(&mut self, rect: Box2D<f32>, color: Fill) {
let mut builder = Path::builder().transformed(self.current_transform());
builder.add_rectangle(&rect, lyon::path::Winding::Positive);
let path = builder.build();
self.fill_path(path, color);
}
pub fn circle(&mut self, position: Point2D, radius: f32, color: Fill) {
let mut builder = Path::builder().transformed(self.current_transform());
builder.add_circle(position, radius, lyon::path::Winding::Positive);
let path = builder.build();
self.fill_path(path, color);
}
pub fn with_transform(
&mut self,
transform: Transform2D,
inner_rc: impl FnOnce(&mut WgpuRenderer),
) {
let last = self.current_transform();
self.transform_stack.push(transform.then(last));
inner_rc(self);
self.transform_stack.pop();
}
fn current_transform(&self) -> &Transform2D {
self.transform_stack.last().unwrap_or(&IDENTITY)
}
fn current_clipping_id(&self) -> u32 {
*self.clipping_stack.last().expect("clipper stack empty???")
}
pub fn with_clipping_bounds(
&mut self,
bounds: Box2D<f32>,
inner_rc: impl FnOnce(&mut WgpuRenderer),
) {
let point_to_unit_rect = Transform2D::translation(-bounds.min.x, -bounds.min.y)
.then_scale(1.0 / bounds.width(), 1.0 / bounds.height());
let clipping_bounds = self
.current_transform()
.inverse()
.expect("non-invertible transform was pushed to the stack") .then(&point_to_unit_rect);
self.clipping_stack.push(self.buffers.stencils.len() as u32);
self.buffers.stencils.push(GpuTransform {
transform: clipping_bounds.to_arrays(),
_pad: 0,
_pad2: 0,
});
inner_rc(self);
self.clipping_stack.pop();
}
pub fn flush(&mut self) {
self.render_backend.render(&mut self.buffers);
}
}
impl RenderContext for WgpuRenderer {
fn stroke_path(&mut self, _path: Path, _stroke: Stroke) {
unimplemented!()
}
fn fill_path(&mut self, path: Path, fill: Fill) {
let fill_id;
let fill_type_flag;
match fill {
Fill::Solid(color) => {
fill_id = self.buffers.colors.len() as u16;
fill_type_flag = 0;
self.buffers.colors.push(GpuColor { color: color.rgba });
}
Fill::Gradient {
gradient_type,
pos,
main_axis,
off_axis,
stops,
} => {
fill_id = self.buffers.gradients.len() as u16;
fill_type_flag = 1;
if stops.len() > 8 {
eprintln!("can't draw graidents with more than 8 stops. truncating.");
}
let len = stops.len().min(8);
let mut colors_buff = [[0.0; 4]; 8];
let mut stops_buff = [0.0; 8];
for i in 0..len {
colors_buff[i] = stops[i].color.rgba;
stops_buff[i] = stops[i].stop;
}
self.buffers.gradients.push(GpuGradient {
type_id: match gradient_type {
GradientType::Linear => 0,
GradientType::Radial => 1,
},
position: pos,
main_axis,
off_axis,
stop_count: len as u32,
colors: colors_buff,
stops: stops_buff,
_padding: [0; 16],
});
}
}
let primitive = GpuPrimitive {
fill_id,
fill_type_flag,
clipping_id: self.current_clipping_id(),
transform_id: 0,
z_index: 0,
};
let prim_id = self.buffers.primitives.len() as u32;
self.buffers.primitives.push(primitive);
let options = FillOptions::tolerance(self.tolerance);
let mut geometry_builder =
BuffersBuilder::new(&mut self.buffers.geometry, |vertex: FillVertex| GpuVertex {
position: vertex.position().to_array(),
normal: [0.0; 2],
prim_id,
});
self.fill_tessellator
.tessellate_path(&path, &options, &mut geometry_builder)
.expect("failed to tesselate path. did you use nan float values?");
}
}
pub struct GradientStop {
pub color: Color,
pub stop: f32,
}
pub enum GradientType {
Linear,
Radial,
}
pub struct Color {
rgba: [f32; 4],
}
impl Color {
pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { rgba: [r, g, b, a] }
}
pub fn hsba(_h: f32, _s: f32, _b: f32, _a: f32) -> Self {
unimplemented!()
}
pub fn hlca(_h: f32, _l: f32, _c: f32, _a: f32) -> Self {
unimplemented!()
}
}
pub enum Fill {
Solid(Color),
Gradient {
gradient_type: GradientType,
pos: [f32; 2],
main_axis: [f32; 2],
off_axis: [f32; 2],
stops: Vec<GradientStop>,
},
}
pub struct Stroke {
pub color: Color,
pub weight: f32,
}
pub struct RoundedRectRadii {
_top_left_radius: f32,
_top_right_radius: f32,
_bottom_left_radius: f32,
_bottom_right_radius: f32,
}
impl RoundedRectRadii {
pub fn new(tl: f32, tr: f32, bl: f32, br: f32) -> Self {
Self {
_top_left_radius: tl,
_top_right_radius: tr,
_bottom_left_radius: bl,
_bottom_right_radius: br,
}
}
}