use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use super::errors::*;
use super::performance::*;
#[cfg(feature = "webgpu")]
use wgpu::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ChartType {
Point,
Line,
Bar,
Area,
Text,
Rect,
Scatter,
BoxPlot,
Violin,
Heatmap,
Histogram,
Density,
Contour,
Radar,
Sankey,
Treemap,
Composite,
Point3D,
Surface3D,
Choropleth,
NetworkGraph,
DotMap,
FlowMap,
}
impl ChartType {
pub fn from_spec(spec: &crate::chart::ChartSpec) -> Self {
match spec.mark {
crate::chart::MarkType::Point { .. } => ChartType::Point,
crate::chart::MarkType::Line { .. } => ChartType::Line,
crate::chart::MarkType::Bar { .. } => ChartType::Bar,
crate::chart::MarkType::Area { .. } => ChartType::Area,
crate::chart::MarkType::Text { .. } => ChartType::Text,
crate::chart::MarkType::Rect { .. } => ChartType::Rect,
crate::chart::MarkType::Scatter { .. } => ChartType::Scatter,
crate::chart::MarkType::BoxPlot { .. } => ChartType::BoxPlot,
crate::chart::MarkType::Violin { .. } => ChartType::Violin,
crate::chart::MarkType::Heatmap { .. } => ChartType::Heatmap,
crate::chart::MarkType::Histogram { .. } => ChartType::Histogram,
crate::chart::MarkType::Density { .. } => ChartType::Density,
crate::chart::MarkType::Contour { .. } => ChartType::Contour,
crate::chart::MarkType::Radar { .. } => ChartType::Radar,
crate::chart::MarkType::Sankey { .. } => ChartType::Sankey,
crate::chart::MarkType::Treemap { .. } => ChartType::Treemap,
crate::chart::MarkType::Composite(_) => ChartType::Composite,
crate::chart::MarkType::Point3D { .. } => ChartType::Point3D,
crate::chart::MarkType::Surface3D { .. } => ChartType::Surface3D,
crate::chart::MarkType::Choropleth { .. } => ChartType::Choropleth,
crate::chart::MarkType::NetworkGraph { .. } => ChartType::NetworkGraph,
crate::chart::MarkType::DotMap { .. } => ChartType::DotMap,
crate::chart::MarkType::FlowMap { .. } => ChartType::FlowMap,
}
}
pub fn name(&self) -> &'static str {
match self {
ChartType::Point => "point",
ChartType::Line => "line",
ChartType::Bar => "bar",
ChartType::Area => "area",
ChartType::Text => "text",
ChartType::Rect => "rect",
ChartType::Scatter => "scatter",
ChartType::BoxPlot => "boxplot",
ChartType::Violin => "violin",
ChartType::Heatmap => "heatmap",
ChartType::Histogram => "histogram",
ChartType::Density => "density",
ChartType::Contour => "contour",
ChartType::Radar => "radar",
ChartType::Sankey => "sankey",
ChartType::Treemap => "treemap",
ChartType::Composite => "composite",
ChartType::Point3D => "point3d",
ChartType::Surface3D => "surface3d",
ChartType::Choropleth => "choropleth",
ChartType::NetworkGraph => "networkgraph",
ChartType::DotMap => "dotmap",
ChartType::FlowMap => "flowmap",
}
}
}
pub struct Renderer {
backend: RenderBackend,
pipelines: HashMap<ChartType, RenderPipeline>,
frame_timer: FrameTimer,
quality_manager: AdaptiveQualityManager,
}
impl Renderer {
pub async fn new() -> Result<Self, RenderError> {
let backend = RenderBackend::create_optimal().await?;
let frame_timer = FrameTimer::new();
let quality_manager = AdaptiveQualityManager::new();
Ok(Self {
backend,
pipelines: HashMap::new(),
frame_timer,
quality_manager,
})
}
pub fn render(&mut self, spec: &crate::chart::ChartSpec) -> RenderStats {
let start_time = Instant::now();
let quality_level = self.frame_timer.suggest_quality();
let _render_config = self.quality_manager.get_render_config(quality_level);
let chart_type = ChartType::from_spec(spec);
let _pipeline = self.get_or_create_pipeline(chart_type);
let frame_time = start_time.elapsed();
RenderStats {
frame_time,
triangles_rendered: 1000, draw_calls: 1,
memory_used: 1024 * 1024, gpu_utilization: 0.5,
cache_hit_rate: 0.95,
}
}
fn get_or_create_pipeline(&mut self, chart_type: ChartType) -> &RenderPipeline {
if !self.pipelines.contains_key(&chart_type) {
let pipeline = RenderPipeline::new(&self.backend, chart_type.clone());
self.pipelines.insert(chart_type.clone(), pipeline);
}
self.pipelines.get(&chart_type).unwrap()
}
pub fn get_backend(&self) -> &RenderBackend {
&self.backend
}
pub fn get_performance_stats(&self) -> RenderStats {
RenderStats {
frame_time: self.frame_timer.get_average_frame_time(),
triangles_rendered: 0,
draw_calls: 0,
memory_used: 0,
gpu_utilization: 0.0,
cache_hit_rate: 0.0,
}
}
}
pub struct RenderPipeline {
#[allow(dead_code)]
chart_type: ChartType,
#[allow(dead_code)]
webgpu_pipeline: Option<Arc<wgpu::RenderPipeline>>,
#[allow(dead_code)]
bind_group_layout: Option<Arc<BindGroupLayout>>,
#[allow(dead_code)]
vertex_buffer: Option<Arc<Buffer>>,
#[allow(dead_code)]
index_buffer: Option<Arc<Buffer>>,
#[allow(dead_code)]
uniform_buffer: Option<Arc<Buffer>>,
#[allow(dead_code)]
shader_module: Option<Arc<ShaderModule>>,
}
impl RenderPipeline {
pub fn new(backend: &RenderBackend, chart_type: ChartType) -> Self {
match backend {
RenderBackend::WebGPU { device, .. } => {
if let Some(device) = device {
Self::create_webgpu_pipeline(device, chart_type)
} else {
Self::fallback_pipeline(chart_type)
}
}
_ => Self::fallback_pipeline(chart_type),
}
}
#[cfg(feature = "webgpu")]
fn create_webgpu_pipeline(device: &Arc<Device>, chart_type: ChartType) -> Self {
let shader_source = Self::get_shader_source(&chart_type);
let shader_module = device.create_shader_module(ShaderModuleDescriptor {
label: Some(&format!("{}_shader", chart_type.name())),
source: ShaderSource::Wgsl(shader_source.into()),
});
let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some(&format!("{}_bind_group_layout", chart_type.name())),
entries: &[],
});
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some(&format!("{}_pipeline_layout", chart_type.name())),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some(&format!("{}_render_pipeline", chart_type.name())),
layout: Some(&pipeline_layout),
vertex: VertexState {
module: &shader_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(FragmentState {
module: &shader_module,
entry_point: Some("fs_main"),
targets: &[Some(ColorTargetState {
format: TextureFormat::Bgra8UnormSrgb,
blend: Some(BlendState::REPLACE),
write_mask: ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
polygon_mode: PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Self {
chart_type,
webgpu_pipeline: Some(Arc::new(render_pipeline)),
bind_group_layout: Some(Arc::new(bind_group_layout)),
vertex_buffer: None,
index_buffer: None,
uniform_buffer: None,
shader_module: Some(Arc::new(shader_module)),
}
}
fn fallback_pipeline(chart_type: ChartType) -> Self {
Self {
chart_type,
webgpu_pipeline: None,
bind_group_layout: None,
vertex_buffer: None,
index_buffer: None,
uniform_buffer: None,
shader_module: None,
}
}
fn get_shader_source(chart_type: &ChartType) -> &'static str {
match chart_type {
ChartType::Point => include_str!("shaders/point.wgsl"),
ChartType::Line => include_str!("shaders/line.wgsl"),
ChartType::Bar => include_str!("shaders/bar.wgsl"),
ChartType::Area => include_str!("shaders/area.wgsl"),
_ => include_str!("shaders/default.wgsl"),
}
}
}