summaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-04-16 10:24:24 +0000
committers-ol <s+removethis@s-ol.nu>2026-05-14 14:46:13 +0000
commit6a67c23a5029aceea1498f892c3424946b152cdc (patch)
tree5d1b2a7283818e8807caaa211d22641b467386ba /src/main.rs
parentinitial commit (diff)
downloadwgsl-view-6a67c23a5029aceea1498f892c3424946b152cdc.tar.gz
wgsl-view-6a67c23a5029aceea1498f892c3424946b152cdc.zip
refactor
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs500
1 files changed, 148 insertions, 352 deletions
diff --git a/src/main.rs b/src/main.rs
index a024ad3..3ce4ffc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,362 +1,117 @@
-use std::sync::Arc;
-use winit::{
- application::ApplicationHandler,
- event::WindowEvent,
- event_loop::EventLoop,
- keyboard::{KeyCode, PhysicalKey},
- window::Window,
-};
-
mod osc;
+mod renderer;
mod uniform;
+mod window;
-const VERTEX_SHADER: &str = "\
-@group(0) @binding(0) var<uniform> _wgsl_resolution: vec2<f32>;
+use std::sync::Arc;
-struct VertexOutput {
- @builtin(position) clip_position: vec4<f32>,
- @location(0) uv: vec2<f32>,
- @location(1) resolution: vec2<f32>,
-};
+use osc::{OscCommand, OscServer};
+use renderer::Renderer;
+use window::{PreviewWindow, ScaleMode};
-@vertex
-fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
- var uv = vec2<f32>(f32(vi % 2u), f32(vi / 2u));
- var out: VertexOutput;
- out.clip_position = vec4<f32>(2.0 * uv - 1.0, 0.0, 1.0);
- out.uv = vec2<f32>(uv.x, 1.0 - uv.y);
- out.resolution = _wgsl_resolution;
- return out;
-}
-";
+use winit::application::ApplicationHandler;
+use winit::event::{ElementState, KeyEvent, WindowEvent};
+use winit::event_loop::EventLoop;
+use winit::keyboard::{Key, NamedKey};
-const DEFAULT_FRAGMENT: &str = "\
-@fragment
-fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
- let check = floor(uv * 10.0);
- return vec4<f32>(vec3<f32>(fract((check.x + check.y) / 2.0)), 1.0);
-}
-";
-
-struct GpuState {
- window: Arc<Window>,
- surface: wgpu::Surface<'static>,
+struct AppState {
device: wgpu::Device,
queue: wgpu::Queue,
- config: wgpu::SurfaceConfiguration,
- surface_format: wgpu::TextureFormat,
- vertex_module: wgpu::ShaderModule,
- vertex_bind_group_layout: wgpu::BindGroupLayout,
- vertex_bind_group: wgpu::BindGroup,
- resolution_buffer: wgpu::Buffer,
- render_pipeline: wgpu::RenderPipeline,
- uniform_cache: uniform::UniformCache,
+ renderer: Renderer,
+ preview: PreviewWindow,
}
struct App {
- gpu: Option<GpuState>,
- osc_server: osc::OscServer,
-}
-
-fn load_shader(
- device: &wgpu::Device,
- surface_format: wgpu::TextureFormat,
- vertex_module: &wgpu::ShaderModule,
- vertex_bgl: &wgpu::BindGroupLayout,
- fragment_source: &str,
- cache: &mut uniform::UniformCache,
-) -> Result<wgpu::RenderPipeline, String> {
- let module = naga::front::wgsl::parse_str(fragment_source)
- .map_err(|e| format!("WGSL parse error: {}", e))?;
-
- naga::valid::Validator::new(
- naga::valid::ValidationFlags::all(),
- naga::valid::Capabilities::all(),
- )
- .validate(&module)
- .map_err(|e| format!("WGSL validation error: {}", e))?;
-
- cache.refresh(module, device);
-
- let fragment_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("fragment_shader"),
- source: wgpu::ShaderSource::Wgsl(fragment_source.into()),
- });
-
- let mut bind_group_layouts: Vec<Option<&wgpu::BindGroupLayout>> = vec![Some(vertex_bgl)];
- if let Some(ref bgl) = cache.bind_group_layout {
- bind_group_layouts.push(Some(bgl));
- }
-
- let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("pipeline_layout"),
- bind_group_layouts: &bind_group_layouts,
- immediate_size: 0,
- });
-
- let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("render_pipeline"),
- layout: Some(&pipeline_layout),
- vertex: wgpu::VertexState {
- module: vertex_module,
- entry_point: Some("vs_main"),
- buffers: &[],
- compilation_options: wgpu::PipelineCompilationOptions::default(),
- },
- fragment: Some(wgpu::FragmentState {
- module: &fragment_module,
- entry_point: Some("fs_main"),
- targets: &[Some(wgpu::ColorTargetState {
- format: surface_format,
- blend: Some(wgpu::BlendState::REPLACE),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- compilation_options: wgpu::PipelineCompilationOptions::default(),
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleStrip,
- 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_mask: None,
- cache: None,
- });
-
- Ok(pipeline)
+ width: u32,
+ height: u32,
+ scale_mode: ScaleMode,
+ osc: OscServer,
+ state: Option<AppState>,
}
impl App {
- fn new() -> Self {
- let osc_server = osc::OscServer::new("0.0.0.0:9000").expect("bind OSC socket");
- Self {
- gpu: None,
- osc_server,
- }
- }
-
- fn init_gpu(&mut self, window: Arc<Window>) {
- let gpu = pollster::block_on(async {
- let size = window.inner_size();
- let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
- backends: wgpu::Backends::VULKAN,
- flags: wgpu::InstanceFlags::default(),
- memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
- backend_options: wgpu::BackendOptions::default(),
- display: None,
- });
-
- let surface = instance
- .create_surface(window.clone())
- .expect("create surface");
-
- let adapter = instance
- .request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::default(),
- compatible_surface: Some(&surface),
- force_fallback_adapter: false,
- })
- .await
- .expect("find adapter");
-
- let (device, queue) = adapter
- .request_device(&wgpu::DeviceDescriptor::default())
- .await
- .expect("create device");
-
- let surface_caps = surface.get_capabilities(&adapter);
- let surface_format = surface_caps
- .formats
- .iter()
- .find(|f| f.is_srgb())
- .copied()
- .unwrap_or(surface_caps.formats[0]);
-
- let config = wgpu::SurfaceConfiguration {
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
- format: surface_format,
- width: size.width.max(1),
- height: size.height.max(1),
- present_mode: wgpu::PresentMode::Fifo,
- alpha_mode: surface_caps.alpha_modes[0],
- view_formats: vec![],
- desired_maximum_frame_latency: 2,
- };
- surface.configure(&device, &config);
-
- let mut cache = uniform::UniformCache::new();
- let vertex_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("vertex_shader"),
- source: wgpu::ShaderSource::Wgsl(VERTEX_SHADER.into()),
- });
-
- let resolution_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("resolution_buffer"),
- size: 8,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let vertex_bind_group_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("vertex_bind_group_layout"),
- entries: &[wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: false,
- min_binding_size: None,
- },
- count: None,
- }],
- });
-
- let vertex_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("vertex_bind_group"),
- layout: &vertex_bind_group_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: resolution_buffer.as_entire_binding(),
- }],
- });
-
- let pipeline = load_shader(
- &device,
- surface_format,
- &vertex_module,
- &vertex_bind_group_layout,
- DEFAULT_FRAGMENT,
- &mut cache,
- )
- .expect("default shader should compile");
-
- GpuState {
- window,
- surface,
- device,
- queue,
- config,
- surface_format,
- vertex_module,
- vertex_bind_group_layout,
- vertex_bind_group,
- resolution_buffer,
- render_pipeline: pipeline,
- uniform_cache: cache,
- }
- });
-
- self.gpu = Some(gpu);
- }
-
fn handle_osc(&mut self) {
- let gpu = self.gpu.as_mut().expect("gpu initialized");
-
- let commands = self.osc_server.poll(&mut gpu.uniform_cache);
- for cmd in commands {
- match cmd {
- osc::OscCommand::Shader(code) => {
- match load_shader(
- &gpu.device,
- gpu.surface_format,
- &gpu.vertex_module,
- &gpu.vertex_bind_group_layout,
- &code,
- &mut gpu.uniform_cache,
- ) {
- Ok(pipeline) => {
- gpu.render_pipeline = pipeline;
- log::info!("shader loaded successfully");
- }
- Err(e) => log::error!("{}", e),
- }
+ let state = self.state.as_mut().expect("gpu not initialized");
+ self.osc.poll(|cmd| match cmd {
+ OscCommand::Shader(source) => {
+ if let Err(e) = state.renderer.load_shader(&state.device, &source) {
+ log::error!("shader error: {}", e);
}
}
- }
- }
-
- fn render(&mut self) {
- let gpu = self.gpu.as_mut().expect("gpu initialized");
-
- gpu.uniform_cache.flush(&gpu.queue);
-
- let output = match gpu.surface.get_current_texture() {
- wgpu::CurrentSurfaceTexture::Success(t)
- | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
- wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
- gpu.surface.configure(&gpu.device, &gpu.config);
- return;
- }
- other => {
- log::error!("surface error: {:?}", other);
- return;
- }
- };
-
- let view = output
- .texture
- .create_view(&wgpu::TextureViewDescriptor::default());
-
- let mut encoder = gpu
- .device
- .create_command_encoder(&wgpu::CommandEncoderDescriptor {
- label: Some("render_encoder"),
- });
-
- {
- let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("render_pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- depth_slice: None,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- occlusion_query_set: None,
- timestamp_writes: None,
- multiview_mask: None,
- });
-
- pass.set_pipeline(&gpu.render_pipeline);
- let resolution = [gpu.config.width as f32, gpu.config.height as f32];
- gpu.queue
- .write_buffer(&gpu.resolution_buffer, 0, bytemuck::cast_slice(&resolution));
- pass.set_bind_group(0, &gpu.vertex_bind_group, &[]);
- if let Some(ref bg) = gpu.uniform_cache.bind_group {
- pass.set_bind_group(1, bg, &[]);
+ OscCommand::Uniform { path, args } => {
+ if let Err(e) = state.renderer.set_uniform(&path, &args) {
+ log::warn!("uniform error: {}", e);
+ }
}
- pass.draw(0..4, 0..1);
- }
-
- gpu.queue.submit(std::iter::once(encoder.finish()));
- output.present();
+ });
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
- if self.gpu.is_none() {
- let window = Arc::new(
- event_loop
- .create_window(Window::default_attributes().with_title("wgsl-view"))
- .expect("create window"),
- );
- self.init_gpu(window);
- }
+ let window = Arc::new(
+ event_loop
+ .create_window(
+ winit::window::Window::default_attributes()
+ .with_title("wgsl-view")
+ .with_inner_size(winit::dpi::LogicalSize::new(self.width, self.height)),
+ )
+ .expect("create window"),
+ );
+
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::VULKAN,
+ flags: wgpu::InstanceFlags::default(),
+ backend_options: wgpu::BackendOptions::default(),
+ memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
+ display: None,
+ });
+
+ let surface = instance
+ .create_surface(window.clone())
+ .expect("create surface");
+
+ let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
+ power_preference: wgpu::PowerPreference::HighPerformance,
+ compatible_surface: Some(&surface),
+ force_fallback_adapter: false,
+ }))
+ .expect("find adapter");
+
+ let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
+ label: Some("device"),
+ required_features: wgpu::Features::empty(),
+ required_limits: wgpu::Limits::default(),
+ ..Default::default()
+ }))
+ .expect("create device");
+
+ let surface_caps = surface.get_capabilities(&adapter);
+ let surface_format = surface_caps
+ .formats
+ .iter()
+ .find(|f| f.is_srgb())
+ .copied()
+ .unwrap_or(surface_caps.formats[0]);
+
+ let renderer = Renderer::new(&device, &queue, self.width, self.height);
+ let preview = PreviewWindow::new(
+ &device,
+ window,
+ surface,
+ surface_format,
+ renderer.canvas_view(),
+ self.width,
+ self.height,
+ self.scale_mode,
+ );
+
+ self.state = Some(AppState {
+ device,
+ queue,
+ renderer,
+ preview,
+ });
}
fn window_event(
@@ -365,44 +120,85 @@ impl ApplicationHandler for App {
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
+ let state = self.state.as_mut().expect("gpu not initialized");
+
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::KeyboardInput {
event:
- winit::event::KeyEvent {
- physical_key: PhysicalKey::Code(KeyCode::Escape),
- state: winit::event::ElementState::Pressed,
+ KeyEvent {
+ logical_key,
+ state: ElementState::Pressed,
..
},
..
- } => event_loop.exit(),
+ } => match logical_key {
+ Key::Named(NamedKey::Escape) => event_loop.exit(),
+ Key::Character(ref c) if c.as_str() == "c" => {
+ state.preview.scale_mode = state.preview.scale_mode.cycle();
+ log::info!("scale mode: {:?}", state.preview.scale_mode);
+ }
+ _ => {}
+ },
WindowEvent::Resized(size) => {
- let gpu = self.gpu.as_mut().expect("gpu initialized");
- gpu.config.width = size.width.max(1);
- gpu.config.height = size.height.max(1);
- gpu.surface.configure(&gpu.device, &gpu.config);
+ state.preview.resize(&state.device, size.width, size.height);
}
WindowEvent::RedrawRequested => {
self.handle_osc();
- self.render();
+ let state = self.state.as_mut().expect("gpu not initialized");
+ state.renderer.render(&state.device, &state.queue);
+ state.preview.draw(&state.device, &state.queue);
}
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
- self.gpu
- .as_ref()
- .expect("gpu initialized")
- .window
- .request_redraw();
+ if let Some(ref state) = self.state {
+ state.preview.window().request_redraw();
+ }
}
}
fn main() {
env_logger::init();
+
+ let mut args = std::env::args().skip(1);
+ let mut width = 1920u32;
+ let mut height = 1080u32;
+ let mut scale_mode = ScaleMode::Contain;
+ let mut port = 9000u16;
+
+ while let Some(arg) = args.next() {
+ match arg.as_str() {
+ "--width" => width = args.next().expect("--width VALUE").parse().expect("u32"),
+ "--height" => height = args.next().expect("--height VALUE").parse().expect("u32"),
+ "--port" => port = args.next().expect("--port VALUE").parse().expect("u16"),
+ "--scale" => {
+ scale_mode = match args.next().expect("--scale VALUE").as_str() {
+ "contain" => ScaleMode::Contain,
+ "cover" => ScaleMode::Cover,
+ "center" => ScaleMode::Center,
+ "natural" => ScaleMode::Natural,
+ s => panic!("unknown scale mode: {}", s),
+ }
+ }
+ other => panic!("unknown argument: {}", other),
+ }
+ }
+
+ let osc = OscServer::new(&format!("0.0.0.0:{}", port)).expect("bind OSC socket");
+
let event_loop = EventLoop::new().expect("create event loop");
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
- let mut app = App::new();
- event_loop.run_app(&mut app).expect("run event loop");
+
+ let mut app = App {
+ width,
+ height,
+ scale_mode,
+ osc,
+ state: None,
+ };
+
+ event_loop.run_app(&mut app).expect("run");
}