diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/graph.rs | 122 | ||||
| -rw-r--r-- | src/main.rs | 89 | ||||
| -rw-r--r-- | src/metrics.rs | 290 | ||||
| -rw-r--r-- | src/renderer.rs | 682 | ||||
| -rw-r--r-- | src/shader.wgsl | 22 | ||||
| -rw-r--r-- | src/vertex.rs | 27 |
6 files changed, 0 insertions, 1232 deletions
diff --git a/src/graph.rs b/src/graph.rs deleted file mode 100644 index 7426bee..0000000 --- a/src/graph.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::vertex::Vertex; - -#[derive(Clone)] -pub struct SubView { - pub x: f32, - pub y: f32, - pub width: f32, - pub height: f32, -} - -pub struct GraphView { - pub viewport: SubView, - pub lines: Vec<Vec<Vertex>>, - pub show_grid: bool, - pub title: String, - pub x_axis_label: String, - pub y_axis_label: String, - pub legend_items: Vec<LegendItem>, -} - -#[derive(Clone)] -pub struct LegendItem { - pub label: String, - pub color: [f32; 3], -} - -impl GraphView { - pub fn new(viewport: SubView, title: String, x_axis_label: String, y_axis_label: String) -> Self { - Self { - viewport, - lines: Vec::new(), - show_grid: true, - title, - x_axis_label, - y_axis_label, - legend_items: Vec::new(), - } - } - - pub fn add_legend_item(&mut self, label: String, color: [f32; 3]) { - self.legend_items.push(LegendItem { label, color }); - } - - pub fn generate_grid_lines(&self) -> Vec<Vertex> { - let mut vertices = Vec::new(); - let grid_color = [0.3, 0.7, 0.9]; - - // Vertical grid lines (10 divisions) - for i in 0..=10 { - let x = -1.0 + (i as f32 / 10.0) * 2.0; - vertices.push(Vertex { position: [x, -1.0], color: grid_color }); - vertices.push(Vertex { position: [x, 1.0], color: grid_color }); - } - - // Horizontal grid lines (10 divisions) - for i in 0..=10 { - let y = -1.0 + (i as f32 / 10.0) * 2.0; - vertices.push(Vertex { position: [-1.0, y], color: grid_color }); - vertices.push(Vertex { position: [1.0, y], color: grid_color }); - } - - vertices - } - - pub fn generate_border(&self) -> Vec<Vertex> { - let border_color = [0.6, 0.7, 0.7]; - vec![ - // Top border - Vertex { position: [-1.0, 1.0], color: border_color }, - Vertex { position: [1.0, 1.0], color: border_color }, - // Right border - Vertex { position: [1.0, 1.0], color: border_color }, - Vertex { position: [1.0, -1.0], color: border_color }, - // Bottom border - Vertex { position: [1.0, -1.0], color: border_color }, - Vertex { position: [-1.0, -1.0], color: border_color }, - // Left border - Vertex { position: [-1.0, -1.0], color: border_color }, - Vertex { position: [-1.0, 1.0], color: border_color }, - ] - } - - pub fn update(&mut self, time: f32, graph_idx: usize) { - // Add new line every 10 frames - if (time * 60.0) as u32 % 10 == 0 && self.lines.len() < 50 { - let mut line = Vec::new(); - let phase = time + (graph_idx as f32 * 2.0); - let freq = 2.0 + (time * 0.5 + graph_idx as f32).sin() * 1.0; - - for i in 0..100 { - let x = (i as f32 / 100.0) * 2.0 - 1.0; - let y = ((i as f32) * 0.1 * freq + phase).sin() * 0.3; - - // Different color per graph - let hue = (time * 0.1 + graph_idx as f32 * 0.5) % 1.0; - let color = [ - (hue * 6.0).sin().abs(), - ((hue + 0.33) * 6.0).sin().abs(), - ((hue + 0.66) * 6.0).sin().abs(), - ]; - - line.push(Vertex { - position: [x, y], - color, - }); - } - self.lines.push(line); - } - - // Scroll lines down - for line in self.lines.iter_mut() { - for vertex in line.iter_mut() { - vertex.position[1] -= 0.01; - } - } - - // Remove lines that have scrolled off screen - self.lines.retain(|line| { - line.first().map(|v| v.position[1] > -1.1).unwrap_or(false) - }); - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 917515d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,89 +0,0 @@ -mod vertex; -mod graph; -mod renderer; -mod metrics; - -use winit::{ - event::*, - event_loop::EventLoop, - keyboard::{KeyCode, PhysicalKey}, -}; - -use renderer::State; - -fn main() { - env_logger::init(); - let event_loop = EventLoop::new().unwrap(); - let window = std::sync::Arc::new( - event_loop - .create_window( - winit::window::Window::default_attributes() - .with_title("TimePlot - Waterfall Display") - .with_inner_size(winit::dpi::LogicalSize::new(1280, 720)) - ) - .unwrap(), - ); - - let mut state = pollster::block_on(State::new(window.clone())); - - event_loop - .run(move |event, control_flow| match event { - Event::WindowEvent { - ref event, - window_id, - } if window_id == state.window.id() => match event { - WindowEvent::CloseRequested => control_flow.exit(), - WindowEvent::Resized(physical_size) => { - state.resize(*physical_size); - } - WindowEvent::KeyboardInput { - event: key_event, - .. - } => { - if key_event.state == winit::event::ElementState::Pressed { - if let PhysicalKey::Code(keycode) = key_event.physical_key { - match keycode { - KeyCode::KeyG => { - println!("Grid toggle pressed"); - // Toggle grid for all graphs - for graph in &mut state.graphs { - graph.show_grid = !graph.show_grid; - println!("Grid now: {}", graph.show_grid); - } - } - KeyCode::KeyM => { - // Toggle metrics display - state.toggle_metrics(); - } - KeyCode::KeyE => { - // Export metrics to CSV - if let Err(e) = state.export_metrics("metrics.csv") { - eprintln!("Failed to export metrics: {}", e); - } else { - println!("Metrics exported to metrics.csv"); - } - } - KeyCode::Escape => control_flow.exit(), - _ => {} - } - } - } - } - WindowEvent::RedrawRequested => { - state.update(); - match state.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), - Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(), - Err(e) => eprintln!("{:?}", e), - } - } - _ => {} - }, - Event::AboutToWait => { - state.window.request_redraw(); - } - _ => {} - }) - .unwrap(); -} diff --git a/src/metrics.rs b/src/metrics.rs deleted file mode 100644 index 369aff8..0000000 --- a/src/metrics.rs +++ /dev/null @@ -1,290 +0,0 @@ -use std::time::{Duration, Instant}; -use std::collections::VecDeque; - -/// Rolling average calculator for smooth metric display -pub struct RollingAverage { - values: VecDeque<f64>, - capacity: usize, - sum: f64, -} - -impl RollingAverage { - pub fn new(capacity: usize) -> Self { - Self { - values: VecDeque::with_capacity(capacity), - capacity, - sum: 0.0, - } - } - - pub fn push(&mut self, value: f64) { - if self.values.len() >= self.capacity { - if let Some(old) = self.values.pop_front() { - self.sum -= old; - } - } - self.values.push_back(value); - self.sum += value; - } - - pub fn average(&self) -> f64 { - if self.values.is_empty() { - 0.0 - } else { - self.sum / self.values.len() as f64 - } - } - - pub fn min(&self) -> f64 { - self.values.iter().copied().fold(f64::INFINITY, f64::min) - } - - pub fn max(&self) -> f64 { - self.values.iter().copied().fold(f64::NEG_INFINITY, f64::max) - } - - pub fn latest(&self) -> f64 { - self.values.back().copied().unwrap_or(0.0) - } -} - -/// Frame timing breakdown -#[derive(Debug, Clone)] -pub struct FrameTiming { - pub total_ms: f64, - pub update_ms: f64, - pub render_ms: f64, - pub vertex_count: usize, - pub line_count: usize, -} - -/// Performance metrics collector with rolling averages -pub struct PerformanceMetrics { - // Rolling averages (default: 60 frames) - frame_time: RollingAverage, - update_time: RollingAverage, - render_time: RollingAverage, - vertex_count: RollingAverage, - line_count: RollingAverage, - - // Session-wide statistics - pub total_frames: u64, - session_start: Instant, - - // Current frame timing - frame_start: Option<Instant>, - update_start: Option<Instant>, - render_start: Option<Instant>, - - // Historical data for export - history: VecDeque<FrameTiming>, - history_capacity: usize, -} - -impl PerformanceMetrics { - pub fn new(rolling_window: usize, history_capacity: usize) -> Self { - Self { - frame_time: RollingAverage::new(rolling_window), - update_time: RollingAverage::new(rolling_window), - render_time: RollingAverage::new(rolling_window), - vertex_count: RollingAverage::new(rolling_window), - line_count: RollingAverage::new(rolling_window), - total_frames: 0, - session_start: Instant::now(), - frame_start: None, - update_start: None, - render_start: None, - history: VecDeque::with_capacity(history_capacity), - history_capacity, - } - } - - // Frame timing markers - pub fn begin_frame(&mut self) { - self.frame_start = Some(Instant::now()); - } - - pub fn begin_update(&mut self) { - self.update_start = Some(Instant::now()); - } - - pub fn end_update(&mut self) -> f64 { - if let Some(start) = self.update_start.take() { - let duration = start.elapsed(); - duration.as_secs_f64() * 1000.0 - } else { - 0.0 - } - } - - pub fn begin_render(&mut self) { - self.render_start = Some(Instant::now()); - } - - pub fn end_render(&mut self) -> f64 { - if let Some(start) = self.render_start.take() { - let duration = start.elapsed(); - duration.as_secs_f64() * 1000.0 - } else { - 0.0 - } - } - - pub fn end_frame(&mut self, update_ms: f64, render_ms: f64, vertex_count: usize, line_count: usize) { - if let Some(start) = self.frame_start.take() { - let total_ms = start.elapsed().as_secs_f64() * 1000.0; - - // Update rolling averages - self.frame_time.push(total_ms); - self.update_time.push(update_ms); - self.render_time.push(render_ms); - self.vertex_count.push(vertex_count as f64); - self.line_count.push(line_count as f64); - - // Record to history - let timing = FrameTiming { - total_ms, - update_ms, - render_ms, - vertex_count, - line_count, - }; - - if self.history.len() >= self.history_capacity { - self.history.pop_front(); - } - self.history.push_back(timing); - - self.total_frames += 1; - } - } - - // Getters for current metrics - pub fn fps(&self) -> f64 { - let avg_frame_time = self.frame_time.average(); - if avg_frame_time > 0.0 { - 1000.0 / avg_frame_time - } else { - 0.0 - } - } - - pub fn avg_frame_time_ms(&self) -> f64 { - self.frame_time.average() - } - - pub fn avg_update_time_ms(&self) -> f64 { - self.update_time.average() - } - - pub fn avg_render_time_ms(&self) -> f64 { - self.render_time.average() - } - - pub fn avg_vertex_count(&self) -> f64 { - self.vertex_count.average() - } - - pub fn avg_line_count(&self) -> f64 { - self.line_count.average() - } - - pub fn min_fps(&self) -> f64 { - let max_frame_time = self.frame_time.max(); - if max_frame_time > 0.0 && max_frame_time.is_finite() { - 1000.0 / max_frame_time - } else { - 0.0 - } - } - - pub fn max_fps(&self) -> f64 { - let min_frame_time = self.frame_time.min(); - if min_frame_time > 0.0 && min_frame_time.is_finite() { - 1000.0 / min_frame_time - } else { - 0.0 - } - } - - pub fn session_duration(&self) -> Duration { - self.session_start.elapsed() - } - - // Export functionality - pub fn export_to_csv(&self, path: &str) -> std::io::Result<()> { - use std::io::Write; - let mut file = std::fs::File::create(path)?; - - // Write header - writeln!(file, "frame,total_ms,update_ms,render_ms,vertex_count,line_count,fps")?; - - // Write data - for (i, timing) in self.history.iter().enumerate() { - let fps = if timing.total_ms > 0.0 { - 1000.0 / timing.total_ms - } else { - 0.0 - }; - writeln!( - file, - "{},{},{},{},{},{},{}", - i, - timing.total_ms, - timing.update_ms, - timing.render_ms, - timing.vertex_count, - timing.line_count, - fps - )?; - } - - Ok(()) - } - - pub fn format_summary(&self) -> String { - format!( - "FPS: {:.1} (min: {:.1}, max: {:.1}) | Frame: {:.2}ms | Update: {:.2}ms | Render: {:.2}ms | Vertices: {:.0} | Lines: {:.0}", - self.fps(), - self.min_fps(), - self.max_fps(), - self.avg_frame_time_ms(), - self.avg_update_time_ms(), - self.avg_render_time_ms(), - self.avg_vertex_count(), - self.avg_line_count() - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_rolling_average() { - let mut avg = RollingAverage::new(3); - avg.push(10.0); - avg.push(20.0); - avg.push(30.0); - assert_eq!(avg.average(), 20.0); - - avg.push(40.0); - assert_eq!(avg.average(), 30.0); // (20 + 30 + 40) / 3 - } - - #[test] - fn test_metrics_lifecycle() { - let mut metrics = PerformanceMetrics::new(60, 1000); - - metrics.begin_frame(); - metrics.begin_update(); - let update_ms = metrics.end_update(); - metrics.begin_render(); - let render_ms = metrics.end_render(); - metrics.end_frame(update_ms, render_ms, 1000, 10); - - assert_eq!(metrics.total_frames, 1); - assert!(metrics.fps() > 0.0); - } -} diff --git a/src/renderer.rs b/src/renderer.rs deleted file mode 100644 index 2b9c0d0..0000000 --- a/src/renderer.rs +++ /dev/null @@ -1,682 +0,0 @@ -use glyphon::{ - Attrs, Buffer, Color as TextColor, Family, FontSystem, Metrics, Resolution, Shaping, - SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport -}; -use crate::vertex::Vertex; -use crate::graph::{GraphView, SubView}; -use crate::metrics::PerformanceMetrics; - -pub struct State { - surface: wgpu::Surface<'static>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - pub size: winit::dpi::PhysicalSize<u32>, - pub window: std::sync::Arc<winit::window::Window>, - line_pipeline: wgpu::RenderPipeline, - line_list_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - time: f32, - pub graphs: Vec<GraphView>, - font_system: FontSystem, - swash_cache: SwashCache, - text_atlas: TextAtlas, - text_renderer: TextRenderer, - viewport: Viewport, - metrics: PerformanceMetrics, - show_metrics: bool, -} - -impl State { - pub async fn new(window: std::sync::Arc<winit::window::Window>) -> Self { - let size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::VULKAN, - ..Default::default() - }); - - let surface = instance.create_surface(window.clone()).unwrap(); - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - label: None, - memory_hints: Default::default(), - }, - None, - ) - .await - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - surface.configure(&device, &config); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Waterfall Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), - }); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Line Strip Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::LineStrip, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let line_list_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Line List Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::LineList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - // Create initial empty buffer (will be updated each frame) - let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Vertex Buffer"), - size: (std::mem::size_of::<Vertex>() * 100 * 100) as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - // Create 2 graph views side-by-side with header area - // Reserve top 60px for header - let header_height = 60.0 / size.height as f32; - let graph_area_height = 1.0 - header_height; - - let mut graphs = vec![ - GraphView::new( - SubView { - x: 0.0, - y: header_height, - width: 0.5, - height: graph_area_height - }, - "Frequency vs Time".to_string(), - "Time (s)".to_string(), - "Frequency (Hz)".to_string() - ), - GraphView::new( - SubView { - x: 0.5, - y: header_height, - width: 0.5, - height: graph_area_height - }, - "Position vs Time".to_string(), - "Time (s)".to_string(), - "Position (m)".to_string() - ), - ]; - - // Add legend items - graphs[0].add_legend_item("Signal A".to_string(), [1.0, 0.3, 0.3]); - graphs[0].add_legend_item("Signal B".to_string(), [0.3, 1.0, 0.3]); - graphs[1].add_legend_item("Object 1".to_string(), [0.3, 0.6, 1.0]); - graphs[1].add_legend_item("Object 2".to_string(), [1.0, 0.8, 0.3]); - - // Initialize text rendering - let font_system = FontSystem::new(); - let swash_cache = SwashCache::new(); - let cache = glyphon::Cache::new(&device); - let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format); - let text_renderer = TextRenderer::new( - &mut text_atlas, - &device, - wgpu::MultisampleState::default(), - None, - ); - - let mut viewport = Viewport::new(&device, &cache); - - // Initialize viewport with current resolution - viewport.update( - &queue, - Resolution { - width: size.width, - height: size.height, - }, - ); - - Self { - surface, - device, - queue, - config, - size, - window, - line_pipeline, - line_list_pipeline, - vertex_buffer, - time: 0.0, - graphs, - font_system, - swash_cache, - text_atlas, - text_renderer, - viewport, - metrics: PerformanceMetrics::new(60, 10000), // 60 frame rolling avg, 10k frame history - show_metrics: true, // Show metrics by default - } - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - - // Update text viewport resolution - self.viewport.update( - &self.queue, - Resolution { - width: new_size.width, - height: new_size.height, - }, - ); - } - } - - pub fn update(&mut self) { - self.metrics.begin_update(); - - self.time += 0.016; // ~60fps - - // Update each graph independently - for (graph_idx, graph) in self.graphs.iter_mut().enumerate() { - graph.update(self.time, graph_idx); - } - } - - pub fn toggle_metrics(&mut self) { - self.show_metrics = !self.show_metrics; - println!("Metrics display: {}", if self.show_metrics { "ON" } else { "OFF" }); - } - - pub fn export_metrics(&self, path: &str) -> std::io::Result<()> { - self.metrics.export_to_csv(path) - } - - pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.metrics.begin_frame(); - - // End update timing from previous update() call - let update_ms = self.metrics.end_update(); - - self.metrics.begin_render(); - - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - // Collect all vertex data for all graphs - struct DrawData { - viewport: SubView, - border_offset: usize, - border_count: usize, - grid_offset: usize, - grid_count: usize, - show_grid: bool, - lines_offset: usize, - lines_count: usize, - num_lines: usize, - } - - let mut all_vertices = Vec::new(); - let mut draw_data = Vec::new(); - let mut total_line_count = 0; - - for graph in &self.graphs { - total_line_count += graph.lines.len(); - let border_vertices = graph.generate_border(); - let border_offset = all_vertices.len(); - all_vertices.extend_from_slice(&border_vertices); - let border_count = border_vertices.len(); - - let grid_offset = all_vertices.len(); - let grid_vertices = graph.generate_grid_lines(); - all_vertices.extend_from_slice(&grid_vertices); - let grid_count = grid_vertices.len(); - - let lines_offset = all_vertices.len(); - let mut line_vertices = Vec::new(); - for line in &graph.lines { - line_vertices.extend_from_slice(line); - } - all_vertices.extend_from_slice(&line_vertices); - let lines_count = line_vertices.len(); - - draw_data.push(DrawData { - viewport: graph.viewport.clone(), - border_offset, - border_count, - grid_offset, - grid_count, - show_grid: graph.show_grid, - lines_offset, - lines_count, - num_lines: graph.lines.len(), - }); - } - - // Write all vertices at once - if !all_vertices.is_empty() { - self.queue.write_buffer( - &self.vertex_buffer, - 0, - bytemuck::cast_slice(&all_vertices), - ); - } - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.1, - b: 0.15, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - // Render each graph view - for data in &draw_data { - // Set viewport for this graph - render_pass.set_viewport( - data.viewport.x * self.size.width as f32, - data.viewport.y * self.size.height as f32, - data.viewport.width * self.size.width as f32, - data.viewport.height * self.size.height as f32, - 0.0, - 1.0, - ); - - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - - // Draw border - render_pass.set_pipeline(&self.line_list_pipeline); - render_pass.draw( - data.border_offset as u32..(data.border_offset + data.border_count) as u32, - 0..1, - ); - - // Draw grid if enabled - if data.show_grid { - render_pass.set_pipeline(&self.line_list_pipeline); - render_pass.draw( - data.grid_offset as u32..(data.grid_offset + data.grid_count) as u32, - 0..1, - ); - } - - // Draw waterfall lines - if data.lines_count > 0 { - render_pass.set_pipeline(&self.line_pipeline); - let points_per_line = 100; - for i in 0..data.num_lines { - let start = (data.lines_offset + i * points_per_line) as u32; - let end = start + points_per_line as u32; - render_pass.draw(start..end, 0..1); - } - } - } - } - - // Render text (header and labels) - self.viewport.update( - &self.queue, - Resolution { - width: self.size.width, - height: self.size.height, - }, - ); - - let mut text_areas = Vec::new(); - - // Main header - let mut header_buffer = Buffer::new(&mut self.font_system, Metrics::new(24.0, 32.0)); - header_buffer.set_size(&mut self.font_system, Some(500.0), Some(40.0)); - header_buffer.set_text( - &mut self.font_system, - "TimePlot - Waterfall Display", - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - - // Performance metrics (if enabled) - let mut metrics_buffer = Buffer::new(&mut self.font_system, Metrics::new(11.0, 14.0)); - if self.show_metrics { - let metrics_text = self.metrics.format_summary(); - metrics_buffer.set_size(&mut self.font_system, Some(self.size.width as f32 - 520.0), Some(40.0)); - metrics_buffer.set_text( - &mut self.font_system, - &metrics_text, - Attrs::new().family(Family::Monospace), - Shaping::Advanced, - ); - } - - // Graph titles and labels - create all buffers first - let mut graph_buffers = Vec::new(); - let mut x_axis_buffers = Vec::new(); - let mut y_axis_buffers = Vec::new(); - let mut legend_buffers = Vec::new(); - - for graph in &self.graphs { - let width = graph.viewport.width * self.size.width as f32; - let height = graph.viewport.height * self.size.height as f32; - - // Title - let mut title_buffer = Buffer::new(&mut self.font_system, Metrics::new(18.0, 24.0)); - title_buffer.set_size(&mut self.font_system, Some(width), Some(30.0)); - title_buffer.set_text( - &mut self.font_system, - &graph.title, - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - graph_buffers.push(title_buffer); - - // X-axis label - let mut x_buffer = Buffer::new(&mut self.font_system, Metrics::new(14.0, 18.0)); - x_buffer.set_size(&mut self.font_system, Some(width), Some(20.0)); - x_buffer.set_text( - &mut self.font_system, - &graph.x_axis_label, - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - x_axis_buffers.push(x_buffer); - - // Y-axis label - let mut y_buffer = Buffer::new(&mut self.font_system, Metrics::new(14.0, 18.0)); - y_buffer.set_size(&mut self.font_system, Some(height * 0.5), Some(20.0)); - y_buffer.set_text( - &mut self.font_system, - &graph.y_axis_label, - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - y_axis_buffers.push(y_buffer); - - // Legend - let mut legend_text = String::new(); - for (i, item) in graph.legend_items.iter().enumerate() { - if i > 0 { legend_text.push_str(" "); } - legend_text.push_str(&format!("● {}", item.label)); - } - let mut legend_buffer = Buffer::new(&mut self.font_system, Metrics::new(12.0, 16.0)); - legend_buffer.set_size(&mut self.font_system, Some(width * 0.8), Some(20.0)); - legend_buffer.set_text( - &mut self.font_system, - &legend_text, - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - legend_buffers.push(legend_buffer); - } - - // Now create text areas with references to the buffers - text_areas.push(TextArea { - buffer: &header_buffer, - left: 10.0, - top: 15.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: 500, - bottom: 40, - }, - default_color: TextColor::rgb(255, 255, 255), - custom_glyphs: &[] - }); - - // Metrics display - if self.show_metrics { - text_areas.push(TextArea { - buffer: &metrics_buffer, - left: 520.0, - top: 18.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: (self.size.width as i32 - 520).max(0), - bottom: 40, - }, - default_color: TextColor::rgb(100, 255, 100), - custom_glyphs: &[] - }); - } - - for (i, graph) in self.graphs.iter().enumerate() { - let x_offset = graph.viewport.x * self.size.width as f32; - let y_offset = graph.viewport.y * self.size.height as f32; - let width = graph.viewport.width * self.size.width as f32; - let height = graph.viewport.height * self.size.height as f32; - - // Graph title - text_areas.push(TextArea { - buffer: &graph_buffers[i], - left: x_offset + 10.0, - top: y_offset + 5.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: width as i32, - bottom: 30, - }, - default_color: TextColor::rgb(230, 230, 230), - custom_glyphs: &[] - }); - - // X-axis label (bottom center) - text_areas.push(TextArea { - buffer: &x_axis_buffers[i], - left: x_offset + width * 0.5 - 50.0, - top: y_offset + height - 20.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: 200, - bottom: 20, - }, - default_color: TextColor::rgb(200, 200, 200), - custom_glyphs: &[] - }); - - // Y-axis label (left center, rotated appearance via positioning) - text_areas.push(TextArea { - buffer: &y_axis_buffers[i], - left: x_offset + 5.0, - top: y_offset + height * 0.3, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: 150, - bottom: 20, - }, - default_color: TextColor::rgb(200, 200, 200), - custom_glyphs: &[] - }); - - // Legend (top right) - text_areas.push(TextArea { - buffer: &legend_buffers[i], - left: x_offset + width * 0.3, - top: y_offset + 30.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: (width * 0.7) as i32, - bottom: 20, - }, - default_color: TextColor::rgb(220, 220, 220), - custom_glyphs: &[] - }); - } - - self.text_renderer - .prepare( - &self.device, - &self.queue, - &mut self.font_system, - &mut self.text_atlas, - &self.viewport, - text_areas, - &mut self.swash_cache, - ) - .expect("Failed to prepare text"); - - { - let mut text_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Text Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - self.text_renderer - .render(&self.text_atlas, &self.viewport, &mut text_pass) - .expect("Failed to render text"); - } - - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - // Finalize metrics - let render_ms = self.metrics.end_render(); - let vertex_count = all_vertices.len(); - self.metrics.end_frame(update_ms, render_ms, vertex_count, total_line_count); - - Ok(()) - } -} diff --git a/src/shader.wgsl b/src/shader.wgsl deleted file mode 100644 index 6655301..0000000 --- a/src/shader.wgsl +++ /dev/null @@ -1,22 +0,0 @@ -struct VertexInput { - @location(0) position: vec2<f32>, - @location(1) color: vec3<f32>, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4<f32>, - @location(0) color: vec3<f32>, -} - -@vertex -fn vs_main(in: VertexInput) -> VertexOutput { - var out: VertexOutput; - out.clip_position = vec4<f32>(in.position, 0.0, 1.0); - out.color = in.color; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { - return vec4<f32>(in.color, 1.0); -} diff --git a/src/vertex.rs b/src/vertex.rs deleted file mode 100644 index 8a25c30..0000000 --- a/src/vertex.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Vertex { - pub position: [f32; 2], - pub color: [f32; 3], -} - -impl Vertex { - pub fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} |
