use encase::{ShaderType, UniformBuffer};
use glam::{Mat4, Vec2, Vec4};
use serde::{Deserialize, Serialize};
use std::sync::{Arc};
use crate::render_traits::{DrawToRasterGpu, DrawToRasterCpu, DrawToSvg, PickableLayer, PreparedLayer, ViewParams, AspectRatioMode, AspectRatioAlignmentMode, UnitsMode, MarginParams};
use crate::render_types::{CpuContext, CpuRenderPass, PrepareResult, RenderResult};
use crate::render_types::GpuContext;
use crate::wgpu;
use crate::two::shapes::{TwoCircle, TwoElement, TwoGroup, TwoLine, TwoPath, TwoRectangle, TwoText};
use crate::two::svg::{update_svg, SvgContext};
use crate::positioning::get_point_position;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LineLayerParams {
pub layer_id: String,
pub bounds: Option<MarginParams>,
pub data_unit_mode_x: UnitsMode,
pub data_unit_mode_y: UnitsMode,
pub line_width: f32,
pub line_width_unit_mode: UnitsMode,
pub source_position_x: Arc<Vec<f32>>, pub source_position_y: Arc<Vec<f32>>,
pub target_position_x: Arc<Vec<f32>>,
pub target_position_y: Arc<Vec<f32>>,
pub labels_vec: Vec<i32>,
}
pub struct LineLayer {
view_params: ViewParams,
layer_params: LineLayerParams,
}
impl LineLayer {
pub fn new(
view_params: ViewParams,
layer_params: LineLayerParams,
) -> Self {
if layer_params.line_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 LineLayer {
async fn prepare(&mut self, _gpu_context: Option<&GpuContext<'_>>) -> PrepareResult {
return PrepareResult {
bailed_early: false,
};
}
}
#[derive(ShaderType, Debug)]
struct LineLayerUniforms {
layer_size: Vec2, camera_view: Mat4, data_unit_mode_x: u32, data_unit_mode_y: u32, line_width: f32, line_width_unit_mode: u32, aspect_ratio_mode: u32, aspect_ratio_alignment_mode: u32, 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 LineLayer {
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 source_x_bytes = bytemuck::cast_slice(&layer_params.source_position_x);
let source_y_bytes = bytemuck::cast_slice(&layer_params.source_position_y);
let target_x_bytes = bytemuck::cast_slice(&layer_params.target_position_x);
let target_y_bytes = bytemuck::cast_slice(&layer_params.target_position_y);
let n = layer_params.labels_vec.len();
let labels_bytes: &[u8] = bytemuck::cast_slice(&layer_params.labels_vec);
let source_x_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Source X Coordinates Storage Buffer"),
size: source_x_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&source_x_buffer, 0, source_x_bytes);
let source_y_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Source Y Coordinates Storage Buffer"),
size: source_y_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&source_y_buffer, 0, source_y_bytes);
let target_x_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Target X Coordinates Storage Buffer"),
size: target_x_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&target_x_buffer, 0, target_x_bytes);
let target_y_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Target Y Coordinates Storage Buffer"),
size: target_y_bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&target_y_buffer, 0, target_y_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 = LineLayerUniforms {
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,
},
line_width: layer_params.line_width,
line_width_unit_mode: match layer_params.line_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,
},
color: Vec4::from_array([1.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("LineLayer 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("LineLayer BG"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: source_x_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: source_y_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: target_x_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: target_y_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: labels_buffer.as_entire_binding(),
},
],
});
let shader = device
.create_shader_module(wgpu::include_wgsl!("shaders/line_layer.wgsl"));
let render_pipeline_layout = device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("LineLayer PLD"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let render_pipeline = device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("LineLayer 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 LineLayer {
async fn draw(&self, _cpu_context: &CpuContext<'_>, _pass: &mut CpuRenderPass) {}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToSvg for LineLayer {
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.source_position_x[i];
let source_y = layer_params.source_position_y[i];
let target_x = layer_params.target_position_x[i];
let target_y = layer_params.target_position_y[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,
);
svg_elements.push(TwoElement::Line(TwoLine {
x1: source_x_px as f64,
y1: (layer_h - source_y_px) as f64,
x2: target_x_px as f64,
y2: (layer_h - target_y_px) as f64,
linewidth: layer_params.line_width 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: "LineLayer",
create_layer: |value, view_params| {
let params: LineLayerParams = serde_json::from_value(value).unwrap();
Box::new(LineLayer::new(view_params.clone(), params))
},
}
}
impl PickableLayer for LineLayer {}