Render API v2 (#112)

* WIP: Render API v2

* Fix doctests

* Expose all of PixelsContext (#110)

* Fix ScalingRenderer::new() taking &mut Device

* Replace getters with direct access to &mut PixelsContext

* Fix wrong reference type

* Fix unneeded mut

* Remove unnecessary mutable borrow, resurrect the shorter getter methods

* Initial port to wgpu master (0.6)
Surface creation is broken (see examples)
Does not support compressed textures

* Fix SurfaceTexture and examples

* Add support for compressed texture formats

* resize doesn't need mutability

* Update documentation

* Update wgpu

* Prepare release

* Goodbye Travis! Thanks for all the fish

Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com>
This commit is contained in:
Jay Oster 2020-08-20 16:49:19 -07:00 committed by GitHub
parent 01d32c11f0
commit 265ba2e5b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 365 additions and 305 deletions

View file

@ -1,22 +0,0 @@
language: rust
dist: bionic
rust:
# MSRV
- 1.41.0
# Stable release channel
- stable
matrix:
fast_finish: true
before_script:
- rustup component add clippy
- rustup component add rustfmt
- sudo apt-get update
- sudo apt-get -y install libsdl2-dev
script:
- cargo clippy --all -- -D warnings
- cargo test --all
- cargo fmt --all -- --check

View file

@ -1,7 +1,7 @@
[package] [package]
name = "pixels" name = "pixels"
description = "A tiny library providing a GPU-powered pixel frame buffer." description = "A tiny library providing a GPU-powered pixel frame buffer."
version = "0.1.0" version = "0.2.0"
authors = ["Jay Oster <jay@kodewerx.org>"] authors = ["Jay Oster <jay@kodewerx.org>"]
edition = "2018" edition = "2018"
repository = "https://github.com/parasyte/pixels" repository = "https://github.com/parasyte/pixels"
@ -19,17 +19,19 @@ include = [
] ]
[dependencies] [dependencies]
thiserror = "1.0.15" pixels-dragons = { path = "internals/pixels-dragons" }
wgpu = "0.5.0"
pollster = "0.2" pollster = "0.2"
ultraviolet = "0.4.6" raw-window-handle = "0.3"
thiserror = "1.0"
ultraviolet = "0.4"
wgpu = "0.6"
[dev-dependencies] [dev-dependencies]
pixels-mocks = { path = "pixels-mocks" } pixels-mocks = { path = "internals/pixels-mocks" }
winit = "0.22.0" winit = "0.22"
[workspace] [workspace]
members = [ members = [
"examples/*", "examples/*",
"pixels-mocks", "internals/*",
] ]

View file

@ -15,10 +15,10 @@ fn main() -> Result<(), Error> {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new(); let mut input = WinitInputHelper::new();
let (window, surface, p_width, p_height, mut _hidpi_factor) = let (window, p_width, p_height, mut _hidpi_factor) =
create_window("Conway's Game of Life", &event_loop); create_window("Conway's Game of Life", &event_loop);
let surface_texture = SurfaceTexture::new(p_width, p_height, surface); let surface_texture = SurfaceTexture::new(p_width, p_height, &window);
let mut life = ConwayGrid::new_random(SCREEN_WIDTH as usize, SCREEN_HEIGHT as usize); let mut life = ConwayGrid::new_random(SCREEN_WIDTH as usize, SCREEN_HEIGHT as usize);
let mut pixels = Pixels::new(SCREEN_WIDTH, SCREEN_HEIGHT, surface_texture)?; let mut pixels = Pixels::new(SCREEN_WIDTH, SCREEN_HEIGHT, surface_texture)?;
@ -137,7 +137,7 @@ fn main() -> Result<(), Error> {
fn create_window( fn create_window(
title: &str, title: &str,
event_loop: &EventLoop<()>, event_loop: &EventLoop<()>,
) -> (winit::window::Window, pixels::wgpu::Surface, u32, u32, f64) { ) -> (winit::window::Window, u32, u32, f64) {
// Create a hidden window so we can estimate a good default window size // Create a hidden window so we can estimate a good default window size
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_visible(false) .with_visible(false)
@ -171,12 +171,10 @@ fn create_window(
window.set_outer_position(center); window.set_outer_position(center);
window.set_visible(true); window.set_visible(true);
let surface = pixels::wgpu::Surface::create(&window);
let size = default_size.to_physical::<f64>(hidpi_factor); let size = default_size.to_physical::<f64>(hidpi_factor);
( (
window, window,
surface,
size.width.round() as u32, size.width.round() as u32,
size.height.round() as u32, size.height.round() as u32,
hidpi_factor, hidpi_factor,

View file

@ -3,10 +3,7 @@
use crate::renderers::NoiseRenderer; use crate::renderers::NoiseRenderer;
use log::error; use log::error;
use pixels::{ use pixels::{raw_window_handle::HasRawWindowHandle, wgpu, Error, Pixels, SurfaceTexture};
wgpu::{self, Surface},
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};
@ -43,25 +40,25 @@ fn main() -> Result<(), Error> {
let mut pixels = { let mut pixels = {
let window_size = window.inner_size(); let window_size = window.inner_size();
let surface = Surface::create(&window); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, surface);
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, mut noise_renderer) = create_noise_renderer(&pixels); let (scaled_texture, noise_renderer) = create_noise_renderer(&pixels);
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
// Draw the current frame // Draw the current frame
if let Event::RedrawRequested(_) = event { if let Event::RedrawRequested(_) = event {
world.draw(pixels.get_frame()); world.draw(pixels.get_frame());
noise_renderer.update(pixels.device(), pixels.queue(), time); let render_result = pixels.render_with(|encoder, render_target, context| {
time += 1.0; context.scaling_renderer.render(encoder, &scaled_texture);
noise_renderer.update(&context.queue, time);
time += 0.01;
let render_result = pixels.render_with(|encoder, render_target, scaling_renderer| {
scaling_renderer.render(encoder, &scaled_texture);
noise_renderer.render(encoder, render_target); noise_renderer.render(encoder, render_target);
}); });
@ -142,7 +139,11 @@ impl World {
} }
} }
fn create_noise_renderer(pixels: &Pixels) -> (wgpu::TextureView, NoiseRenderer) { fn create_noise_renderer<W: HasRawWindowHandle>(
pixels: &Pixels<W>,
) -> (wgpu::TextureView, NoiseRenderer) {
let device = &pixels.device();
let texture_descriptor = wgpu::TextureDescriptor { let texture_descriptor = wgpu::TextureDescriptor {
label: None, label: None,
size: pixels::wgpu::Extent3d { size: pixels::wgpu::Extent3d {
@ -150,18 +151,16 @@ fn create_noise_renderer(pixels: &Pixels) -> (wgpu::TextureView, NoiseRenderer)
height: HEIGHT, height: HEIGHT,
depth: 1, depth: 1,
}, },
array_layer_count: 1,
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb, format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT,
}; };
let scaled_texture = pixels let scaled_texture = device
.device()
.create_texture(&texture_descriptor) .create_texture(&texture_descriptor)
.create_default_view(); .create_view(&wgpu::TextureViewDescriptor::default());
let noise_renderer = NoiseRenderer::new(pixels.device(), &scaled_texture); let noise_renderer = NoiseRenderer::new(device, &scaled_texture);
(scaled_texture, noise_renderer) (scaled_texture, noise_renderer)
} }

View file

@ -1,4 +1,4 @@
use pixels::{include_spv, wgpu}; use pixels::wgpu::{self, util::DeviceExt};
pub(crate) struct NoiseRenderer { pub(crate) struct NoiseRenderer {
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
@ -8,11 +8,12 @@ pub(crate) struct NoiseRenderer {
impl NoiseRenderer { impl NoiseRenderer {
pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self { pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self {
let vs_module = device.create_shader_module(include_spv!("../shaders/vert.spv")); let vs_module = device.create_shader_module(wgpu::include_spirv!("../shaders/vert.spv"));
let fs_module = device.create_shader_module(include_spv!("../shaders/frag.spv")); let fs_module = device.create_shader_module(wgpu::include_spirv!("../shaders/frag.spv"));
// 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"),
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
@ -21,20 +22,21 @@ impl NoiseRenderer {
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0, lod_min_clamp: 0.0,
lod_max_clamp: 1.0, lod_max_clamp: 1.0,
compare: wgpu::CompareFunction::Always, compare: None,
anisotropy_clamp: None,
}); });
// Create uniform buffer // Create uniform buffer
let time_buffer = device.create_buffer(&wgpu::BufferDescriptor { let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer u_Time"), label: Some("NoiseRenderer u_Time"),
size: 4, contents: &0.0_f32.to_ne_bytes(),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
}); });
// Create bind group // Create bind group
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None, label: None,
bindings: &[ entries: &[
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT, visibility: wgpu::ShaderStage::FRAGMENT,
@ -43,47 +45,53 @@ impl NoiseRenderer {
multisampled: false, multisampled: false,
dimension: wgpu::TextureViewDimension::D2, dimension: wgpu::TextureViewDimension::D2,
}, },
count: None,
}, },
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT, visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler { comparison: false }, ty: wgpu::BindingType::Sampler { comparison: false },
count: None,
}, },
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 2, binding: 2,
visibility: wgpu::ShaderStage::FRAGMENT, visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::UniformBuffer { dynamic: false }, ty: wgpu::BindingType::UniformBuffer {
dynamic: false,
min_binding_size: None,
},
count: None,
}, },
], ],
}); });
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None, label: None,
layout: &bind_group_layout, layout: &bind_group_layout,
bindings: &[ entries: &[
wgpu::Binding { wgpu::BindGroupEntry {
binding: 0, binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view), resource: wgpu::BindingResource::TextureView(texture_view),
}, },
wgpu::Binding { wgpu::BindGroupEntry {
binding: 1, binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler), resource: wgpu::BindingResource::Sampler(&sampler),
}, },
wgpu::Binding { wgpu::BindGroupEntry {
binding: 2, binding: 2,
resource: wgpu::BindingResource::Buffer { resource: wgpu::BindingResource::Buffer(time_buffer.slice(..)),
buffer: &time_buffer,
range: 0..4,
},
}, },
], ],
}); });
// Create pipeline // Create pipeline
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("NoiseRenderer pipeline layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
}); });
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: &pipeline_layout, label: Some("NoiseRenderer pipeline"),
layout: Some(&pipeline_layout),
vertex_stage: wgpu::ProgrammableStageDescriptor { vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module, module: &vs_module,
entry_point: "main", entry_point: "main",
@ -95,6 +103,7 @@ impl NoiseRenderer {
rasterization_state: Some(wgpu::RasterizationStateDescriptor { rasterization_state: Some(wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw, front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None, cull_mode: wgpu::CullMode::None,
clamp_depth: false,
depth_bias: 0, depth_bias: 0,
depth_bias_slope_scale: 0.0, depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0, depth_bias_clamp: 0.0,
@ -123,15 +132,8 @@ impl NoiseRenderer {
} }
} }
pub(crate) fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, time: f32) { pub(crate) fn update(&self, queue: &wgpu::Queue, time: f32) {
let mut encoder = queue.write_buffer(&self.time_buffer, 0, &time.to_ne_bytes());
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let temp_buf =
device.create_buffer_with_data(&time.to_ne_bytes(), wgpu::BufferUsage::COPY_SRC);
encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.time_buffer, 0, 4);
queue.submit(&[encoder.finish()]);
} }
pub(crate) fn render( pub(crate) fn render(
@ -143,9 +145,10 @@ impl NoiseRenderer {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: render_target, attachment: render_target,
resolve_target: None, resolve_target: None,
load_op: wgpu::LoadOp::Clear, ops: wgpu::Operations {
store_op: wgpu::StoreOp::Store, load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
clear_color: wgpu::Color::BLACK, store: true,
},
}], }],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}); });

View file

@ -25,9 +25,8 @@ fn main() -> Result<(), Error> {
.parse() .parse()
.unwrap_or(false); .unwrap_or(false);
let (window, surface, width, height, mut _hidpi_factor) = let (window, width, height, mut _hidpi_factor) = create_window("pixel invaders", &event_loop);
create_window("pixel invaders", &event_loop); let surface_texture = SurfaceTexture::new(width, height, &window);
let surface_texture = SurfaceTexture::new(width, height, surface);
let mut pixels = Pixels::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, surface_texture)?; let mut pixels = Pixels::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, surface_texture)?;
let mut invaders = World::new(generate_seed(), debug); let mut invaders = World::new(generate_seed(), debug);
let mut time = Instant::now(); let mut time = Instant::now();
@ -130,7 +129,7 @@ fn main() -> Result<(), Error> {
fn create_window( fn create_window(
title: &str, title: &str,
event_loop: &EventLoop<()>, event_loop: &EventLoop<()>,
) -> (winit::window::Window, pixels::wgpu::Surface, u32, u32, f64) { ) -> (winit::window::Window, u32, u32, f64) {
// Create a hidden window so we can estimate a good default window size // Create a hidden window so we can estimate a good default window size
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_visible(false) .with_visible(false)
@ -163,12 +162,10 @@ fn create_window(
window.set_outer_position(center); window.set_outer_position(center);
window.set_visible(true); window.set_visible(true);
let surface = pixels::wgpu::Surface::create(&window);
let size = default_size.to_physical::<f64>(hidpi_factor); let size = default_size.to_physical::<f64>(hidpi_factor);
( (
window, window,
surface,
size.width.round() as u32, size.width.round() as u32,
size.height.round() as u32, size.height.round() as u32,
hidpi_factor, hidpi_factor,

View file

@ -3,7 +3,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use beryllium::*; use beryllium::*;
use pixels::{wgpu::Surface, Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
const WIDTH: u32 = 320; const WIDTH: u32 = 320;
const HEIGHT: u32 = 240; const HEIGHT: u32 = 240;
@ -24,10 +24,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
sdl.create_raw_window("Hello Pixels", WindowPosition::Centered, WIDTH, HEIGHT, 0)?; sdl.create_raw_window("Hello Pixels", WindowPosition::Centered, WIDTH, HEIGHT, 0)?;
let mut pixels = { let mut pixels = {
let surface = Surface::create(&window);
// TODO: Beryllium does not expose the SDL2 `GetDrawableSize` APIs, so choosing the correct // TODO: Beryllium does not expose the SDL2 `GetDrawableSize` APIs, so choosing the correct
// surface texture size is not possible. // surface texture size is not possible.
let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, surface); let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, &window);
Pixels::new(WIDTH, HEIGHT, surface_texture)? Pixels::new(WIDTH, HEIGHT, surface_texture)?
}; };
let mut world = World::new(); let mut world = World::new();

View file

@ -2,7 +2,7 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use log::error; use log::error;
use pixels::{wgpu::Surface, 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};
@ -37,8 +37,7 @@ fn main() -> Result<(), Error> {
let mut pixels = { let mut pixels = {
let window_size = window.inner_size(); let window_size = window.inner_size();
let surface = Surface::create(&window); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, surface);
Pixels::new(WIDTH, HEIGHT, surface_texture)? Pixels::new(WIDTH, HEIGHT, surface_texture)?
}; };
let mut world = World::new(); let mut world = World::new();

View file

@ -0,0 +1,9 @@
[package]
name = "pixels-dragons"
version = "0.1.0"
authors = ["Jay Oster <jay@kodewerx.org>"]
edition = "2018"
[dependencies]
raw-window-handle = "0.3"
wgpu = "0.6"

View file

@ -0,0 +1,24 @@
//! Here be dragons. Abandon all hope, ye who enter.
//!
//! This is probably a bad idea. The purpose of this crate is to move all `unsafe` invocations
//! into a single location and provide a faux safe interface that can be accessed by safe code with
//! `#![forbid(unsafe_code)]`
//!
//! This crate is only intended to be used by `pixels`.
#![deny(clippy::all)]
use raw_window_handle::HasRawWindowHandle;
use wgpu::{Instance, Surface};
/// Create a [`wgpu::Surface`] from the given window handle.
///
/// # Safety
///
/// The window handle must be valid, or very bad things will happen.
pub fn surface_from_window_handle<W: HasRawWindowHandle>(
instance: &Instance,
window: &W,
) -> Surface {
unsafe { instance.create_surface(window) }
}

View file

@ -7,7 +7,8 @@
//! //!
//! The GPU interface is offered by [`wgpu`](https://crates.io/crates/wgpu), and is re-exported for //! The GPU interface is offered by [`wgpu`](https://crates.io/crates/wgpu), and is re-exported for
//! your convenience. Use a windowing framework or context manager of your choice; //! your convenience. Use a windowing framework or context manager of your choice;
//! [`winit`](https://crates.io/crates/winit) is a good place to start. //! [`winit`](https://crates.io/crates/winit) is a good place to start. Any windowing framework that
//! uses [`raw-window-handle`](https://crates.io/crates/raw-window-handle) will work.
//! //!
//! # Environment variables //! # Environment variables
//! //!
@ -28,43 +29,74 @@
#![deny(clippy::all)] #![deny(clippy::all)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use std::env;
pub use crate::macros::*;
pub use crate::renderers::ScalingRenderer; pub use crate::renderers::ScalingRenderer;
use thiserror::Error; pub use raw_window_handle;
pub use wgpu; pub use wgpu;
mod macros; use pixels_dragons::surface_from_window_handle;
use raw_window_handle::HasRawWindowHandle;
use std::env;
use thiserror::Error;
mod renderers; mod renderers;
/// A logical texture for a window surface. /// A logical texture for a window surface.
#[derive(Debug)] #[derive(Debug)]
pub struct SurfaceTexture { pub struct SurfaceTexture<'win, W: HasRawWindowHandle> {
surface: wgpu::Surface, window: &'win W,
size: SurfaceSize,
}
/// A logical texture size for a window surface.
#[derive(Debug)]
pub struct SurfaceSize {
width: u32, width: u32,
height: u32, height: u32,
} }
/// Provides the internal state for custom shaders.
///
/// A reference to this struct is given to the `render_function` closure when using
/// [`Pixels::render_with`].
#[derive(Debug)]
pub struct PixelsContext {
/// The `Device` allows creating GPU resources.
pub device: wgpu::Device,
/// The `Queue` provides access to the GPU command queue.
pub queue: wgpu::Queue,
surface: wgpu::Surface,
swap_chain: wgpu::SwapChain,
/// This is the texture that your raw data is copied to by [`Pixels::render`] or
/// [`Pixels::render_with`].
pub texture: wgpu::Texture,
/// Provides access to the texture size.
pub texture_extent: wgpu::Extent3d,
/// Defines the "data rate" for the raw texture data. This is effectively the "bytes per pixel"
/// count.
///
/// Compressed textures may have less than one byte per pixel.
pub texture_format_size: f32,
/// A default renderer to scale the input texture to the screen size.
pub scaling_renderer: ScalingRenderer,
}
/// Represents a 2D pixel buffer with an explicit image resolution. /// Represents a 2D pixel buffer with an explicit image resolution.
/// ///
/// See [`PixelsBuilder`] for building a customized pixel buffer. /// See [`PixelsBuilder`] for building a customized pixel buffer.
#[derive(Debug)] #[derive(Debug)]
pub struct Pixels { pub struct Pixels<W: HasRawWindowHandle> {
// WGPU state context: PixelsContext,
device: wgpu::Device, surface_size: SurfaceSize,
queue: wgpu::Queue,
swap_chain: wgpu::SwapChain,
surface_texture: SurfaceTexture,
present_mode: wgpu::PresentMode, present_mode: wgpu::PresentMode,
_phantom: std::marker::PhantomData<W>,
// A default renderer to scale the input texture to the screen size // Pixel buffer
scaling_renderer: ScalingRenderer,
// Texture state for the texel upload
texture: wgpu::Texture,
texture_extent: wgpu::Extent3d,
texture_format_size: u32,
pixels: Vec<u8>, pixels: Vec<u8>,
// The inverse of the scaling matrix used by the renderer // The inverse of the scaling matrix used by the renderer
@ -73,7 +105,7 @@ pub struct Pixels {
} }
/// A builder to help create customized pixel buffers. /// A builder to help create customized pixel buffers.
pub struct PixelsBuilder<'req> { pub struct PixelsBuilder<'req, 'win, W: HasRawWindowHandle> {
request_adapter_options: Option<wgpu::RequestAdapterOptions<'req>>, request_adapter_options: Option<wgpu::RequestAdapterOptions<'req>>,
device_descriptor: wgpu::DeviceDescriptor, device_descriptor: wgpu::DeviceDescriptor,
backend: wgpu::BackendBit, backend: wgpu::BackendBit,
@ -81,7 +113,7 @@ pub struct PixelsBuilder<'req> {
height: u32, height: u32,
pixel_aspect_ratio: f64, pixel_aspect_ratio: f64,
present_mode: wgpu::PresentMode, present_mode: wgpu::PresentMode,
surface_texture: SurfaceTexture, surface_texture: SurfaceTexture<'win, W>,
texture_format: wgpu::TextureFormat, texture_format: wgpu::TextureFormat,
} }
@ -89,14 +121,17 @@ pub struct PixelsBuilder<'req> {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// No suitable [`wgpu::Adapter`] found /// No suitable [`wgpu::Adapter`] found
#[error("No suitable `wgpu::Adapter` found")] #[error("No suitable `wgpu::Adapter` found.")]
AdapterNotFound, AdapterNotFound,
/// Equivalent to [`wgpu::TimeOut`] /// Equivalent to [`wgpu::RequestDeviceError`]
#[error("The GPU timed out when attempting to acquire the next texture or if a previous output is still alive.")] #[error("No wgpu::Device found.")]
Timeout, DeviceNotFound(wgpu::RequestDeviceError),
/// Equivalent to [`wgpu::SwapChainError`]
#[error("The GPU failed to acquire a swapchain frame.")]
Swapchain(wgpu::SwapChainError),
} }
impl SurfaceTexture { impl<'win, W: HasRawWindowHandle> SurfaceTexture<'win, W> {
/// Create a logical texture for a window surface. /// Create a logical texture for a window surface.
/// ///
/// It is recommended (but not required) that the `width` and `height` are equivalent to the /// It is recommended (but not required) that the `width` and `height` are equivalent to the
@ -105,46 +140,43 @@ impl SurfaceTexture {
/// # Examples /// # Examples
/// ///
/// ```no_run /// ```no_run
/// use pixels::{wgpu::Surface, SurfaceTexture}; /// use pixels::SurfaceTexture;
/// use winit::event_loop::EventLoop; /// use winit::event_loop::EventLoop;
/// use winit::window::Window; /// use winit::window::Window;
/// ///
/// let event_loop = EventLoop::new(); /// let event_loop = EventLoop::new();
/// let window = Window::new(&event_loop).unwrap(); /// let window = Window::new(&event_loop).unwrap();
/// let surface = Surface::create(&window);
/// let size = window.inner_size(); /// let size = window.inner_size();
/// ///
/// let width = size.width; /// let width = size.width;
/// let height = size.height; /// let height = size.height;
/// ///
/// let surface_texture = SurfaceTexture::new(width, height, surface); /// let surface_texture = SurfaceTexture::new(width, height, &window);
/// # Ok::<(), pixels::Error>(()) /// # Ok::<(), pixels::Error>(())
/// ``` /// ```
/// ///
/// # Panics /// # Panics
/// ///
/// Panics when `width` or `height` are 0. /// Panics when `width` or `height` are 0.
pub fn new(width: u32, height: u32, surface: wgpu::Surface) -> SurfaceTexture { pub fn new(width: u32, height: u32, window: &'win W) -> SurfaceTexture<'win, W> {
assert!(width > 0); assert!(width > 0);
assert!(height > 0); assert!(height > 0);
SurfaceTexture { let size = SurfaceSize { width, height };
surface,
width, SurfaceTexture { window, size }
height,
}
} }
} }
impl Pixels { impl<'win, W: HasRawWindowHandle> Pixels<W> {
/// Create a pixel buffer instance with default options. /// Create a pixel buffer instance with default options.
/// ///
/// # Examples /// # Examples
/// ///
/// ```no_run /// ```no_run
/// # use pixels::Pixels; /// # use pixels::Pixels;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// let mut pixels = Pixels::new(320, 240, surface_texture)?; /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
/// # Ok::<(), pixels::Error>(()) /// # Ok::<(), pixels::Error>(())
/// ``` /// ```
@ -156,7 +188,11 @@ impl Pixels {
/// # Panics /// # Panics
/// ///
/// Panics when `width` or `height` are 0. /// Panics when `width` or `height` are 0.
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture) -> Result<Pixels, Error> { pub fn new(
width: u32,
height: u32,
surface_texture: SurfaceTexture<'win, W>,
) -> Result<Pixels<W>, Error> {
PixelsBuilder::new(width, height, surface_texture).build() PixelsBuilder::new(width, height, surface_texture).build()
} }
@ -169,14 +205,14 @@ impl Pixels {
/// is in physical pixel units. /// is in physical pixel units.
pub fn resize(&mut self, width: u32, height: u32) { pub fn resize(&mut self, width: u32, height: u32) {
// Update SurfaceTexture dimensions // Update SurfaceTexture dimensions
self.surface_texture.width = width; self.surface_size.width = width;
self.surface_texture.height = height; self.surface_size.height = height;
// Update ScalingMatrix for mouse transformation // Update ScalingMatrix for mouse transformation
self.scaling_matrix_inverse = renderers::ScalingMatrix::new( self.scaling_matrix_inverse = renderers::ScalingMatrix::new(
( (
self.texture_extent.width as f32, self.context.texture_extent.width as f32,
self.texture_extent.height as f32, self.context.texture_extent.height as f32,
), ),
(width as f32, height as f32), (width as f32, height as f32),
) )
@ -184,38 +220,35 @@ impl Pixels {
.inversed(); .inversed();
// Recreate the swap chain // Recreate the swap chain
self.swap_chain = self.device.create_swap_chain( self.context.swap_chain = self.context.device.create_swap_chain(
&self.surface_texture.surface, &self.context.surface,
&wgpu::SwapChainDescriptor { &wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb, format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: self.surface_texture.width, width: self.surface_size.width,
height: self.surface_texture.height, height: self.surface_size.height,
present_mode: self.present_mode, present_mode: self.present_mode,
}, },
); );
// Update state for all render passes // Update state for all render passes
let mut encoder = self self.context
.device .scaling_renderer
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); .resize(&self.context.queue, width, height);
self.scaling_renderer
.resize(&mut self.device, &mut encoder, width, height);
self.queue.submit(&[encoder.finish()]);
} }
/// Draw this pixel buffer to the configured [`SurfaceTexture`]. /// Draw this pixel buffer to the configured [`SurfaceTexture`].
/// ///
/// # Errors /// # Errors
/// ///
/// Returns an error when [`wgpu::SwapChain::get_next_texture`] times out. /// Returns an error when [`wgpu::SwapChain::get_current_frame`] fails.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use pixels::Pixels; /// # use pixels::Pixels;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// let mut pixels = Pixels::new(320, 240, surface_texture)?; /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
/// ///
/// // Clear the pixel buffer /// // Clear the pixel buffer
@ -232,8 +265,8 @@ impl Pixels {
/// # Ok::<(), pixels::Error>(()) /// # Ok::<(), pixels::Error>(())
/// ``` /// ```
pub fn render(&mut self) -> Result<(), Error> { pub fn render(&mut self) -> Result<(), Error> {
self.render_with(|encoder, render_target, scaling_renderer| { self.render_with(|encoder, render_target, context| {
scaling_renderer.render(encoder, render_target); context.scaling_renderer.render(encoder, render_target);
}) })
} }
@ -241,18 +274,19 @@ impl Pixels {
/// render function. /// render function.
/// ///
/// Provides access to a [`wgpu::CommandEncoder`], a [`wgpu::TextureView`] from the swapchain /// Provides access to a [`wgpu::CommandEncoder`], a [`wgpu::TextureView`] from the swapchain
/// which you can use to render to the screen, and the default [`ScalingRenderer`]. /// which you can use to render to the screen, and a [`PixelsContext`] with all of the internal
/// `wgpu` context.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns an error when [`wgpu::SwapChain::get_next_texture`] times out. /// Returns an error when [`wgpu::SwapChain::get_current_frame`] fails.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use pixels::Pixels; /// # use pixels::Pixels;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// let mut pixels = Pixels::new(320, 240, surface_texture)?; /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
/// ///
/// // Clear the pixel buffer /// // Clear the pixel buffer
@ -265,54 +299,51 @@ impl Pixels {
/// } /// }
/// ///
/// // Draw it to the `SurfaceTexture` /// // Draw it to the `SurfaceTexture`
/// pixels.render_with(|encoder, render_target, scaling_renderer| { /// pixels.render_with(|encoder, render_target, context| {
/// scaling_renderer.render(encoder, render_target); /// context.scaling_renderer.render(encoder, render_target);
/// // etc... /// // etc...
/// }); /// });
/// # Ok::<(), pixels::Error>(()) /// # Ok::<(), pixels::Error>(())
/// ``` /// ```
pub fn render_with<F>(&mut self, render_function: F) -> Result<(), Error> pub fn render_with<F>(&mut self, render_function: F) -> Result<(), Error>
where where
F: FnOnce(&mut wgpu::CommandEncoder, &wgpu::TextureView, &ScalingRenderer), F: FnOnce(&mut wgpu::CommandEncoder, &wgpu::TextureView, &PixelsContext),
{ {
// TODO: Center frame buffer in surface // TODO: Center frame buffer in surface
let frame = self let frame = self
.context
.swap_chain .swap_chain
.get_next_texture() .get_current_frame()
.map_err(|_| Error::Timeout)?; .map_err(Error::Swapchain)?;
let mut encoder = self let mut encoder =
self.context
.device .device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); .create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("pixels_command_encoder"),
});
// Update the pixel buffer texture view // Update the pixel buffer texture view
let mapped = self.device.create_buffer_mapped(&wgpu::BufferDescriptor { let bytes_per_row =
label: None, (self.context.texture_extent.width as f32 * self.context.texture_format_size) as u32;
size: self.pixels.len() as u64, self.context.queue.write_texture(
usage: wgpu::BufferUsage::COPY_SRC,
});
mapped.data.copy_from_slice(&self.pixels);
let buffer = mapped.finish();
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: &buffer,
offset: 0,
bytes_per_row: self.texture_extent.width * self.texture_format_size,
rows_per_image: self.texture_extent.height,
},
wgpu::TextureCopyView { wgpu::TextureCopyView {
texture: &self.texture, texture: &self.context.texture,
mip_level: 0, mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
}, },
self.texture_extent, &self.pixels,
wgpu::TextureDataLayout {
offset: 0,
bytes_per_row,
rows_per_image: self.context.texture_extent.height,
},
self.context.texture_extent,
); );
// Call the users render function. // Call the users render function.
(render_function)(&mut encoder, &frame.view, &self.scaling_renderer); (render_function)(&mut encoder, &frame.output.view, &self.context);
self.queue.submit(&[encoder.finish()]); self.context.queue.submit(Some(encoder.finish()));
Ok(()) Ok(())
} }
@ -334,8 +365,8 @@ impl Pixels {
/// ///
/// ```no_run /// ```no_run
/// # use pixels::Pixels; /// # use pixels::Pixels;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// const WIDTH: u32 = 320; /// const WIDTH: u32 = 320;
/// const HEIGHT: u32 = 240; /// const HEIGHT: u32 = 240;
/// ///
@ -354,11 +385,11 @@ impl Pixels {
&self, &self,
physical_position: (f32, f32), physical_position: (f32, f32),
) -> Result<(usize, usize), (isize, isize)> { ) -> Result<(usize, usize), (isize, isize)> {
let physical_width = self.surface_texture.width as f32; let physical_width = self.surface_size.width as f32;
let physical_height = self.surface_texture.height as f32; let physical_height = self.surface_size.height as f32;
let pixels_width = self.texture_extent.width as f32; let pixels_width = self.context.texture_extent.width as f32;
let pixels_height = self.texture_extent.height as f32; let pixels_height = self.context.texture_extent.height as f32;
let pos = ultraviolet::Vec4::new( let pos = ultraviolet::Vec4::new(
(physical_position.0 / physical_width - 0.5) * pixels_width, (physical_position.0 / physical_width - 0.5) * pixels_width,
@ -377,9 +408,9 @@ impl Pixels {
let pixel_y = pos.1.floor() as isize; let pixel_y = pos.1.floor() as isize;
if pixel_x < 0 if pixel_x < 0
|| pixel_x >= self.texture_extent.width as isize || pixel_x >= self.context.texture_extent.width as isize
|| pixel_y < 0 || pixel_y < 0
|| pixel_y >= self.texture_extent.height as isize || pixel_y >= self.context.texture_extent.height as isize
{ {
Err((pixel_x, pixel_y)) Err((pixel_x, pixel_y))
} else { } else {
@ -394,8 +425,8 @@ impl Pixels {
/// ///
/// ```no_run /// ```no_run
/// # use pixels::Pixels; /// # use pixels::Pixels;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// const WIDTH: u32 = 320; /// const WIDTH: u32 = 320;
/// const HEIGHT: u32 = 240; /// const HEIGHT: u32 = 240;
/// ///
@ -410,38 +441,47 @@ impl Pixels {
/// ``` /// ```
pub fn clamp_pixel_pos(&self, pos: (isize, isize)) -> (usize, usize) { pub fn clamp_pixel_pos(&self, pos: (isize, isize)) -> (usize, usize) {
( (
pos.0.max(0).min(self.texture_extent.width as isize - 1) as usize, pos.0
pos.1.max(0).min(self.texture_extent.height as isize - 1) as usize, .max(0)
.min(self.context.texture_extent.width as isize - 1) as usize,
pos.1
.max(0)
.min(self.context.texture_extent.height as isize - 1) as usize,
) )
} }
/// Provides access to the internal [`wgpu::Device`]. /// Provides access to the internal [`wgpu::Device`].
pub fn device(&self) -> &wgpu::Device { pub fn device(&self) -> &wgpu::Device {
&self.device &self.context.device
} }
/// Provides access to the internal [`wgpu::Queue`]. /// Provides access to the internal [`wgpu::Queue`].
pub fn queue(&self) -> &wgpu::Queue { pub fn queue(&self) -> &wgpu::Queue {
&self.queue &self.context.queue
} }
/// Provides access to the internal source [`wgpu::Texture`]. /// Provides access to the internal source [`wgpu::Texture`].
/// ///
/// This is the pre-scaled texture copied from the pixel buffer. /// This is the pre-scaled texture copied from the pixel buffer.
pub fn texture(&self) -> &wgpu::Texture { pub fn texture(&self) -> &wgpu::Texture {
&self.texture &self.context.texture
}
/// Provides access to the internal [`PixelsContext`]
pub fn context(&self) -> &PixelsContext {
&self.context
} }
} }
impl<'req> PixelsBuilder<'req> { impl<'req, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'win, W> {
/// Create a builder that can be finalized into a [`Pixels`] pixel buffer. /// Create a builder that can be finalized into a [`Pixels`] pixel buffer.
/// ///
/// # Examples /// # Examples
/// ///
/// ```no_run /// ```no_run
/// # use pixels::PixelsBuilder; /// # use pixels::PixelsBuilder;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let window = pixels_mocks::RWH;
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &window);
/// let mut pixels = PixelsBuilder::new(256, 240, surface_texture) /// let mut pixels = PixelsBuilder::new(256, 240, surface_texture)
/// .request_adapter_options(wgpu::RequestAdapterOptions { /// .request_adapter_options(wgpu::RequestAdapterOptions {
/// power_preference: wgpu::PowerPreference::HighPerformance, /// power_preference: wgpu::PowerPreference::HighPerformance,
@ -455,7 +495,11 @@ impl<'req> PixelsBuilder<'req> {
/// # Panics /// # Panics
/// ///
/// Panics when `width` or `height` are 0. /// Panics when `width` or `height` are 0.
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture) -> PixelsBuilder<'req> { pub fn new(
width: u32,
height: u32,
surface_texture: SurfaceTexture<'win, W>,
) -> PixelsBuilder<'req, 'win, W> {
assert!(width > 0); assert!(width > 0);
assert!(height > 0); assert!(height > 0);
@ -473,19 +517,19 @@ impl<'req> PixelsBuilder<'req> {
} }
/// Add options for requesting a [`wgpu::Adapter`]. /// Add options for requesting a [`wgpu::Adapter`].
pub const fn request_adapter_options( pub fn request_adapter_options(
mut self, mut self,
request_adapter_options: wgpu::RequestAdapterOptions<'req>, request_adapter_options: wgpu::RequestAdapterOptions<'req>,
) -> PixelsBuilder { ) -> PixelsBuilder<'req, 'win, W> {
self.request_adapter_options = Some(request_adapter_options); self.request_adapter_options = Some(request_adapter_options);
self self
} }
/// Add options for requesting a [`wgpu::Device`]. /// Add options for requesting a [`wgpu::Device`].
pub const fn device_descriptor( pub fn device_descriptor(
mut self, mut self,
device_descriptor: wgpu::DeviceDescriptor, device_descriptor: wgpu::DeviceDescriptor,
) -> PixelsBuilder<'req> { ) -> PixelsBuilder<'req, 'win, W> {
self.device_descriptor = device_descriptor; self.device_descriptor = device_descriptor;
self self
} }
@ -494,7 +538,7 @@ impl<'req> PixelsBuilder<'req> {
/// ///
/// The default value of this is [`wgpu::BackendBit::PRIMARY`], which enables /// The default value of this is [`wgpu::BackendBit::PRIMARY`], which enables
/// the well supported backends for wgpu. /// the well supported backends for wgpu.
pub const fn wgpu_backend(mut self, backend: wgpu::BackendBit) -> PixelsBuilder<'req> { pub fn wgpu_backend(mut self, backend: wgpu::BackendBit) -> PixelsBuilder<'req, 'win, W> {
self.backend = backend; self.backend = backend;
self self
} }
@ -514,7 +558,7 @@ impl<'req> PixelsBuilder<'req> {
/// ///
/// This documentation is hidden because support for pixel aspect ratio is incomplete. /// This documentation is hidden because support for pixel aspect ratio is incomplete.
#[doc(hidden)] #[doc(hidden)]
pub fn pixel_aspect_ratio(mut self, pixel_aspect_ratio: f64) -> PixelsBuilder<'req> { pub fn pixel_aspect_ratio(mut self, pixel_aspect_ratio: f64) -> PixelsBuilder<'req, 'win, W> {
assert!(pixel_aspect_ratio > 0.0); assert!(pixel_aspect_ratio > 0.0);
self.pixel_aspect_ratio = pixel_aspect_ratio; self.pixel_aspect_ratio = pixel_aspect_ratio;
@ -528,7 +572,7 @@ impl<'req> PixelsBuilder<'req> {
/// The `wgpu` present mode will be set to `Fifo` when Vsync is enabled, or `Immediate` when /// The `wgpu` present mode will be set to `Fifo` when Vsync is enabled, or `Immediate` when
/// Vsync is disabled. To set the present mode to `Mailbox` or another value, use the /// Vsync is disabled. To set the present mode to `Mailbox` or another value, use the
/// [`PixelsBuilder::present_mode`] method. /// [`PixelsBuilder::present_mode`] method.
pub fn enable_vsync(mut self, enable_vsync: bool) -> PixelsBuilder<'req> { pub fn enable_vsync(mut self, enable_vsync: bool) -> PixelsBuilder<'req, 'win, W> {
self.present_mode = if enable_vsync { self.present_mode = if enable_vsync {
wgpu::PresentMode::Fifo wgpu::PresentMode::Fifo
} else { } else {
@ -541,7 +585,7 @@ impl<'req> PixelsBuilder<'req> {
/// ///
/// This differs from [`PixelsBuilder::enable_vsync`] by allowing the present mode to be set to /// This differs from [`PixelsBuilder::enable_vsync`] by allowing the present mode to be set to
/// any value. /// any value.
pub fn present_mode(mut self, present_mode: wgpu::PresentMode) -> PixelsBuilder<'req> { pub fn present_mode(mut self, present_mode: wgpu::PresentMode) -> PixelsBuilder<'req, 'win, W> {
self.present_mode = present_mode; self.present_mode = present_mode;
self self
} }
@ -551,10 +595,10 @@ impl<'req> PixelsBuilder<'req> {
/// The default value is [`wgpu::TextureFormat::Rgba8UnormSrgb`], which is 4 unsigned bytes in /// The default value is [`wgpu::TextureFormat::Rgba8UnormSrgb`], which is 4 unsigned bytes in
/// `RGBA` order using the SRGB color space. This is typically what you want when you are /// `RGBA` order using the SRGB color space. This is typically what you want when you are
/// working with color values from popular image editing tools or web apps. /// working with color values from popular image editing tools or web apps.
pub const fn texture_format( pub fn texture_format(
mut self, mut self,
texture_format: wgpu::TextureFormat, texture_format: wgpu::TextureFormat,
) -> PixelsBuilder<'req> { ) -> PixelsBuilder<'req, 'win, W> {
self.texture_format = texture_format; self.texture_format = texture_format;
self self
} }
@ -564,11 +608,13 @@ impl<'req> PixelsBuilder<'req> {
/// # Errors /// # Errors
/// ///
/// Returns an error when a [`wgpu::Adapter`] cannot be found. /// Returns an error when a [`wgpu::Adapter`] cannot be found.
pub fn build(self) -> Result<Pixels, Error> { pub fn build(self) -> Result<Pixels<W>, Error> {
let instance = wgpu::Instance::new(self.backend);
// TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture // TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture
let compatible_surface = Some(&self.surface_texture.surface); let surface = surface_from_window_handle(&instance, self.surface_texture.window);
let adapter = pollster::block_on(wgpu::Adapter::request( let compatible_surface = Some(&surface);
&self.request_adapter_options.map_or_else( let adapter = instance.request_adapter(&self.request_adapter_options.map_or_else(
|| wgpu::RequestAdapterOptions { || wgpu::RequestAdapterOptions {
compatible_surface, compatible_surface,
power_preference: get_default_power_preference(), power_preference: get_default_power_preference(),
@ -577,13 +623,12 @@ impl<'req> PixelsBuilder<'req> {
compatible_surface: rao.compatible_surface.or(compatible_surface), compatible_surface: rao.compatible_surface.or(compatible_surface),
power_preference: rao.power_preference, power_preference: rao.power_preference,
}, },
), ));
self.backend, let adapter = pollster::block_on(adapter).ok_or(Error::AdapterNotFound)?;
))
.ok_or(Error::AdapterNotFound)?;
let (mut device, queue) = let (device, queue) =
pollster::block_on(adapter.request_device(&self.device_descriptor)); pollster::block_on(adapter.request_device(&self.device_descriptor, None))
.map_err(Error::DeviceNotFound)?;
// The rest of this is technically a fixed-function pipeline... For now! // The rest of this is technically a fixed-function pipeline... For now!
@ -596,70 +641,75 @@ impl<'req> PixelsBuilder<'req> {
depth: 1, depth: 1,
}; };
let texture = device.create_texture(&wgpu::TextureDescriptor { let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None, label: Some("pixels_source_texture"),
size: texture_extent, size: texture_extent,
array_layer_count: 1,
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: self.texture_format, format: self.texture_format,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
}); });
let texture_view = texture.create_default_view(); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let texture_format_size = get_texture_format_size(self.texture_format); let texture_format_size = get_texture_format_size(self.texture_format);
// Create the pixel buffer // Create the pixel buffer
let capacity = (width * height * texture_format_size) as usize; let capacity = ((width * height) as f32 * texture_format_size) as usize;
let mut pixels = Vec::with_capacity(capacity); let mut pixels = Vec::with_capacity(capacity);
pixels.resize_with(capacity, Default::default); pixels.resize_with(capacity, Default::default);
let present_mode = self.present_mode; let present_mode = self.present_mode;
// Create swap chain // Create swap chain
let surface_texture = self.surface_texture; let surface_size = self.surface_texture.size;
let swap_chain = device.create_swap_chain( let swap_chain = device.create_swap_chain(
&surface_texture.surface, &surface,
&wgpu::SwapChainDescriptor { &wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb, format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: surface_texture.width, width: surface_size.width,
height: surface_texture.height, height: surface_size.height,
present_mode, present_mode,
}, },
); );
let scaling_matrix_inverse = renderers::ScalingMatrix::new( let scaling_matrix_inverse = renderers::ScalingMatrix::new(
(width as f32, height as f32), (width as f32, height as f32),
(surface_texture.width as f32, surface_texture.height as f32), (surface_size.width as f32, surface_size.height as f32),
) )
.transform .transform
.inversed(); .inversed();
let scaling_renderer = ScalingRenderer::new(&mut device, &texture_view, &texture_extent); let scaling_renderer = ScalingRenderer::new(&device, &texture_view, &texture_extent);
Ok(Pixels { let context = PixelsContext {
device, device,
queue, queue,
surface,
swap_chain, swap_chain,
surface_texture,
present_mode,
scaling_renderer,
texture, texture,
texture_extent, texture_extent,
texture_format_size, texture_format_size,
scaling_renderer,
};
Ok(Pixels {
context,
surface_size,
present_mode,
_phantom: std::marker::PhantomData,
pixels, pixels,
scaling_matrix_inverse, scaling_matrix_inverse,
}) })
} }
} }
fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> u32 { fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> f32 {
match texture_format { match texture_format {
// 8-bit formats // 8-bit formats
wgpu::TextureFormat::R8Unorm wgpu::TextureFormat::R8Unorm
| wgpu::TextureFormat::R8Snorm | wgpu::TextureFormat::R8Snorm
| wgpu::TextureFormat::R8Uint | wgpu::TextureFormat::R8Uint
| wgpu::TextureFormat::R8Sint => 1, | wgpu::TextureFormat::R8Sint => 1.0,
// 16-bit formats // 16-bit formats
wgpu::TextureFormat::R16Uint wgpu::TextureFormat::R16Uint
@ -668,7 +718,7 @@ fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> u32 {
| wgpu::TextureFormat::Rg8Unorm | wgpu::TextureFormat::Rg8Unorm
| wgpu::TextureFormat::Rg8Snorm | wgpu::TextureFormat::Rg8Snorm
| wgpu::TextureFormat::Rg8Uint | wgpu::TextureFormat::Rg8Uint
| wgpu::TextureFormat::Rg8Sint => 2, | wgpu::TextureFormat::Rg8Sint => 2.0,
// 32-bit formats // 32-bit formats
wgpu::TextureFormat::R32Uint wgpu::TextureFormat::R32Uint
@ -688,7 +738,7 @@ fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> u32 {
| wgpu::TextureFormat::Rg11b10Float | wgpu::TextureFormat::Rg11b10Float
| wgpu::TextureFormat::Depth32Float | wgpu::TextureFormat::Depth32Float
| wgpu::TextureFormat::Depth24Plus | wgpu::TextureFormat::Depth24Plus
| wgpu::TextureFormat::Depth24PlusStencil8 => 4, | wgpu::TextureFormat::Depth24PlusStencil8 => 4.0,
// 64-bit formats // 64-bit formats
wgpu::TextureFormat::Rg32Uint wgpu::TextureFormat::Rg32Uint
@ -696,12 +746,29 @@ fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> u32 {
| wgpu::TextureFormat::Rg32Float | wgpu::TextureFormat::Rg32Float
| wgpu::TextureFormat::Rgba16Uint | wgpu::TextureFormat::Rgba16Uint
| wgpu::TextureFormat::Rgba16Sint | wgpu::TextureFormat::Rgba16Sint
| wgpu::TextureFormat::Rgba16Float => 8, | wgpu::TextureFormat::Rgba16Float => 8.0,
// 128-bit formats // 128-bit formats
wgpu::TextureFormat::Rgba32Uint wgpu::TextureFormat::Rgba32Uint
| wgpu::TextureFormat::Rgba32Sint | wgpu::TextureFormat::Rgba32Sint
| wgpu::TextureFormat::Rgba32Float => 16, | wgpu::TextureFormat::Rgba32Float => 16.0,
// Compressed formats
wgpu::TextureFormat::Bc1RgbaUnorm
| wgpu::TextureFormat::Bc1RgbaUnormSrgb
| wgpu::TextureFormat::Bc4RUnorm
| wgpu::TextureFormat::Bc4RSnorm => 0.5,
wgpu::TextureFormat::Bc2RgbaUnorm
| wgpu::TextureFormat::Bc2RgbaUnormSrgb
| wgpu::TextureFormat::Bc3RgbaUnorm
| wgpu::TextureFormat::Bc3RgbaUnormSrgb
| wgpu::TextureFormat::Bc5RgUnorm
| wgpu::TextureFormat::Bc5RgSnorm
| wgpu::TextureFormat::Bc6hRgbUfloat
| wgpu::TextureFormat::Bc6hRgbSfloat
| wgpu::TextureFormat::Bc7RgbaUnorm
| wgpu::TextureFormat::Bc7RgbaUnormSrgb => 1.0,
} }
} }

View file

@ -1,17 +0,0 @@
/// Provides a macro and type for including SPIR-V shaders in const data.
///
/// In an ideal world, a shader will be compiled at build-time directly into the executable. This
/// is opposed to the typical method of including a shader, which reads a GLSL source code file
/// from the file system at start, compiles it, and sends it to the GPU. That process adds a
/// non-trivial amount of time to startup, and additional error handling code at runtime.
///
/// This macro moves all of that complexity to build-time. At least for the SPIR-V part of the
/// shader pipeline. (`gfx-hal` backends have their own SPIR-V-to-native compilers at runtime.)
#[macro_export]
macro_rules! include_spv {
($path:expr) => {
&wgpu::read_spirv(std::io::Cursor::new(&include_bytes!($path)[..]))
.expect(&format!("Invalid SPIR-V shader in file: {}", $path))
};
}

View file

@ -1,5 +1,5 @@
use crate::include_spv;
use ultraviolet::Mat4; use ultraviolet::Mat4;
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)]
@ -13,15 +13,16 @@ pub struct ScalingRenderer {
impl ScalingRenderer { impl ScalingRenderer {
pub(crate) fn new( pub(crate) fn new(
device: &mut wgpu::Device, device: &wgpu::Device,
texture_view: &wgpu::TextureView, texture_view: &wgpu::TextureView,
texture_size: &wgpu::Extent3d, texture_size: &wgpu::Extent3d,
) -> Self { ) -> Self {
let vs_module = device.create_shader_module(include_spv!("../shaders/vert.spv")); let vs_module = device.create_shader_module(wgpu::include_spirv!("../shaders/vert.spv"));
let fs_module = device.create_shader_module(include_spv!("../shaders/frag.spv")); let fs_module = device.create_shader_module(wgpu::include_spirv!("../shaders/frag.spv"));
// 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("pixels_scaling_renderer_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
@ -30,7 +31,8 @@ impl ScalingRenderer {
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0, lod_min_clamp: 0.0,
lod_max_clamp: 1.0, lod_max_clamp: 1.0,
compare: wgpu::CompareFunction::Always, compare: None,
anisotropy_clamp: None,
}); });
// Create uniform buffer // Create uniform buffer
@ -41,15 +43,16 @@ impl ScalingRenderer {
(texture_size.width as f32, texture_size.height as f32), (texture_size.width as f32, texture_size.height as f32),
); );
let transform_bytes = matrix.as_bytes(); let transform_bytes = matrix.as_bytes();
let uniform_buffer = device.create_buffer_with_data( let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
&transform_bytes, label: Some("pixels_scaling_renderer_matrix_uniform_buffer"),
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, contents: &transform_bytes,
); usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
});
// Create bind group // Create bind group
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None, label: Some("pixels_scaling_renderer_bind_group_layout"),
bindings: &[ entries: &[
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT, visibility: wgpu::ShaderStage::FRAGMENT,
@ -58,47 +61,53 @@ impl ScalingRenderer {
multisampled: false, multisampled: false,
dimension: wgpu::TextureViewDimension::D2, dimension: wgpu::TextureViewDimension::D2,
}, },
count: None,
}, },
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT, visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler { comparison: false }, ty: wgpu::BindingType::Sampler { comparison: false },
count: None,
}, },
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 2, binding: 2,
visibility: wgpu::ShaderStage::VERTEX, visibility: wgpu::ShaderStage::VERTEX,
ty: wgpu::BindingType::UniformBuffer { dynamic: false }, ty: wgpu::BindingType::UniformBuffer {
dynamic: false,
min_binding_size: None,
},
count: None,
}, },
], ],
}); });
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None, label: Some("pixels_scaling_renderer_bind_group"),
layout: &bind_group_layout, layout: &bind_group_layout,
bindings: &[ entries: &[
wgpu::Binding { wgpu::BindGroupEntry {
binding: 0, binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view), resource: wgpu::BindingResource::TextureView(texture_view),
}, },
wgpu::Binding { wgpu::BindGroupEntry {
binding: 1, binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler), resource: wgpu::BindingResource::Sampler(&sampler),
}, },
wgpu::Binding { wgpu::BindGroupEntry {
binding: 2, binding: 2,
resource: wgpu::BindingResource::Buffer { resource: wgpu::BindingResource::Buffer(uniform_buffer.slice(..)),
buffer: &uniform_buffer,
range: 0..64,
},
}, },
], ],
}); });
// Create pipeline // Create pipeline
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("pixels_scaling_renderer_pipeline_layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
}); });
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: &pipeline_layout, label: Some("pixels_scaling_renderer_pipeline"),
layout: Some(&pipeline_layout),
vertex_stage: wgpu::ProgrammableStageDescriptor { vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module, module: &vs_module,
entry_point: "main", entry_point: "main",
@ -110,6 +119,7 @@ impl ScalingRenderer {
rasterization_state: Some(wgpu::RasterizationStateDescriptor { rasterization_state: Some(wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw, front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None, cull_mode: wgpu::CullMode::None,
clamp_depth: false,
depth_bias: 0, depth_bias: 0,
depth_bias_slope_scale: 0.0, depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0, depth_bias_clamp: 0.0,
@ -146,9 +156,10 @@ impl ScalingRenderer {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: render_target, attachment: render_target,
resolve_target: None, resolve_target: None,
load_op: wgpu::LoadOp::Clear, ops: wgpu::Operations {
store_op: wgpu::StoreOp::Store, load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
clear_color: wgpu::Color::BLACK, store: true,
},
}], }],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}); });
@ -157,19 +168,10 @@ impl ScalingRenderer {
rpass.draw(0..6, 0..1); rpass.draw(0..6, 0..1);
} }
pub(crate) fn resize( pub(crate) fn resize(&self, queue: &wgpu::Queue, width: u32, height: u32) {
&mut self,
device: &mut wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
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);
let temp_buf =
device.create_buffer_with_data(&transform_bytes, wgpu::BufferUsage::COPY_SRC);
encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buffer, 0, 64);
} }
} }