use encase::{ShaderType, UniformBuffer};
use glam::{Mat4, Vec2, Vec4};
use serde::{Deserialize, Serialize};
use std::sync::{Arc};
use crate::render_traits::{
AspectRatioAlignmentMode, AspectRatioMode, ColorMode, DrawToRasterCpu, DrawToRasterGpu, DrawToSvg, MarginParams, PickableLayer, PreparedLayer, UnitsMode, ViewParams
};
use crate::positioning::get_point_position;
use crate::render_types::{CpuContext, CpuRenderPass, PrepareResult, RenderResult};
use crate::render_types::GpuContext;
use crate::two::shapes::{
TwoCircle, TwoColor, TwoElement, TwoGroup, TwoLine, TwoPath, TwoRectangle, TwoText
};
use crate::two::svg::{update_svg, SvgContext};
use crate::wgpu;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RectLayerParams {
pub layer_id: String,
pub bounds: Option<MarginParams>,
pub data_unit_mode_x: UnitsMode,
pub data_unit_mode_y: UnitsMode,
pub stroke_width: Option<f32>,
pub stroke_width_unit_mode: UnitsMode,
pub fill_color_mode: ColorMode,
pub fill_color: Option<(u8, u8, u8)>,
pub position_x0: Arc<Vec<f32>>, pub position_y0: Arc<Vec<f32>>,
pub position_x1: Arc<Vec<f32>>,
pub position_y1: Arc<Vec<f32>>,
pub labels_vec: Arc<Vec<i32>>,
}
pub struct RectLayer {
view_params: ViewParams,
layer_params: RectLayerParams,
}
impl RectLayer {
pub fn new(view_params: ViewParams, layer_params: RectLayerParams) -> Self {
if layer_params.stroke_width_unit_mode == UnitsMode::Data && (layer_params.data_unit_mode_x == UnitsMode::Pixels || layer_params.data_unit_mode_y == UnitsMode::Pixels) {
panic!("line_width_unit_mode cannot be 'data' when data_unit_mode is 'pixels'");
}
Self {
view_params,
layer_params,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl PreparedLayer for RectLayer {
async fn prepare(&mut self, _gpu_context: Option<&GpuContext<'_>>) -> PrepareResult {
return PrepareResult {
bailed_early: false,
};
}
}
#[derive(ShaderType, Debug)]
struct RectLayerUniforms {
layer_size: Vec2, camera_view: Mat4, data_unit_mode_x: u32, data_unit_mode_y: u32, filled: u32, stroke_width: f32, stroke_width_unit_mode: u32, aspect_ratio_mode: u32, aspect_ratio_alignment_mode: u32, fill_color_mode: u32,
fill_color: Vec4, }
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToRasterGpu for RectLayer {
async fn draw(&self, gpu_context: &GpuContext<'_>, pass: &mut wgpu::RenderPass) {
let GpuContext { device, queue } = gpu_context;
let Self { layer_params, view_params } = self;
let position_x0_bytes = bytemuck::cast_slice(&layer_params.position_x0);
let position_y0_bytes = bytemuck::cast_slice(&layer_params.position_y0);
let position_x1_bytes = bytemuck::cast_slice(&layer_params.position_x1);
let position_y1_bytes = bytemuck::cast_slice(&layer_params.position_y1);
let n = layer_params.labels_vec.len();
let labels_bytes: &[u8] = bytemuck::cast_slice(&layer_params.labels_vec);
let position_x0_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("x0 Coordinates Storage Buffer"),
size: position_x0_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&position_x0_buffer, 0, position_x0_bytes);
let position_y0_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("y0 Coordinates Storage Buffer"),
size: position_y0_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&position_y0_buffer, 0, position_y0_bytes);
let position_x1_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("x1 Coordinates Storage Buffer"),
size: position_x1_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&position_x1_buffer, 0, position_x1_bytes);
let position_y1_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("y1 Coordinates Storage Buffer"),
size: position_y1_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&position_y1_buffer, 0, position_y1_bytes);
let labels_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Class labels Storage Buffer"),
size: labels_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&labels_buffer, 0, labels_bytes);
let camera_view = view_params.camera_view.unwrap_or([
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]);
let bounds = if layer_params.bounds.is_none() {
&view_params.margins
} else {
&layer_params.bounds
};
let margin_top = if let Some(margin_params) = &bounds {
margin_params.margin_top.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_right = if let Some(margin_params) = &bounds {
margin_params.margin_right.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_bottom = if let Some(margin_params) = &bounds {
margin_params.margin_bottom.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_left = if let Some(margin_params) = &bounds {
margin_params.margin_left.unwrap_or(0.0)
} else { 0.0 } as f64;
let viewport_w = view_params.width as f32;
let viewport_h = view_params.height as f32;
let layer_w = viewport_w - (margin_left + margin_right) as f32;
let layer_h = viewport_h - (margin_top + margin_bottom) as f32;
let uniform_struct = RectLayerUniforms {
layer_size: Vec2::new(layer_w, layer_h),
camera_view: Mat4::from_cols_array(&camera_view),
data_unit_mode_x: match layer_params.data_unit_mode_x {
UnitsMode::Pixels => 0,
UnitsMode::Data => 1,
},
data_unit_mode_y: match layer_params.data_unit_mode_y {
UnitsMode::Pixels => 0,
UnitsMode::Data => 1,
},
filled: if layer_params.stroke_width.is_none() { 1 } else { 0 },
stroke_width: layer_params.stroke_width.unwrap_or(0.0),
stroke_width_unit_mode: match layer_params.stroke_width_unit_mode {
UnitsMode::Pixels => 0,
UnitsMode::Data => 1,
},
aspect_ratio_mode: match view_params.aspect_ratio_mode {
AspectRatioMode::Ignore => 0,
AspectRatioMode::Contain => 1,
AspectRatioMode::Cover => 2,
},
aspect_ratio_alignment_mode: match view_params.aspect_ratio_alignment_mode {
AspectRatioAlignmentMode::Center => 0,
AspectRatioAlignmentMode::Start => 1,
AspectRatioAlignmentMode::End => 2,
},
fill_color_mode: match layer_params.fill_color_mode {
ColorMode::Static => 0,
ColorMode::Explicit => 1,
ColorMode::Categorical => 2,
ColorMode::Quantitative => 3,
},
fill_color: match layer_params.fill_color {
Some(color) => Vec4::from_array([
color.0 as f32 / 255.0,
color.1 as f32 / 255.0,
color.2 as f32 / 255.0,
1.0
]),
None => Vec4::from_array([0.0, 0.0, 0.0, 1.0])
},
};
let mut buffer = UniformBuffer::new(Vec::<u8>::new());
buffer.write(&uniform_struct).unwrap();
let uniform_bytes = buffer.into_inner();
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"),
size: uniform_bytes.len() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&uniform_buffer, 0, &uniform_bytes);
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("RectLayer BGL"),
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::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("RectLayer BG"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: position_x0_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: position_y0_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: position_x1_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: position_y1_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: labels_buffer.as_entire_binding(),
},
],
});
let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/rect_layer.wgsl"));
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("RectLayer PLD"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("RectLayer RPD"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8UnormSrgb,
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::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
cache: None,
multiview_mask: None,
});
pass.set_viewport(
margin_left as f32,
margin_top as f32,
viewport_w - (margin_left + margin_right) as f32,
viewport_h - (margin_top + margin_bottom) as f32,
0.0, 1.0, );
pass.set_scissor_rect(
margin_left as u32,
margin_top as u32,
(viewport_w - (margin_left + margin_right) as f32) as u32,
(viewport_h - (margin_top + margin_bottom) as f32) as u32,
);
pass.set_pipeline(&render_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.draw(0..4, 0..(n as u32));
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToRasterCpu for RectLayer {
async fn draw(&self, _cpu_context: &CpuContext<'_>, _pass: &mut CpuRenderPass) {}
}
const CATEGORICAL_COLORS: [(u8, u8, u8); 10] = [
(31, 119, 180),
(255, 127, 14),
(44, 160, 44),
(214, 39, 40),
(148, 103, 189),
(227, 119, 194),
(127, 127, 127),
(188, 189, 34),
(23, 190, 207),
(219, 219, 219),
];
fn get_categorical_color(index: i32) -> (u8, u8, u8) {
CATEGORICAL_COLORS[index.rem_euclid(10) as usize]
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToSvg for RectLayer {
async fn draw(&self, ctx: &mut SvgContext) {
let Self { layer_params, view_params } = self;
let n = layer_params.labels_vec.len();
let camera_view = view_params.camera_view.unwrap_or([
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]);
let bounds = if layer_params.bounds.is_none() {
&view_params.margins
} else {
&layer_params.bounds
};
let margin_top = if let Some(margin_params) = &bounds {
margin_params.margin_top.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_right = if let Some(margin_params) = &bounds {
margin_params.margin_right.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_bottom = if let Some(margin_params) = &bounds {
margin_params.margin_bottom.unwrap_or(0.0)
} else { 0.0 } as f64;
let margin_left = if let Some(margin_params) = &bounds {
margin_params.margin_left.unwrap_or(0.0)
} else { 0.0 } as f64;
let viewport_w = view_params.width as f32;
let viewport_h = view_params.height as f32;
let layer_w = viewport_w - (margin_left + margin_right) as f32;
let layer_h = viewport_h - (margin_top + margin_bottom) as f32;
let mut svg_elements: Vec<TwoElement> = Vec::with_capacity(n);
for i in 0..n {
let source_x = layer_params.position_x0[i];
let source_y = layer_params.position_y0[i];
let target_x = layer_params.position_x1[i];
let target_y = layer_params.position_y1[i];
let label = layer_params.labels_vec[i];
let (source_x_px, source_y_px) = get_point_position(
source_x,
source_y,
layer_w,
layer_h,
&camera_view,
layer_params.data_unit_mode_x,
layer_params.data_unit_mode_y,
view_params.aspect_ratio_mode,
view_params.aspect_ratio_alignment_mode,
None,
);
let (target_x_px, target_y_px) = get_point_position(
target_x,
target_y,
layer_w,
layer_h,
&camera_view,
layer_params.data_unit_mode_x,
layer_params.data_unit_mode_y,
view_params.aspect_ratio_mode,
view_params.aspect_ratio_alignment_mode,
None,
);
let rect_height = (target_y_px - source_y_px).abs();
let color = TwoColor::Rgb(match layer_params.fill_color_mode {
ColorMode::Categorical => get_categorical_color(label),
_ => layer_params.fill_color.unwrap_or((0, 0, 0)),
});
svg_elements.push(TwoElement::Rectangle(TwoRectangle {
x: source_x_px.min(target_x_px) as f64,
y: ((layer_h - source_y_px.min(target_y_px)) - rect_height) as f64,
width: (target_x_px - source_x_px).abs() as f64,
height: rect_height as f64,
fill: if layer_params.stroke_width.is_none() {
Some(color.clone())
} else { None },
stroke: if layer_params.stroke_width.is_some() {
Some(color)
} else { None },
linewidth: layer_params.stroke_width.unwrap_or(0.0) as f64,
..Default::default()
}));
}
let svg_elements = vec![TwoElement::Group(TwoGroup {
elements: svg_elements,
translate: Some((margin_left, margin_top)),
layer_id: Some(layer_params.layer_id.clone()),
clip_rect: Some((0.0, 0.0, layer_w as f64, layer_h as f64)),
..Default::default()
})];
update_svg(ctx, &svg_elements);
}
}
inventory::submit! {
crate::registry::LayerRegistration {
layer_type_name: "RectLayer",
create_layer: |value, view_params| {
let params: RectLayerParams = serde_json::from_value(value).unwrap();
Box::new(RectLayer::new(view_params.clone(), params))
},
}
}
impl PickableLayer for RectLayer {}