Fix invalid texture sizes (#250)

- Makes methods fallible when they create textures.
- Correctly handle window resize in fltk example.
- TODO: tests
- Closes #240
This commit is contained in:
Jay Oster 2022-12-17 20:21:28 -08:00 committed by GitHub
parent b4fc48c740
commit 6f4fa6c967
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 209 additions and 112 deletions

View file

@ -45,11 +45,8 @@ fn main() -> Result<(), Error> {
// The one and only event that winit_input_helper doesn't have for us...
if let Event::RedrawRequested(_) = event {
life.draw(pixels.get_frame_mut());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {}", err);
*control_flow = ControlFlow::Exit;
return;
}
@ -98,17 +95,17 @@ fn main() -> Result<(), Error> {
.unwrap_or_default();
if input.mouse_pressed(0) {
debug!("Mouse click at {:?}", mouse_cell);
debug!("Mouse click at {mouse_cell:?}");
draw_state = Some(life.toggle(mouse_cell.0, mouse_cell.1));
} else if let Some(draw_alive) = draw_state {
let release = input.mouse_released(0);
let held = input.mouse_held(0);
debug!("Draw at {:?} => {:?}", mouse_prev_cell, mouse_cell);
debug!("Mouse held {:?}, release {:?}", held, release);
debug!("Draw at {mouse_prev_cell:?} => {mouse_cell:?}");
debug!("Mouse held {held:?}, release {release:?}");
// If they either released (finishing the drawing) or are still
// in the middle of drawing, keep going.
if release || held {
debug!("Draw line of {:?}", draw_alive);
debug!("Draw line of {draw_alive:?}");
life.set_line(
mouse_prev_cell.0,
mouse_prev_cell.1,
@ -125,7 +122,11 @@ fn main() -> Result<(), Error> {
}
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
if !paused || input.key_pressed_os(VirtualKeyCode::Space) {
life.update();

View file

@ -45,7 +45,7 @@ fn main() -> Result<(), Error> {
};
let mut world = World::new();
let mut time = 0.0;
let mut noise_renderer = NoiseRenderer::new(&pixels, window_size.width, window_size.height);
let mut noise_renderer = NoiseRenderer::new(&pixels, window_size.width, window_size.height)?;
event_loop.run(move |event, _, control_flow| {
// Draw the current frame
@ -64,10 +64,8 @@ fn main() -> Result<(), Error> {
Ok(())
});
if render_result
.map_err(|e| error!("pixels.render_with() failed: {}", e))
.is_err()
{
if let Err(err) = render_result {
error!("pixels.render_with() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -83,8 +81,16 @@ 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, size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
if let Err(err) = noise_renderer.resize(&pixels, size.width, size.height) {
error!("noise_renderer.resize() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
// Update internal state and request a redraw

View file

@ -1,4 +1,8 @@
use pixels::wgpu::{self, util::DeviceExt};
use pixels::{
check_texture_size,
wgpu::{self, util::DeviceExt},
TextureError,
};
pub(crate) struct NoiseRenderer {
texture_view: wgpu::TextureView,
@ -11,14 +15,18 @@ pub(crate) struct NoiseRenderer {
}
impl NoiseRenderer {
pub(crate) fn new(pixels: &pixels::Pixels, width: u32, height: u32) -> Self {
pub(crate) fn new(
pixels: &pixels::Pixels,
width: u32,
height: u32,
) -> Result<Self, TextureError> {
let device = pixels.device();
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(pixels, width, height);
let texture_view = create_texture_view(pixels, width, height)?;
// Create a texture sampler with nearest neighbor
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
@ -139,7 +147,7 @@ impl NoiseRenderer {
multiview: None,
});
Self {
Ok(Self {
texture_view,
sampler,
bind_group_layout,
@ -147,19 +155,20 @@ impl NoiseRenderer {
render_pipeline,
time_buffer,
vertex_buffer,
}
})
}
pub(crate) fn get_texture_view(&self) -> &wgpu::TextureView {
&self.texture_view
}
pub(crate) fn resize(&mut self, pixels: &pixels::Pixels, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
self.texture_view = create_texture_view(pixels, width, height);
pub(crate) fn resize(
&mut self,
pixels: &pixels::Pixels,
width: u32,
height: u32,
) -> Result<(), TextureError> {
self.texture_view = create_texture_view(pixels, width, height)?;
self.bind_group = create_bind_group(
pixels.device(),
&self.bind_group_layout,
@ -167,6 +176,8 @@ impl NoiseRenderer {
&self.sampler,
&self.time_buffer,
);
Ok(())
}
pub(crate) fn update(&self, queue: &wgpu::Queue, time: f32) {
@ -199,8 +210,13 @@ impl NoiseRenderer {
}
}
fn create_texture_view(pixels: &pixels::Pixels, width: u32, height: u32) -> wgpu::TextureView {
fn create_texture_view(
pixels: &pixels::Pixels,
width: u32,
height: u32,
) -> Result<wgpu::TextureView, TextureError> {
let device = pixels.device();
check_texture_size(device, width, height)?;
let texture_descriptor = wgpu::TextureDescriptor {
label: None,
size: pixels::wgpu::Extent3d {
@ -215,9 +231,9 @@ fn create_texture_view(pixels: &pixels::Pixels, width: u32, height: u32) -> wgpu
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
};
device
Ok(device
.create_texture(&texture_descriptor)
.create_view(&wgpu::TextureViewDescriptor::default())
.create_view(&wgpu::TextureViewDescriptor::default()))
}
fn create_bind_group(

View file

@ -76,10 +76,8 @@ fn main() -> Result<(), Error> {
});
// Basic error handling
if render_result
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = render_result {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -103,12 +101,20 @@ fn main() -> Result<(), Error> {
if let Some(size) = input.window_resized() {
if size.width > 0 && size.height > 0 {
// Resize the surface texture
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
// Resize the world
let LogicalSize { width, height } = size.to_logical(scale_factor);
world.resize(width, height);
pixels.resize_buffer(width, height);
if let Err(err) = pixels.resize_buffer(width, height) {
error!("pixels.resize_buffer() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
}

View file

@ -140,8 +140,8 @@ fn main() -> Result<(), Error> {
move |g| {
// Drawing
g.game.world.draw(g.game.pixels.get_frame_mut());
if let Err(e) = g.game.pixels.render() {
error!("pixels.render() failed: {}", e);
if let Err(err) = g.game.pixels.render() {
error!("pixels.render() failed: {err}");
g.exit();
}
@ -166,7 +166,10 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = g.game.input.window_resized() {
g.game.pixels.resize_surface(size.width, size.height);
if let Err(err) = g.game.pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
g.exit();
}
}
}
},

View file

@ -71,7 +71,11 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
framework.resize(size.width, size.height);
}
@ -105,10 +109,8 @@ fn main() -> Result<(), Error> {
});
// Basic error handling
if render_result
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = render_result {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
}
}

View file

@ -2,9 +2,10 @@
#![forbid(unsafe_code)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use fltk::{app, enums::Event, prelude::*, window::Window};
use fltk::{app, prelude::*, window::Window};
use log::error;
use pixels::{Error, Pixels, SurfaceTexture};
use std::{cell::RefCell, rc::Rc};
const WIDTH: u32 = 600;
const HEIGHT: u32 = 400;
@ -26,6 +27,7 @@ fn main() -> Result<(), Error> {
let mut win = Window::default()
.with_size(WIDTH as i32, HEIGHT as i32)
.with_label("Hello Pixels");
win.make_resizable(true);
win.end();
win.show();
@ -33,29 +35,39 @@ fn main() -> Result<(), Error> {
let pixel_width = win.pixel_w() as u32;
let pixel_height = win.pixel_h() as u32;
let surface_texture = SurfaceTexture::new(pixel_width, pixel_height, &win);
Pixels::new(WIDTH, HEIGHT, surface_texture)?
};
let mut world = World::new();
while app.wait() {
// Handle window events
if app::event() == Event::Resize {
let pixel_width = win.pixel_w() as u32;
let pixel_height = win.pixel_h() as u32;
pixels.resize_surface(pixel_width, pixel_height);
}
// Handle resize events
let surface_size = Rc::new(RefCell::new(None));
let surface_resize = surface_size.clone();
win.resize_callback(move |win, _x, _y, width, height| {
let scale_factor = win.pixels_per_unit();
let width = (width as f32 * scale_factor) as u32;
let height = (height as f32 * scale_factor) as u32;
surface_resize.borrow_mut().replace((width, height));
});
while app.wait() {
// Update internal state
world.update();
// Resize the window
if let Some((width, height)) = surface_size.borrow_mut().take() {
if let Err(err) = pixels.resize_surface(width, height) {
error!("pixels.resize_surface() failed: {err}");
app.quit();
}
}
// Draw the current frame
world.draw(pixels.get_frame_mut());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
app.quit();
}

View file

@ -67,7 +67,10 @@ fn main() -> Result<(), Error> {
// Resize the window
WindowEvent::Resized(size) => {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
}
}
_ => {}
@ -82,11 +85,8 @@ fn main() -> Result<(), Error> {
// Draw the current frame
Event::RedrawRequested(_) => {
world.draw(pixels.get_frame_mut());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
}
}

View file

@ -111,11 +111,8 @@ async fn run() {
// Draw the current frame
if let Event::RedrawRequested(_) = event {
world.draw(pixels.get_frame_mut());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -131,7 +128,11 @@ async fn run() {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
// Update internal state and request a redraw

View file

@ -46,11 +46,8 @@ fn main() -> Result<(), Error> {
// Draw the current frame
if let Event::RedrawRequested(_) = event {
world.draw(pixels.get_frame_mut());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -66,7 +63,11 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
// Update internal state and request a redraw

View file

@ -56,11 +56,8 @@ fn main() -> Result<(), Error> {
dst[3] = (src >> 24) as u8;
}
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -76,7 +73,11 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
// Update internal state and request a redraw

View file

@ -44,11 +44,8 @@ fn main() -> Result<(), Error> {
// Draw the current frame
if let Event::RedrawRequested(_) = event {
pixels.get_frame_mut().copy_from_slice(drawing.data());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
if let Err(err) = pixels.render() {
error!("pixels.render() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
@ -64,7 +61,11 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
if let Err(err) = pixels.resize_surface(size.width, size.height) {
error!("pixels.resize_surface() failed: {err}");
*control_flow = ControlFlow::Exit;
return;
}
}
// Update internal state and request a redraw

View file

@ -1,6 +1,5 @@
use crate::renderers::{ScalingMatrix, ScalingRenderer};
use crate::SurfaceSize;
use crate::{Error, Pixels, PixelsContext, SurfaceTexture};
use crate::{Error, Pixels, PixelsContext, SurfaceSize, SurfaceTexture, TextureError};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
/// A builder to help create customized pixel buffers.
@ -321,7 +320,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle + HasRawDisplayHandle>
render_texture_format,
clear_color,
blend_state,
);
)?;
// Create the pixel buffer
let mut pixels = Vec::with_capacity(pixels_buffer_size);
@ -397,6 +396,28 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle + HasRawDisplayHandle>
}
}
/// Compare the given size to the limits defined by `device`.
///
/// # Errors
///
/// - [`TextureError::TextureWidth`] when `width` is 0 or greater than GPU texture limits.
/// - [`TextureError::TextureHeight`] when `height` is 0 or greater than GPU texture limits.
pub fn check_texture_size(
device: &wgpu::Device,
width: u32,
height: u32,
) -> Result<(), TextureError> {
let limits = device.limits();
if width == 0 || width > limits.max_texture_dimension_2d {
return Err(TextureError::TextureWidth(width));
}
if height == 0 || height > limits.max_texture_dimension_2d {
return Err(TextureError::TextureHeight(height));
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_backing_texture(
device: &wgpu::Device,
@ -407,13 +428,18 @@ pub(crate) fn create_backing_texture(
render_texture_format: wgpu::TextureFormat,
clear_color: wgpu::Color,
blend_state: wgpu::BlendState,
) -> (
ultraviolet::Mat4,
wgpu::Extent3d,
wgpu::Texture,
ScalingRenderer,
usize,
) {
) -> Result<
(
ultraviolet::Mat4,
wgpu::Extent3d,
wgpu::Texture,
ScalingRenderer,
usize,
),
TextureError,
> {
check_texture_size(device, width, height)?;
let scaling_matrix_inverse = ScalingMatrix::new(
(width as f32, height as f32),
(surface_size.width as f32, surface_size.height as f32),
@ -451,13 +477,13 @@ pub(crate) fn create_backing_texture(
let texture_format_size = get_texture_format_size(backing_texture_format);
let pixels_buffer_size = ((width * height) as f32 * texture_format_size) as usize;
(
Ok((
scaling_matrix_inverse,
texture_extent,
texture,
scaling_renderer,
pixels_buffer_size,
)
))
}
#[rustfmt::skip]

View file

@ -31,7 +31,7 @@
#![deny(clippy::all)]
pub use crate::builder::PixelsBuilder;
pub use crate::builder::{check_texture_size, PixelsBuilder};
pub use crate::renderers::ScalingRenderer;
pub use raw_window_handle;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
@ -111,6 +111,7 @@ pub struct Pixels {
/// All the ways in which creating a pixel buffer can fail.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
/// No suitable [`wgpu::Adapter`] found
#[error("No suitable `wgpu::Adapter` found.")]
@ -121,6 +122,9 @@ pub enum Error {
/// Equivalent to [`wgpu::SurfaceError`]
#[error("The GPU failed to acquire a surface frame.")]
Surface(wgpu::SurfaceError),
/// Equivalent to [`TextureError`]
#[error("Texture creation failed: {0}")]
InvalidTexture(#[from] TextureError),
/// User-defined error from custom render function
#[error("User-defined error.")]
UserDefined(#[from] DynError),
@ -128,6 +132,18 @@ pub enum Error {
type DynError = Box<dyn std::error::Error + Send + Sync + 'static>;
/// All the ways in which creating a texture can fail.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum TextureError {
/// Unable to create a backing texture; Width is either 0 or greater than GPU limits
#[error("Texture width is invalid: {0}")]
TextureWidth(u32),
/// Unable to create a backing texture; Height is either 0 or greater than GPU limits
#[error("Texture height is invalid: {0}")]
TextureHeight(u32),
}
impl<'win, W: HasRawWindowHandle + HasRawDisplayHandle> SurfaceTexture<'win, W> {
/// Create a logical texture for a window surface.
///
@ -268,13 +284,11 @@ impl Pixels {
/// Call this method to change the virtual screen resolution. E.g. when you want your pixel
/// buffer to be resized from `640x480` to `800x600`.
///
/// # Panics
/// # Errors
///
/// Panics when `width` or `height` are 0.
pub fn resize_buffer(&mut self, width: u32, height: u32) {
assert!(width > 0);
assert!(height > 0);
/// - [`TextureError::TextureWidth`] when `width` is 0 or greater than GPU texture limits.
/// - [`TextureError::TextureHeight`] when `height` is 0 or greater than GPU texture limits.
pub fn resize_buffer(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
// Recreate the backing texture
let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) =
builder::create_backing_texture(
@ -288,7 +302,7 @@ impl Pixels {
self.render_texture_format,
self.context.scaling_renderer.clear_color,
self.blend_state,
);
)?;
self.scaling_matrix_inverse = scaling_matrix_inverse;
self.context.texture_extent = texture_extent;
@ -298,6 +312,8 @@ impl Pixels {
// Resize the pixel buffer
self.pixels
.resize_with(pixels_buffer_size, Default::default);
Ok(())
}
/// Resize the surface upon which the pixel buffer texture is rendered.
@ -311,10 +327,13 @@ impl Pixels {
///
/// Call this method in response to a resize event from your window manager. The size expected
/// is in physical pixel units. Does nothing when `width` or `height` are 0.
pub fn resize_surface(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
///
/// # Errors
///
/// - [`TextureError::TextureWidth`] when `width` is 0 or greater than GPU texture limits.
/// - [`TextureError::TextureHeight`] when `height` is 0 or greater than GPU texture limits.
pub fn resize_surface(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
check_texture_size(&self.context.device, width, height)?;
// Update SurfaceTexture dimensions
self.surface_size.width = width;
@ -338,6 +357,8 @@ impl Pixels {
self.context
.scaling_renderer
.resize(&self.context.queue, width, height);
Ok(())
}
/// Draw this pixel buffer to the configured [`SurfaceTexture`].