summaryrefslogtreecommitdiffstats
path: root/src/window.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/window.rs')
-rw-r--r--src/window.rs245
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();
+ }
+}