diff options
| author | grothedev <grothedev@gmail.com> | 2025-10-01 23:19:48 -0400 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-10-01 23:19:48 -0400 |
| commit | 047761dfae9c0f71c69d8259d93d65eed8f02064 (patch) | |
| tree | 5c492f41e05c8de742bbfdaccca9329c6b3fe434 /src | |
got some sinewaves rendering
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 336 | ||||
| -rw-r--r-- | src/shader.wgsl | 22 |
2 files changed, 358 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0df8566 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,336 @@ +use winit::{ + event::*, + event_loop::EventLoop, +}; +use wgpu::util::DeviceExt; + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + position: [f32; 2], + color: [f32; 3], +} + +impl Vertex { + 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, + }, + ], + } + } +} + +struct State { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize<u32>, + window: std::sync::Arc<winit::window::Window>, + render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + num_vertices: u32, + time: f32, + lines: Vec<Vec<Vertex>>, +} + +impl State { + 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 render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render 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, + }); + + // 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 * 50) as u64, // 50 lines, 100 points each + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + Self { + surface, + device, + queue, + config, + size, + window, + render_pipeline, + vertex_buffer, + num_vertices: 0, + time: 0.0, + lines: Vec::new(), + } + } + + 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); + } + } + + fn update(&mut self) { + self.time += 0.016; // ~60fps + + // Add new line every 10 frames + if (self.time * 60.0) as u32 % 10 == 0 && self.lines.len() < 50 { + let mut line = Vec::new(); + let phase = self.time; + let freq = 2.0 + (self.time * 0.5).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; + + // Color gradient based on time + let hue = (self.time * 0.1) % 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 (idx, line) in self.lines.iter_mut().enumerate() { + let scroll_offset = idx as f32 * 0.04; + 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) + }); + + // Update vertex buffer + let mut all_vertices = Vec::new(); + for line in &self.lines { + all_vertices.extend_from_slice(line); + } + + if !all_vertices.is_empty() { + self.queue.write_buffer( + &self.vertex_buffer, + 0, + bytemuck::cast_slice(&all_vertices), + ); + self.num_vertices = all_vertices.len() as u32; + } + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + 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"), + }); + + { + 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_pass.set_pipeline(&self.render_pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + + // Draw each line separately for proper line strip rendering + let points_per_line = 100; + for i in 0..self.lines.len() { + let start = (i * points_per_line) as u32; + let end = start + points_per_line as u32; + render_pass.draw(start..end, 0..1); + } + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + +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::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/shader.wgsl b/src/shader.wgsl new file mode 100644 index 0000000..6655301 --- /dev/null +++ b/src/shader.wgsl @@ -0,0 +1,22 @@ +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); +} |
