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:
parent
01d32c11f0
commit
265ba2e5b3
22
.travis.yml
22
.travis.yml
|
@ -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
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -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/*",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
9
internals/pixels-dragons/Cargo.toml
Normal file
9
internals/pixels-dragons/Cargo.toml
Normal 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"
|
24
internals/pixels-dragons/src/lib.rs
Normal file
24
internals/pixels-dragons/src/lib.rs
Normal 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) }
|
||||||
|
}
|
367
src/lib.rs
367
src/lib.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue