summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2025-10-01 23:19:48 -0400
committergrothedev <grothedev@gmail.com>2025-10-01 23:19:48 -0400
commit047761dfae9c0f71c69d8259d93d65eed8f02064 (patch)
tree5c492f41e05c8de742bbfdaccca9329c6b3fe434 /src
got some sinewaves rendering
Diffstat (limited to 'src')
-rw-r--r--src/main.rs336
-rw-r--r--src/shader.wgsl22
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);
+}