diff options
Diffstat (limited to 'src/window.rs')
| -rw-r--r-- | src/window.rs | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..e385ecb --- /dev/null +++ b/src/window.rs @@ -0,0 +1,245 @@ +use std::sync::Arc; + +#[derive(Clone, Copy, Debug)] +pub enum ScaleMode { + Contain, + Cover, + Center, + Natural, +} + +impl ScaleMode { + pub fn cycle(self) -> Self { + match self { + Self::Contain => Self::Cover, + Self::Cover => Self::Center, + Self::Center => Self::Natural, + Self::Natural => Self::Contain, + } + } +} + +const BLIT_SHADER: &str = "\ +@group(0) @binding(0) var t: texture_2d<f32>; +@group(0) @binding(1) var s: sampler; + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) uv: vec2<f32>, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput { + var uv = vec2<f32>(f32(vi % 2u), f32(vi / 2u)); + var out: VertexOutput; + out.position = vec4<f32>(2.0 * uv - 1.0, 0.0, 1.0); + out.uv = vec2<f32>(uv.x, 1.0 - uv.y); + return out; +} + +@fragment +fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { + return textureSample(t, s, uv); +} +"; + +pub struct PreviewWindow { + window: Arc<winit::window::Window>, + surface: wgpu::Surface<'static>, + surface_config: wgpu::SurfaceConfiguration, + blit_pipeline: wgpu::RenderPipeline, + blit_bind_group: wgpu::BindGroup, + canvas_width: u32, + canvas_height: u32, + pub scale_mode: ScaleMode, +} + +impl PreviewWindow { + #[allow(clippy::too_many_arguments)] + pub fn new( + device: &wgpu::Device, + window: Arc<winit::window::Window>, + surface: wgpu::Surface<'static>, + surface_format: wgpu::TextureFormat, + canvas_view: &wgpu::TextureView, + canvas_width: u32, + canvas_height: u32, + scale_mode: ScaleMode, + ) -> Self { + let size = window.inner_size(); + let surface_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::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(device, &surface_config); + + let sampler_nearest = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("blit_sampler"), + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let blit_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("blit_bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("blit_bind_group"), + layout: &blit_bgl, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(canvas_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler_nearest), + }, + ], + }); + + let blit_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("blit_shader"), + source: wgpu::ShaderSource::Wgsl(BLIT_SHADER.into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("blit_pipeline_layout"), + bind_group_layouts: &[Some(&blit_bgl)], + immediate_size: 0, + }); + + let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("blit_pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &blit_module, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &blit_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, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + + Self { + window, + surface, + surface_config, + blit_pipeline, + blit_bind_group, + canvas_width, + canvas_height, + scale_mode, + } + } + + pub fn window(&self) -> &winit::window::Window { + &self.window + } + + pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) { + if width == 0 || height == 0 { + return; + } + self.surface_config.width = width; + self.surface_config.height = height; + self.surface.configure(device, &self.surface_config); + } + + pub fn draw(&self, device: &wgpu::Device, queue: &wgpu::Queue) { + let frame = match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(frame) + | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame, + _ => return, + }; + + let view = frame.texture.create_view(&Default::default()); + + let w_scale = self.surface_config.width as f32 / self.canvas_width as f32; + let h_scale = self.surface_config.height as f32 / self.canvas_height as f32; + let scale = match self.scale_mode { + ScaleMode::Contain => w_scale.min(h_scale), + ScaleMode::Cover => w_scale.max(h_scale), + ScaleMode::Center => 1.0, + ScaleMode::Natural => 1.0 / (1.0 / w_scale.min(h_scale)).ceil(), + }; + + let vp_w = self.canvas_width as f32 * scale; + let vp_h = self.canvas_height as f32 * scale; + let vp_x = (self.surface_config.width as f32 - vp_w) / 2.0; + let vp_y = (self.surface_config.height as f32 - vp_h) / 2.0; + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("blit_encoder"), + }); + + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("blit_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_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0); + pass.set_pipeline(&self.blit_pipeline); + pass.set_bind_group(0, &self.blit_bind_group, &[]); + pass.draw(0..4, 0..1); + } + + queue.submit(std::iter::once(encoder.finish())); + frame.present(); + } +} |
