Update to wgpu 0.9 (#179)

* Update to wgpu 0.9

* Fix validation error in WGSL shader

- This moves the hardcoded vertex positions and texture coordinates to
  the vertex buffer.
- Replaces the two-triangle quad to 1 full-screen triangle (fixes #180)
- Rewrites the custom shader example to fix a bug with large surface
  textures;
  - The input texture size was used for the output texture, causing the
    purple rectangle to appear very jumpy on large displays in full screen.
- The `ScalingRenderer` now exposes its clipping rectangle. The custom
  shader example uses this for its own clipping rectangle, but it can
  also be used for interacting with the border in general.

* Switch to `wgpu::include_wgsl!()`

- This is a nice little simplification.
- Thanks to @JMS55 for the suggestion!
This commit is contained in:
Jay Oster 2021-06-27 11:09:29 -07:00 committed by GitHub
parent 3ce4b75ad9
commit c4df23f65d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 242 additions and 147 deletions

View file

@ -19,11 +19,12 @@ include = [
] ]
[dependencies] [dependencies]
bytemuck = "1.7"
pollster = "0.2" pollster = "0.2"
raw-window-handle = "0.3" raw-window-handle = "0.3"
thiserror = "1.0" thiserror = "1.0"
ultraviolet = "0.8" ultraviolet = "0.8"
wgpu = "0.8.1" wgpu = "0.9"
[dev-dependencies] [dev-dependencies]
pixels-mocks = { path = "internals/pixels-mocks" } pixels-mocks = { path = "internals/pixels-mocks" }

View file

@ -142,7 +142,7 @@ fn create_window(
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_visible(false) .with_visible(false)
.with_title(title) .with_title(title)
.build(&event_loop) .build(event_loop)
.unwrap(); .unwrap();
let hidpi_factor = window.scale_factor(); let hidpi_factor = window.scale_factor();

View file

@ -10,6 +10,7 @@ optimize = ["log/release_max_level_warn"]
default = ["optimize"] default = ["optimize"]
[dependencies] [dependencies]
bytemuck = "1.7"
env_logger = "0.8" env_logger = "0.8"
log = "0.4" log = "0.4"
pixels = { path = "../.." } pixels = { path = "../.." }

View file

@ -5,35 +5,14 @@ struct VertexOutput {
[[builtin(position)]] position: vec4<f32>; [[builtin(position)]] position: vec4<f32>;
}; };
let positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
// Lower right triangle
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0),
);
let uv: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
// Lower right triangle
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
);
[[stage(vertex)]] [[stage(vertex)]]
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput { fn vs_main(
[[location(0)]] position: vec2<f32>,
[[location(1)]] tex_coord: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.tex_coord = uv[vertex_index]; out.tex_coord = tex_coord;
out.position = vec4<f32>(positions[vertex_index], 0.0, 1.0); out.position = vec4<f32>(position, 0.0, 1.0);
return out; return out;
} }
@ -64,10 +43,8 @@ fn random_vec2(st: vec2<f32>) -> f32 {
[[stage(fragment)]] [[stage(fragment)]]
fn fs_main([[location(0)]] tex_coord: vec2<f32>) -> [[location(0)]] vec4<f32> { fn fs_main([[location(0)]] tex_coord: vec2<f32>) -> [[location(0)]] vec4<f32> {
let sampled_color: vec4<f32> = textureSample(r_tex_color, r_tex_sampler, tex_coord); let sampled_color = textureSample(r_tex_color, r_tex_sampler, tex_coord);
let noise_color: vec3<f32> = vec3<f32>(random_vec2( let noise_color = vec3<f32>(random_vec2(tex_coord.xy * vec2<f32>(r_locals.time % tau + bias)));
tex_coord.xy * vec2<f32>(r_locals.time % tau + bias)
));
return vec4<f32>(sampled_color.rgb * noise_color, sampled_color.a); return vec4<f32>(sampled_color.rgb * noise_color, sampled_color.a);
} }

View file

@ -3,7 +3,7 @@
use crate::renderers::NoiseRenderer; use crate::renderers::NoiseRenderer;
use log::error; use log::error;
use pixels::{wgpu, Error, Pixels, SurfaceTexture}; use pixels::{Error, Pixels, SurfaceTexture};
use winit::dpi::LogicalSize; use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode}; use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
@ -44,9 +44,8 @@ fn main() -> Result<(), Error> {
Pixels::new(WIDTH, HEIGHT, surface_texture)? Pixels::new(WIDTH, HEIGHT, surface_texture)?
}; };
let mut world = World::new(); let mut world = World::new();
let mut time = 0.0; 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| { event_loop.run(move |event, _, control_flow| {
// Draw the current frame // Draw the current frame
@ -54,12 +53,13 @@ fn main() -> Result<(), Error> {
world.draw(pixels.get_frame()); world.draw(pixels.get_frame());
let render_result = pixels.render_with(|encoder, render_target, context| { 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); noise_renderer.update(&context.queue, time);
time += 0.01; time += 0.01;
noise_renderer.render(encoder, render_target); noise_renderer.render(encoder, render_target, context.scaling_renderer.clip_rect());
}); });
if render_result if render_result
@ -82,6 +82,7 @@ fn main() -> Result<(), Error> {
// Resize the window // Resize the window
if let Some(size) = input.window_resized() { if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height); pixels.resize_surface(size.width, size.height);
noise_renderer.resize(pixels.device(), size.width, size.height);
} }
// Update internal state and request a redraw // Update internal state and request a redraw
@ -117,7 +118,7 @@ impl World {
/// Draw the `World` state to the frame buffer. /// 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]) { fn draw(&self, frame: &mut [u8]) {
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
let x = (i % WIDTH as usize) as i16; 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)
}

View file

@ -1,21 +1,24 @@
use pixels::wgpu::{self, util::DeviceExt}; use pixels::wgpu::{self, util::DeviceExt};
use std::borrow::Cow;
pub(crate) struct NoiseRenderer { pub(crate) struct NoiseRenderer {
texture_view: wgpu::TextureView,
sampler: wgpu::Sampler,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
time_buffer: wgpu::Buffer, time_buffer: wgpu::Buffer,
vertex_buffer: wgpu::Buffer,
} }
impl NoiseRenderer { impl NoiseRenderer {
pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self { pub(crate) fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
let shader = wgpu::ShaderModuleDescriptor { let shader = wgpu::include_wgsl!("../shaders/noise.wgsl");
label: Some("custom_noise_shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/noise.wgsl"))),
flags: wgpu::ShaderFlags::VALIDATION,
};
let module = device.create_shader_module(&shader); 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 // Create a texture sampler with nearest neighbor
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("NoiseRenderer sampler"), label: Some("NoiseRenderer sampler"),
@ -32,6 +35,37 @@ impl NoiseRenderer {
border_color: None, 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 // Create uniform buffer
let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer u_Time"), label: Some("NoiseRenderer u_Time"),
@ -74,28 +108,13 @@ impl NoiseRenderer {
}, },
], ],
}); });
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { let bind_group = create_bind_group(
label: None, device,
layout: &bind_group_layout, &bind_group_layout,
entries: &[ &texture_view,
wgpu::BindGroupEntry { &sampler,
binding: 0, &time_buffer,
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,
}),
},
],
});
// Create pipeline // Create pipeline
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
@ -109,7 +128,7 @@ impl NoiseRenderer {
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &module, module: &module,
entry_point: "vs_main", entry_point: "vs_main",
buffers: &[], buffers: &[vertex_buffer_layout],
}, },
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
@ -129,12 +148,31 @@ impl NoiseRenderer {
}); });
Self { Self {
texture_view,
sampler,
bind_group_layout,
bind_group, bind_group,
render_pipeline, render_pipeline,
time_buffer, 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) { pub(crate) fn update(&self, queue: &wgpu::Queue, time: f32) {
queue.write_buffer(&self.time_buffer, 0, &time.to_ne_bytes()); queue.write_buffer(&self.time_buffer, 0, &time.to_ne_bytes());
} }
@ -143,6 +181,7 @@ impl NoiseRenderer {
&self, &self,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
render_target: &wgpu::TextureView, render_target: &wgpu::TextureView,
clip_rect: (u32, u32, u32, u32),
) { ) {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("NoiseRenderer render pass"), label: Some("NoiseRenderer render pass"),
@ -158,6 +197,59 @@ impl NoiseRenderer {
}); });
rpass.set_pipeline(&self.render_pipeline); rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.bind_group, &[]); 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,
}),
},
],
})
}

View file

@ -11,17 +11,14 @@ default = ["optimize"]
[dependencies] [dependencies]
egui = "0.12" egui = "0.12"
egui_wgpu_backend = "0.9"
env_logger = "0.8" env_logger = "0.8"
log = "0.4" log = "0.4"
pixels = { path = "../.." } pixels = { path = "../.." }
winit = "0.25" winit = "0.25"
winit_input_helper = "0.10" winit_input_helper = "0.10"
[dependencies.egui_wgpu_backend]
git = "https://github.com/hasenbanck/egui_wgpu_backend.git"
rev = "63a002c6a9b6c016e45806dd065864431caab621"
[dependencies.egui_winit_platform] [dependencies.egui_winit_platform]
git = "https://github.com/hasenbanck/egui_winit_platform.git" git = "https://github.com/hasenbanck/egui_winit_platform.git"
rev = "f9a0814a395a5cc1cc997081f33ee09503a4a307" rev = "ab576591949a2c0ac1494af52713a1771ff8326a"
features = ["webbrowser"] features = ["webbrowser"]

