diff --git a/Cargo.toml b/Cargo.toml index 817e2ba..e60f491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,12 @@ include = [ ] [dependencies] +bytemuck = "1.7" pollster = "0.2" raw-window-handle = "0.3" thiserror = "1.0" ultraviolet = "0.8" -wgpu = "0.8.1" +wgpu = "0.9" [dev-dependencies] pixels-mocks = { path = "internals/pixels-mocks" } diff --git a/examples/conway/src/main.rs b/examples/conway/src/main.rs index 1e89866..e1b2e74 100644 --- a/examples/conway/src/main.rs +++ b/examples/conway/src/main.rs @@ -142,7 +142,7 @@ fn create_window( let window = winit::window::WindowBuilder::new() .with_visible(false) .with_title(title) - .build(&event_loop) + .build(event_loop) .unwrap(); let hidpi_factor = window.scale_factor(); diff --git a/examples/custom-shader/Cargo.toml b/examples/custom-shader/Cargo.toml index dfe5b76..e16f027 100644 --- a/examples/custom-shader/Cargo.toml +++ b/examples/custom-shader/Cargo.toml @@ -10,6 +10,7 @@ optimize = ["log/release_max_level_warn"] default = ["optimize"] [dependencies] +bytemuck = "1.7" env_logger = "0.8" log = "0.4" pixels = { path = "../.." } diff --git a/examples/custom-shader/shaders/noise.wgsl b/examples/custom-shader/shaders/noise.wgsl index cdde83c..82e8452 100644 --- a/examples/custom-shader/shaders/noise.wgsl +++ b/examples/custom-shader/shaders/noise.wgsl @@ -5,35 +5,14 @@ struct VertexOutput { [[builtin(position)]] position: vec4; }; -let positions: array, 6> = array, 6>( - // Upper left triangle - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(-1.0, 1.0), - - // Lower right triangle - vec2(-1.0, 1.0), - vec2(1.0, -1.0), - vec2(1.0, 1.0), -); - -let uv: array, 6> = array, 6>( - // Upper left triangle - vec2(0.0, 0.0), - vec2(1.0, 0.0), - vec2(0.0, 1.0), - - // Lower right triangle - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0), -); - [[stage(vertex)]] -fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput { +fn vs_main( + [[location(0)]] position: vec2, + [[location(1)]] tex_coord: vec2, +) -> VertexOutput { var out: VertexOutput; - out.tex_coord = uv[vertex_index]; - out.position = vec4(positions[vertex_index], 0.0, 1.0); + out.tex_coord = tex_coord; + out.position = vec4(position, 0.0, 1.0); return out; } @@ -64,10 +43,8 @@ fn random_vec2(st: vec2) -> f32 { [[stage(fragment)]] fn fs_main([[location(0)]] tex_coord: vec2) -> [[location(0)]] vec4 { - let sampled_color: vec4 = textureSample(r_tex_color, r_tex_sampler, tex_coord); - let noise_color: vec3 = vec3(random_vec2( - tex_coord.xy * vec2(r_locals.time % tau + bias) - )); + let sampled_color = textureSample(r_tex_color, r_tex_sampler, tex_coord); + let noise_color = vec3(random_vec2(tex_coord.xy * vec2(r_locals.time % tau + bias))); return vec4(sampled_color.rgb * noise_color, sampled_color.a); } diff --git a/examples/custom-shader/src/main.rs b/examples/custom-shader/src/main.rs index 8d737ed..2cf9d05 100644 --- a/examples/custom-shader/src/main.rs +++ b/examples/custom-shader/src/main.rs @@ -3,7 +3,7 @@ use crate::renderers::NoiseRenderer; use log::error; -use pixels::{wgpu, Error, Pixels, SurfaceTexture}; +use pixels::{Error, Pixels, SurfaceTexture}; use winit::dpi::LogicalSize; use winit::event::{Event, VirtualKeyCode}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -44,9 +44,8 @@ fn main() -> Result<(), Error> { Pixels::new(WIDTH, HEIGHT, surface_texture)? }; let mut world = World::new(); - let mut time = 0.0; - let (scaled_texture, noise_renderer) = create_noise_renderer(&pixels); + let mut noise_renderer = NoiseRenderer::new(pixels.device(), WIDTH, HEIGHT); event_loop.run(move |event, _, control_flow| { // Draw the current frame @@ -54,12 +53,13 @@ fn main() -> Result<(), Error> { world.draw(pixels.get_frame()); let render_result = pixels.render_with(|encoder, render_target, context| { - context.scaling_renderer.render(encoder, &scaled_texture); + let noise_texture = noise_renderer.get_texture_view(); + context.scaling_renderer.render(encoder, noise_texture); noise_renderer.update(&context.queue, time); time += 0.01; - noise_renderer.render(encoder, render_target); + noise_renderer.render(encoder, render_target, context.scaling_renderer.clip_rect()); }); if render_result @@ -82,6 +82,7 @@ fn main() -> Result<(), Error> { // Resize the window if let Some(size) = input.window_resized() { pixels.resize_surface(size.width, size.height); + noise_renderer.resize(pixels.device(), size.width, size.height); } // Update internal state and request a redraw @@ -117,7 +118,7 @@ impl World { /// Draw the `World` state to the frame buffer. /// - /// Assumes the default texture format: [`wgpu::TextureFormat::Rgba8UnormSrgb`] + /// Assumes the default texture format: [`pixels::wgpu::TextureFormat::Rgba8UnormSrgb`] fn draw(&self, frame: &mut [u8]) { for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { let x = (i % WIDTH as usize) as i16; @@ -138,27 +139,3 @@ impl World { } } } - -fn create_noise_renderer(pixels: &Pixels) -> (wgpu::TextureView, NoiseRenderer) { - let device = &pixels.device(); - - let texture_descriptor = wgpu::TextureDescriptor { - label: None, - size: pixels::wgpu::Extent3d { - width: WIDTH, - height: HEIGHT, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, - }; - let scaled_texture = device - .create_texture(&texture_descriptor) - .create_view(&wgpu::TextureViewDescriptor::default()); - let noise_renderer = NoiseRenderer::new(device, &scaled_texture); - - (scaled_texture, noise_renderer) -} diff --git a/examples/custom-shader/src/renderers.rs b/examples/custom-shader/src/renderers.rs index 576b730..1f8212b 100644 --- a/examples/custom-shader/src/renderers.rs +++ b/examples/custom-shader/src/renderers.rs @@ -1,21 +1,24 @@ use pixels::wgpu::{self, util::DeviceExt}; -use std::borrow::Cow; pub(crate) struct NoiseRenderer { + texture_view: wgpu::TextureView, + sampler: wgpu::Sampler, + bind_group_layout: wgpu::BindGroupLayout, bind_group: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, time_buffer: wgpu::Buffer, + vertex_buffer: wgpu::Buffer, } impl NoiseRenderer { - pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self { - let shader = wgpu::ShaderModuleDescriptor { - label: Some("custom_noise_shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/noise.wgsl"))), - flags: wgpu::ShaderFlags::VALIDATION, - }; + pub(crate) fn new(device: &wgpu::Device, width: u32, height: u32) -> Self { + let shader = wgpu::include_wgsl!("../shaders/noise.wgsl"); let module = device.create_shader_module(&shader); + // Create a texture view that will be used as input + // This will be used as the render target for the default scaling renderer + let texture_view = create_texture_view(device, width, height); + // Create a texture sampler with nearest neighbor let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("NoiseRenderer sampler"), @@ -32,6 +35,37 @@ impl NoiseRenderer { border_color: None, }); + // Create vertex buffer; array-of-array of position and texture coordinates + let vertex_data: [[[f32; 2]; 2]; 3] = [ + // One full-screen triangle + // See: https://github.com/parasyte/pixels/issues/180 + [[-1.0, -1.0], [0.0, 0.0]], + [[3.0, -1.0], [2.0, 0.0]], + [[-1.0, 3.0], [0.0, 2.0]], + ]; + let vertex_data_slice = bytemuck::cast_slice(&vertex_data); + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("NoiseRenderer vertex buffer"), + contents: vertex_data_slice, + usage: wgpu::BufferUsage::VERTEX, + }); + let vertex_buffer_layout = wgpu::VertexBufferLayout { + array_stride: (vertex_data_slice.len() / vertex_data.len()) as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 4 * 2, + shader_location: 1, + }, + ], + }; + // Create uniform buffer let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("NoiseRenderer u_Time"), @@ -74,28 +108,13 @@ impl NoiseRenderer { }, ], }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &time_buffer, - offset: 0, - size: None, - }), - }, - ], - }); + let bind_group = create_bind_group( + device, + &bind_group_layout, + &texture_view, + &sampler, + &time_buffer, + ); // Create pipeline let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -109,7 +128,7 @@ impl NoiseRenderer { vertex: wgpu::VertexState { module: &module, entry_point: "vs_main", - buffers: &[], + buffers: &[vertex_buffer_layout], }, primitive: wgpu::PrimitiveState::default(), depth_stencil: None, @@ -129,12 +148,31 @@ impl NoiseRenderer { }); Self { + texture_view, + sampler, + bind_group_layout, bind_group, render_pipeline, time_buffer, + vertex_buffer, } } + pub(crate) fn get_texture_view(&self) -> &wgpu::TextureView { + &self.texture_view + } + + pub(crate) fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) { + self.texture_view = create_texture_view(device, width, height); + self.bind_group = create_bind_group( + device, + &self.bind_group_layout, + &self.texture_view, + &self.sampler, + &self.time_buffer, + ); + } + pub(crate) fn update(&self, queue: &wgpu::Queue, time: f32) { queue.write_buffer(&self.time_buffer, 0, &time.to_ne_bytes()); } @@ -143,6 +181,7 @@ impl NoiseRenderer { &self, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView, + clip_rect: (u32, u32, u32, u32), ) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("NoiseRenderer render pass"), @@ -158,6 +197,59 @@ impl NoiseRenderer { }); rpass.set_pipeline(&self.render_pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.draw(0..6, 0..1); + rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + rpass.set_scissor_rect(clip_rect.0, clip_rect.1, clip_rect.2, clip_rect.3); + rpass.draw(0..3, 0..1); } } + +fn create_texture_view(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView { + let texture_descriptor = wgpu::TextureDescriptor { + label: None, + size: pixels::wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + device + .create_texture(&texture_descriptor) + .create_view(&wgpu::TextureViewDescriptor::default()) +} + +fn create_bind_group( + device: &wgpu::Device, + bind_group_layout: &wgpu::BindGroupLayout, + texture_view: &wgpu::TextureView, + sampler: &wgpu::Sampler, + time_buffer: &wgpu::Buffer, +) -> pixels::wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: time_buffer, + offset: 0, + size: None, + }), + }, + ], + }) +} diff --git a/examples/egui-winit/Cargo.toml b/examples/egui-winit/Cargo.toml index 11f96fa..5f316eb 100644 --- a/examples/egui-winit/Cargo.toml +++ b/examples/egui-winit/Cargo.toml @@ -11,17 +11,14 @@ default = ["optimize"] [dependencies] egui = "0.12" +egui_wgpu_backend = "0.9" env_logger = "0.8" log = "0.4" pixels = { path = "../.." } winit = "0.25" winit_input_helper = "0.10" -[dependencies.egui_wgpu_backend] -git = "https://github.com/hasenbanck/egui_wgpu_backend.git" -rev = "63a002c6a9b6c016e45806dd065864431caab621" - [dependencies.egui_winit_platform] git = "https://github.com/hasenbanck/egui_winit_platform.git" -rev = "f9a0814a395a5cc1cc997081f33ee09503a4a307" +rev = "ab576591949a2c0ac1494af52713a1771ff8326a" features = ["webbrowser"] diff --git a/examples/imgui-winit/Cargo.toml b/examples/imgui-winit/Cargo.toml index 093ba69..fd8ff3b 100644 --- a/examples/imgui-winit/Cargo.toml +++ b/examples/imgui-winit/Cargo.toml @@ -12,9 +12,12 @@ default = ["optimize"] [dependencies] env_logger = "0.8" imgui = "0.7" -imgui-wgpu = "0.15.1" imgui-winit-support = { version = "0.7.1", default-features = false, features = ["winit-25"] } log = "0.4" pixels = { path = "../.." } winit = "0.25" winit_input_helper = "0.10" + +[dependencies.imgui-wgpu] +git = "https://github.com/Yatekii/imgui-wgpu-rs.git" +rev = "5980d8f3ebeda52310bb9f38d98ae1d513907e7f" diff --git a/examples/imgui-winit/src/gui.rs b/examples/imgui-winit/src/gui.rs index 1616bee..c4cb623 100644 --- a/examples/imgui-winit/src/gui.rs +++ b/examples/imgui-winit/src/gui.rs @@ -49,7 +49,7 @@ impl Gui { texture_format, ..Default::default() }; - let renderer = imgui_wgpu::Renderer::new(&mut imgui, &device, &queue, config); + let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config); // Return GUI context Self { diff --git a/examples/invaders/simple-invaders/src/collision.rs b/examples/invaders/simple-invaders/src/collision.rs index e72afcd..877913b 100644 --- a/examples/invaders/simple-invaders/src/collision.rs +++ b/examples/invaders/simple-invaders/src/collision.rs @@ -102,7 +102,7 @@ impl Collision { for (i, shield_rect) in shield_rects.iter().enumerate() { // broad phase collision detection - if bullet_rect.intersects(&shield_rect) { + if bullet_rect.intersects(shield_rect) { // TODO: Narrow phase (per-pixel) collision detection // TODO: Break shield @@ -163,7 +163,7 @@ impl Collision { for (i, shield_rect) in shield_rects.iter().enumerate() { // broad phase collision detection - if laser_rect.intersects(&shield_rect) { + if laser_rect.intersects(shield_rect) { // TODO: Narrow phase (per-pixel) collision detection // TODO: Break shield diff --git a/examples/invaders/src/main.rs b/examples/invaders/src/main.rs index 895fce4..4e611d2 100644 --- a/examples/invaders/src/main.rs +++ b/examples/invaders/src/main.rs @@ -134,7 +134,7 @@ fn create_window( let window = winit::window::WindowBuilder::new() .with_visible(false) .with_title(title) - .build(&event_loop) + .build(event_loop) .unwrap(); let hidpi_factor = window.scale_factor(); diff --git a/shaders/scale.wgsl b/shaders/scale.wgsl index f716c90..b10f2c9 100644 --- a/shaders/scale.wgsl +++ b/shaders/scale.wgsl @@ -10,35 +10,14 @@ struct VertexOutput { }; [[group(0), binding(2)]] var r_locals: Locals; -let positions: array, 6> = array, 6>( - // Upper left triangle - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(-1.0, 1.0), - - // Lower right triangle - vec2(-1.0, 1.0), - vec2(1.0, -1.0), - vec2(1.0, 1.0), -); - -let uv: array, 6> = array, 6>( - // Upper left triangle - vec2(0.0, 0.0), - vec2(1.0, 0.0), - vec2(0.0, 1.0), - - // Lower right triangle - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0), -); - [[stage(vertex)]] -fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput { +fn vs_main( + [[location(0)]] position: vec2, + [[location(1)]] tex_coord: vec2, +) -> VertexOutput { var out: VertexOutput; - out.tex_coord = uv[vertex_index]; - out.position = r_locals.transform * vec4(positions[vertex_index], 0.0, 1.0); + out.tex_coord = tex_coord; + out.position = r_locals.transform * vec4(position, 0.0, 1.0); return out; } diff --git a/src/builder.rs b/src/builder.rs index db9d6a0..cabc36b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -256,7 +256,7 @@ pub(crate) fn create_swap_chain( present_mode: wgpu::PresentMode, ) -> wgpu::SwapChain { device.create_swap_chain( - &surface, + surface, &wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format, diff --git a/src/renderers.rs b/src/renderers.rs index dfbae09..ba84029 100644 --- a/src/renderers.rs +++ b/src/renderers.rs @@ -1,16 +1,17 @@ use crate::SurfaceSize; -use std::borrow::Cow; use ultraviolet::Mat4; use wgpu::util::DeviceExt; /// The default renderer that scales your frame to the screen size. #[derive(Debug)] pub struct ScalingRenderer { + vertex_buffer: wgpu::Buffer, uniform_buffer: wgpu::Buffer, bind_group: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, width: f32, height: f32, + clip_rect: (u32, u32, u32, u32), render_texture_format: wgpu::TextureFormat, } @@ -22,11 +23,7 @@ impl ScalingRenderer { surface_size: &SurfaceSize, render_texture_format: wgpu::TextureFormat, ) -> Self { - let shader = wgpu::ShaderModuleDescriptor { - label: Some("pixels_scaling_renderer_shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/scale.wgsl"))), - flags: wgpu::ShaderFlags::VALIDATION, - }; + let shader = wgpu::include_wgsl!("../shaders/scale.wgsl"); let module = device.create_shader_module(&shader); // Create a texture sampler with nearest neighbor @@ -45,6 +42,37 @@ impl ScalingRenderer { border_color: None, }); + // Create vertex buffer; array-of-array of position and texture coordinates + let vertex_data: [[[f32; 2]; 2]; 3] = [ + // One full-screen triangle + // See: https://github.com/parasyte/pixels/issues/180 + [[-1.0, -1.0], [0.0, 0.0]], + [[3.0, -1.0], [2.0, 0.0]], + [[-1.0, 3.0], [0.0, 2.0]], + ]; + let vertex_data_slice = bytemuck::cast_slice(&vertex_data); + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("pixels_scaling_renderer_vertex_buffer"), + contents: vertex_data_slice, + usage: wgpu::BufferUsage::VERTEX, + }); + let vertex_buffer_layout = wgpu::VertexBufferLayout { + array_stride: (vertex_data_slice.len() / vertex_data.len()) as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 4 * 2, + shader_location: 1, + }, + ], + }; + // Create uniform buffer let matrix = ScalingMatrix::new( (texture_size.width as f32, texture_size.height as f32), @@ -53,7 +81,7 @@ impl ScalingRenderer { let transform_bytes = matrix.as_bytes(); let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("pixels_scaling_renderer_matrix_uniform_buffer"), - contents: &transform_bytes, + contents: transform_bytes, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, }); @@ -127,7 +155,7 @@ impl ScalingRenderer { vertex: wgpu::VertexState { module: &module, entry_point: "vs_main", - buffers: &[], + buffers: &[vertex_buffer_layout], }, primitive: wgpu::PrimitiveState::default(), depth_stencil: None, @@ -146,18 +174,23 @@ impl ScalingRenderer { }), }); + // Create clipping rectangle + let clip_rect = matrix.clip_rect(); + Self { + vertex_buffer, uniform_buffer, bind_group, render_pipeline, width: texture_size.width as f32, height: texture_size.height as f32, + clip_rect, render_texture_format, } } + /// Draw the pixel buffer to the render target. pub fn render(&self, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView) { - // Draw the updated texture to the render target let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("pixels_scaling_renderer_render_pass"), color_attachments: &[wgpu::RenderPassColorAttachment { @@ -172,27 +205,44 @@ impl ScalingRenderer { }); rpass.set_pipeline(&self.render_pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.draw(0..6, 0..1); + rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + rpass.set_scissor_rect( + self.clip_rect.0, + self.clip_rect.1, + self.clip_rect.2, + self.clip_rect.3, + ); + rpass.draw(0..3, 0..1); } - pub(crate) fn resize(&self, queue: &wgpu::Queue, width: u32, height: u32) { + /// Get the clipping rectangle for the scaling renderer. + /// + /// This rectangle defines the inner bounds of the surface texture, without the border. + pub fn clip_rect(&self) -> (u32, u32, u32, u32) { + self.clip_rect + } + + pub(crate) fn resize(&mut self, queue: &wgpu::Queue, width: u32, height: u32) { let matrix = ScalingMatrix::new((self.width, self.height), (width as f32, height as f32)); let transform_bytes = matrix.as_bytes(); - queue.write_buffer(&self.uniform_buffer, 0, &transform_bytes); + queue.write_buffer(&self.uniform_buffer, 0, transform_bytes); + + self.clip_rect = matrix.clip_rect(); } } #[derive(Debug)] pub(crate) struct ScalingMatrix { pub(crate) transform: Mat4, + clip_rect: (u32, u32, u32, u32), } impl ScalingMatrix { // texture_size is the dimensions of the drawing texture // screen_size is the dimensions of the surface being drawn to pub(crate) fn new(texture_size: (f32, f32), screen_size: (f32, f32)) -> ScalingMatrix { - let (screen_width, screen_height) = screen_size; let (texture_width, texture_height) = texture_size; + let (screen_width, screen_height) = screen_size; // Get smallest scale size let scale = (screen_width / texture_width) @@ -200,9 +250,12 @@ impl ScalingMatrix { .max(1.0) .floor(); + let scaled_width = texture_width * scale; + let scaled_height = texture_height * scale; + // Update transformation matrix - let sw = texture_width * scale / screen_width; - let sh = texture_height * scale / screen_height; + let sw = scaled_width / screen_width; + let sh = scaled_height / screen_height; #[rustfmt::skip] let transform: [f32; 16] = [ sw, 0.0, 0.0, 0.0, @@ -211,12 +264,27 @@ impl ScalingMatrix { 0.0, 0.0, 0.0, 1.0, ]; + // Create a clipping rectangle + let x = (screen_width - scaled_width) / 2.0; + let y = (screen_height - scaled_height) / 2.0; + let clip_rect = ( + x as u32, + y as u32, + scaled_width as u32, + scaled_height as u32, + ); + ScalingMatrix { transform: Mat4::from(transform), + clip_rect, } } fn as_bytes(&self) -> &[u8] { self.transform.as_byte_slice() } + + pub(crate) fn clip_rect(&self) -> (u32, u32, u32, u32) { + self.clip_rect + } }