Skip to content

Commit 8c67065

Browse files
authored
Add several scaling modes for non-integer upscaling (#425)
* Add set_scaling_mode with support for bilinear scaling * Add hybrid upscaler for single pass pixel-perfect + linear * Fix scale_hybrid blurriness * Remove linear modes. Rename PixelPerfectHybrid to Fill. * Set "invaders" to use the fill scaling mode * Rename "hybrid" to "fill" in code * Cargo fmt for lints * Update set_scaling_mode comment * Allow "too_many_arguments" on ScalingRenderer::new for now
1 parent 3d3b318 commit 8c67065

6 files changed

Lines changed: 209 additions & 34 deletions

File tree

examples/invaders/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,16 @@ fn main() -> Result<(), Error> {
122122
Arc::new(window)
123123
};
124124

125-
let pixels = {
125+
let mut pixels = {
126126
let window_size = window.inner_size();
127127
let surface_texture =
128128
SurfaceTexture::new(window_size.width, window_size.height, Arc::clone(&window));
129129
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture)?
130130
};
131131

132+
// Use the fill scaling mode which supports non-integer scaling.
133+
pixels.set_scaling_mode(pixels::ScalingMode::Fill);
134+
132135
let game = Game::new(pixels, debug);
133136

134137
let res = game_loop(

shaders/scale.wgsl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct VertexOutput {
77

88
struct Locals {
99
transform: mat4x4<f32>,
10+
input_size: vec4<f32>
1011
}
1112
@group(0) @binding(2) var<uniform> r_locals: Locals;
1213

shaders/scale_fill.wgsl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Vertex shader bindings
2+
3+
struct VertexOutput {
4+
@location(0) tex_coord: vec2<f32>,
5+
@builtin(position) position: vec4<f32>,
6+
}
7+
8+
struct Locals {
9+
transform: mat4x4<f32>,
10+
input_size: vec4<f32>
11+
}
12+
@group(0) @binding(2) var<uniform> r_locals: Locals;
13+
14+
@vertex
15+
fn vs_main(
16+
@location(0) position: vec2<f32>,
17+
) -> VertexOutput {
18+
var out: VertexOutput;
19+
// Output tex coord in texel coordinates (0..width, 0..height)
20+
out.tex_coord = fma(position, vec2<f32>(0.5, -0.5), vec2<f32>(0.5, 0.5)) * r_locals.input_size.xy;
21+
out.position = r_locals.transform * vec4<f32>(position, 0.0, 1.0);
22+
return out;
23+
}
24+
25+
// Fragment shader bindings
26+
27+
@group(0) @binding(0) var r_tex_color: texture_2d<f32>;
28+
@group(0) @binding(1) var r_tex_sampler: sampler;
29+
30+
@fragment
31+
fn fs_main(@location(0) tex_coord: vec2<f32>) -> @location(0) vec4<f32> {
32+
let half = vec2<f32>(0.5);
33+
let one = vec2<f32>(1.0);
34+
let zero = vec2<f32>(0.0);
35+
let texels_per_pixel = vec2<f32>(dpdx(tex_coord.x), dpdy(tex_coord.y));
36+
let tex_coord_fract = fract(tex_coord);
37+
let tex_coord_x = clamp(tex_coord_fract / texels_per_pixel, zero, half) + clamp((tex_coord_fract - one) / texels_per_pixel + half, zero, half);
38+
let tex_coord_final = (floor(tex_coord) + tex_coord_x) * r_locals.input_size.zw;
39+
return textureSample(r_tex_color, r_tex_sampler, tex_coord_final);
40+
}

src/builder.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::renderers::{ScalingMatrix, ScalingRenderer};
2-
use crate::{Error, Pixels, PixelsContext, SurfaceSize, SurfaceTexture, TextureError};
2+
use crate::{Error, Pixels, PixelsContext, ScalingMode, SurfaceSize, SurfaceTexture, TextureError};
33

44
/// A builder to help create customized pixel buffers.
55
pub struct PixelsBuilder<'req, 'dev, 'win, W: wgpu::WindowHandle + 'win> {
@@ -309,6 +309,7 @@ impl<'req, 'dev, 'win, W: wgpu::WindowHandle + 'win> PixelsBuilder<'req, 'dev, '
309309
let surface_size = self.surface_texture.size;
310310
let clear_color = self.clear_color;
311311
let blend_state = self.blend_state;
312+
let scaling_mode = ScalingMode::PixelPerfect;
312313
let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) =
313314
create_backing_texture(
314315
&device,
@@ -322,6 +323,7 @@ impl<'req, 'dev, 'win, W: wgpu::WindowHandle + 'win> PixelsBuilder<'req, 'dev, '
322323
// Clear color and blending values
323324
clear_color,
324325
blend_state,
326+
scaling_mode,
325327
)?;
326328

327329
// Create the pixel buffer
@@ -432,6 +434,7 @@ pub(crate) fn create_backing_texture(
432434
render_texture_format: wgpu::TextureFormat,
433435
clear_color: wgpu::Color,
434436
blend_state: wgpu::BlendState,
437+
scaling_mode: ScalingMode,
435438
) -> Result<
436439
(
437440
ultraviolet::Mat4,
@@ -447,6 +450,7 @@ pub(crate) fn create_backing_texture(
447450
let scaling_matrix_inverse = ScalingMatrix::new(
448451
(width as f32, height as f32),
449452
(surface_size.width as f32, surface_size.height as f32),
453+
scaling_mode,
450454
)
451455
.transform
452456
.inversed();
@@ -477,6 +481,7 @@ pub(crate) fn create_backing_texture(
477481
render_texture_format,
478482
clear_color,
479483
blend_state,
484+
scaling_mode,
480485
);
481486

482487
let texture_format_size = texture_format_size(backing_texture_format);

src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ struct SurfaceSize {
5555
height: u32,
5656
}
5757

58+
/// The scaling mode controls the scaling behavior of [`renderers::ScalingRenderer`].
59+
#[derive(Debug, Copy, Clone)]
60+
pub enum ScalingMode {
61+
/// The buffer is scaled up, if needed, to the nearest integer multiple of the buffer size.
62+
PixelPerfect,
63+
/// Fill the screen while preserving aspect ratio. The renderer effectively scales the buffer
64+
/// to the nearest integer multiple first, then linearly interpolates to fit.
65+
Fill,
66+
}
67+
5868
/// Provides the internal state for custom shaders.
5969
///
6070
/// A reference to this struct is given to the `render_function` closure when using
@@ -295,6 +305,23 @@ impl<'win> Pixels<'win> {
295305
self.context.scaling_renderer.clear_color = color;
296306
}
297307

308+
/// Set the scaling mode.
309+
///
310+
/// Controls how the pixel buffer is scaled to the screen.
311+
///
312+
/// ```no_run
313+
/// # use pixels::{Pixels, ScalingMode};
314+
/// # let window = pixels_mocks::Window;
315+
/// # let surface_texture = pixels::SurfaceTexture::new(1920, 1080, &window);
316+
/// let mut pixels = Pixels::new(640, 480, surface_texture)?;
317+
/// // Scale the buffer up to fill the screen while preserving aspect ratio.
318+
/// pixels.set_scaling_mode(ScalingMode::Fill);
319+
/// # Ok::<(), pixels::Error>(())
320+
/// ```
321+
pub fn set_scaling_mode(&mut self, scaling_mode: ScalingMode) {
322+
self.context.scaling_renderer.scaling_mode = scaling_mode;
323+
}
324+
298325
/// Returns a reference of the `wgpu` adapter used by the crate.
299326
///
300327
/// The adapter can be used to retrieve runtime information about the host system
@@ -343,6 +370,7 @@ impl<'win> Pixels<'win> {
343370
self.render_texture_format,
344371
self.context.scaling_renderer.clear_color,
345372
self.blend_state,
373+
self.context.scaling_renderer.scaling_mode,
346374
)?;
347375

348376
self.scaling_matrix_inverse = scaling_matrix_inverse;
@@ -387,6 +415,7 @@ impl<'win> Pixels<'win> {
387415
self.context.texture_extent.height as f32,
388416
),
389417
(width as f32, height as f32),
418+
self.context.scaling_renderer.scaling_mode,
390419
)
391420
.transform
392421
.inversed();

0 commit comments

Comments
 (0)