View file

@ -12,9 +12,12 @@ default = ["optimize"]
[dependencies] [dependencies]
env_logger = "0.8" env_logger = "0.8"
imgui = "0.7" imgui = "0.7"
imgui-wgpu = "0.15.1"
imgui-winit-support = { version = "0.7.1", default-features = false, features = ["winit-25"] } imgui-winit-support = { version = "0.7.1", default-features = false, features = ["winit-25"] }
log = "0.4" log = "0.4"
pixels = { path = "../.." } pixels = { path = "../.." }
winit = "0.25" winit = "0.25"
winit_input_helper = "0.10" winit_input_helper = "0.10"
[dependencies.imgui-wgpu]
git = "https://github.com/Yatekii/imgui-wgpu-rs.git"
rev = "5980d8f3ebeda52310bb9f38d98ae1d513907e7f"

View file

@ -49,7 +49,7 @@ impl Gui {
texture_format, texture_format,
..Default::default() ..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 // Return GUI context
Self { Self {

View file

@ -102,7 +102,7 @@ impl Collision {
for (i, shield_rect) in shield_rects.iter().enumerate() { for (i, shield_rect) in shield_rects.iter().enumerate() {
// broad phase collision detection // broad phase collision detection
if bullet_rect.intersects(&shield_rect) { if bullet_rect.intersects(shield_rect) {
// TODO: Narrow phase (per-pixel) collision detection // TODO: Narrow phase (per-pixel) collision detection
// TODO: Break shield // TODO: Break shield
@ -163,7 +163,7 @@ impl Collision {
for (i, shield_rect) in shield_rects.iter().enumerate() { for (i, shield_rect) in shield_rects.iter().enumerate() {
// broad phase collision detection // broad phase collision detection
if laser_rect.intersects(&shield_rect) { if laser_rect.intersects(shield_rect) {
// TODO: Narrow phase (per-pixel) collision detection // TODO: Narrow phase (per-pixel) collision detection
// TODO: Break shield // TODO: Break shield

View file

@ -134,7 +134,7 @@ fn create_window(
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_visible(false) .with_visible(false)
.with_title(title) .with_title(title)
.build(&event_loop) .build(event_loop)
.unwrap(); .unwrap();
let hidpi_factor = window.scale_factor(); let hidpi_factor = window.scale_factor();

View file

@ -10,35 +10,14 @@ struct VertexOutput {
}; };
[[group(0), binding(2)]] var r_locals: Locals; [[group(0), binding(2)]] var r_locals: Locals;
let positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
// Lower right triangle
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0),
);
let uv: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
// Lower right triangle
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
);
[[stage(vertex)]] [[stage(vertex)]]
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput { fn vs_main(
[[location(0)]] position: vec2<f32>,
[[location(1)]] tex_coord: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.tex_coord = uv[vertex_index]; out.tex_coord = tex_coord;
out.position = r_locals.transform * vec4<f32>(positions[vertex_index], 0.0, 1.0); out.position = r_locals.transform * vec4<f32>(position, 0.0, 1.0);
return out; return out;
} }

View file

@ -256,7 +256,7 @@ pub(crate) fn create_swap_chain(
present_mode: wgpu::PresentMode, present_mode: wgpu::PresentMode,
) -> wgpu::SwapChain { ) -> wgpu::SwapChain {
device.create_swap_chain( device.create_swap_chain(
&surface, surface,
&wgpu::SwapChainDescriptor { &wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT, usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format, format,

View file

@ -1,16 +1,17 @@
use crate::SurfaceSize; use crate::SurfaceSize;
use std::borrow::Cow;
use ultraviolet::Mat4; use ultraviolet::Mat4;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
/// The default renderer that scales your frame to the screen size. /// The default renderer that scales your frame to the screen size.
#[derive(Debug)] #[derive(Debug)]
pub struct ScalingRenderer { pub struct ScalingRenderer {
vertex_buffer: wgpu::Buffer,
uniform_buffer: wgpu::Buffer, uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
width: f32, width: f32,
height: f32, height: f32,
clip_rect: (u32, u32, u32, u32),
render_texture_format: wgpu::TextureFormat, render_texture_format: wgpu::TextureFormat,
} }
@ -22,11 +23,7 @@ impl ScalingRenderer {
surface_size: &SurfaceSize, surface_size: &SurfaceSize,
render_texture_format: wgpu::TextureFormat, render_texture_format: wgpu::TextureFormat,
) -> Self { ) -> Self {
let shader = wgpu::ShaderModuleDescriptor { let shader = wgpu::include_wgsl!("../shaders/scale.wgsl");
label: Some("pixels_scaling_renderer_shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/scale.wgsl"))),
flags: wgpu::ShaderFlags::VALIDATION,
};
let module = device.create_shader_module(&shader); let module = device.create_shader_module(&shader);
// Create a texture sampler with nearest neighbor // Create a texture sampler with nearest neighbor
@ -45,6 +42,37 @@ impl ScalingRenderer {
border_color: None, 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 // Create uniform buffer
let matrix = ScalingMatrix::new( let matrix = ScalingMatrix::new(
(texture_size.width as f32, texture_size.height as f32), (texture_size.width as f32, texture_size.height as f32),
@ -53,7 +81,7 @@ impl ScalingRenderer {
let transform_bytes = matrix.as_bytes(); let transform_bytes = matrix.as_bytes();
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("pixels_scaling_renderer_matrix_uniform_buffer"), label: Some("pixels_scaling_renderer_matrix_uniform_buffer"),
contents: &transform_bytes, contents: transform_bytes,
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
}); });
@ -127,7 +155,7 @@ impl ScalingRenderer {
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &module, module: &module,
entry_point: "vs_main", entry_point: "vs_main",
buffers: &[], buffers: &[vertex_buffer_layout],
}, },
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
@ -146,18 +174,23 @@ impl ScalingRenderer {
}), }),
}); });
// Create clipping rectangle
let clip_rect = matrix.clip_rect();
Self { Self {
vertex_buffer,
uniform_buffer, uniform_buffer,
bind_group, bind_group,
render_pipeline, render_pipeline,
width: texture_size.width as f32, width: texture_size.width as f32,
height: texture_size.height as f32, height: texture_size.height as f32,
clip_rect,
render_texture_format, render_texture_format,
} }
} }
/// Draw the pixel buffer to the render target.
pub fn render(&self, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView) { 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 { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("pixels_scaling_renderer_render_pass"), label: Some("pixels_scaling_renderer_render_pass"),
color_attachments: &[wgpu::RenderPassColorAttachment { color_attachments: &[wgpu::RenderPassColorAttachment {
@ -172,27 +205,44 @@ impl ScalingRenderer {
}); });
rpass.set_pipeline(&self.render_pipeline); rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.bind_group, &[]); 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 matrix = ScalingMatrix::new((self.width, self.height), (width as f32, height as f32));
let transform_bytes = matrix.as_bytes(); 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)] #[derive(Debug)]
pub(crate) struct ScalingMatrix { pub(crate) struct ScalingMatrix {
pub(crate) transform: Mat4, pub(crate) transform: Mat4,
clip_rect: (u32, u32, u32, u32),
} }
impl ScalingMatrix { impl ScalingMatrix {
// texture_size is the dimensions of the drawing texture // texture_size is the dimensions of the drawing texture
// screen_size is the dimensions of the surface being drawn to // screen_size is the dimensions of the surface being drawn to
pub(crate) fn new(texture_size: (f32, f32), screen_size: (f32, f32)) -> ScalingMatrix { 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 (texture_width, texture_height) = texture_size;
let (screen_width, screen_height) = screen_size;
// Get smallest scale size // Get smallest scale size
let scale = (screen_width / texture_width) let scale = (screen_width / texture_width)
@ -200,9 +250,12 @@ impl ScalingMatrix {
.max(1.0) .max(1.0)
.floor(); .floor();
let scaled_width = texture_width * scale;
let scaled_height = texture_height * scale;
// Update transformation matrix // Update transformation matrix
let sw = texture_width * scale / screen_width; let sw = scaled_width / screen_width;
let sh = texture_height * scale / screen_height; let sh = scaled_height / screen_height;
#[rustfmt::skip] #[rustfmt::skip]
let transform: [f32; 16] = [ let transform: [f32; 16] = [
sw, 0.0, 0.0, 0.0, sw, 0.0, 0.0, 0.0,
@ -211,12 +264,27 @@ impl ScalingMatrix {
0.0, 0.0, 0.0, 1.0, 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 { ScalingMatrix {
transform: Mat4::from(transform), transform: Mat4::from(transform),
clip_rect,
} }
} }
fn as_bytes(&self) -> &[u8] { fn as_bytes(&self) -> &[u8] {
self.transform.as_byte_slice() self.transform.as_byte_slice()
} }
pub(crate) fn clip_rect(&self) -> (u32, u32, u32, u32) {
self.clip_rect
}
} }