Update to wgpu 0.10 (#187)

- It would be nice to return an error from the render function
- imgui-winit is still a WIP (open PR: https://github.com/Yatekii/imgui-wgpu-rs/pull/66)
- Update README

Co-authored-by: Mohammed Alyousef <mohammed.alyousef@neurosrg.com>
This commit is contained in:
Jay Oster 2021-09-01 14:50:43 -07:00 committed by GitHub
parent 480764ed5a
commit e08c91bfd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 98 additions and 108 deletions

View file

@ -24,7 +24,7 @@ pollster = "0.2"
raw-window-handle = "0.3"
thiserror = "1.0"
ultraviolet = "0.8"
wgpu = "0.9"
wgpu = "0.10"
[dev-dependencies]
pixels-mocks = { path = "internals/pixels-mocks" }

View file

@ -19,7 +19,8 @@ The Minimum Supported Rust Version for `pixels` will always be made available in
## Features
- Built on modern graphics APIs powered by [`wgpu`](https://crates.io/crates/wgpu): DirectX 12, Vulkan, Metal. OpenGL support is a work in progress.
- Built on modern graphics APIs powered by [`wgpu`](https://crates.io/crates/wgpu): Vulkan, Metal, DirectX 12, OpenGL ES3.
- DirectX 11, WebGL2, and WebGPU support are a work in progress.
- Use your own custom shaders for special effects.
- Hardware accelerated scaling on perfect pixel boundaries.
- Supports non-square pixel aspect ratios. (WIP)
@ -38,6 +39,16 @@ The Minimum Supported Rust Version for `pixels` will always be made available in
## Troubleshooting
### Cargo resolver
Starting with [`wgpu` 0.10](https://github.com/gfx-rs/wgpu/blob/06316c1bac8b78ac04d762cfb1a886bd1d453b30/CHANGELOG.md#v010-2021-08-18), the [resolver version](https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions) needs to be set in your `Cargo.toml` to avoid build errors:
```toml
resolver = "2"
```
### Driver issues
The most common issue is having an outdated graphics driver installed on the host machine. `pixels`
requests a low power (aka integrated) GPU by default. If the examples are not working for any reason, you may try setting the `PIXELS_HIGH_PERF` environment variable (the value does not matter, e.g. `PIXELS_HIGH_PERF=1` is fine) to see if that addresses the issue on your host machine.

View file

@ -48,11 +48,11 @@ impl NoiseRenderer {
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer vertex buffer"),
contents: vertex_data_slice,
usage: wgpu::BufferUsage::VERTEX,
usage: wgpu::BufferUsages::VERTEX,
});
let vertex_buffer_layout = wgpu::VertexBufferLayout {
array_stride: (vertex_data_slice.len() / vertex_data.len()) as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
@ -71,7 +71,7 @@ impl NoiseRenderer {
let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer u_Time"),
contents: &0.0_f32.to_ne_bytes(),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
// Create bind group
@ -80,7 +80,7 @@ impl NoiseRenderer {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
multisampled: false,
@ -90,7 +90,7 @@ impl NoiseRenderer {
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
@ -99,7 +99,7 @@ impl NoiseRenderer {
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStage::FRAGMENT,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
@ -143,7 +143,7 @@ impl NoiseRenderer {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrite::ALL,
write_mask: wgpu::ColorWrites::ALL,
}],
}),
});
@ -217,7 +217,7 @@ fn create_texture_view(pixels: &pixels::Pixels, width: u32, height: u32) -> wgpu
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: pixels.render_texture_format(),
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
};
device

View file

@ -10,9 +10,9 @@ optimize = ["log/release_max_level_warn"]
default = ["optimize"]
[dependencies]
egui = "0.13"
egui_wgpu_backend = "0.10"
egui_winit_platform = { version = "0.9", features = ["webbrowser"] }
egui = "0.14"
egui_wgpu_backend = "0.12"
egui_winit_platform = { version = "0.10", features = ["webbrowser"] }
env_logger = "0.9"
log = "0.4"
pixels = { path = "../.." }

View file

@ -1,8 +1,9 @@
use egui::{ClippedMesh, FontDefinitions};
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor};
use egui_winit_platform::{Platform, PlatformDescriptor};
use pixels::{wgpu, PixelsContext};
use std::time::Instant;
use winit::window::Window;
/// Manages all state required for rendering egui over `Pixels`.
pub(crate) struct Gui {
@ -63,7 +64,7 @@ impl Gui {
}
/// Prepare egui.
pub(crate) fn prepare(&mut self) {
pub(crate) fn prepare(&mut self, window: &Window) {
self.platform
.update_time(self.start_time.elapsed().as_secs_f64());
@ -74,7 +75,7 @@ impl Gui {
self.ui(&self.platform.context());
// End the egui frame and create all paint jobs to prepare for rendering.
let (_output, paint_commands) = self.platform.end_frame();
let (_output, paint_commands) = self.platform.end_frame(Some(window));
self.paint_jobs = self.platform.context().tessellate(paint_commands);
}
@ -112,7 +113,7 @@ impl Gui {
encoder: &mut wgpu::CommandEncoder,
render_target: &wgpu::TextureView,
context: &PixelsContext,
) {
) -> Result<(), BackendError> {
// Upload all resources to the GPU.
self.rpass.update_texture(
&context.device,
@ -135,6 +136,6 @@ impl Gui {
&self.paint_jobs,
&self.screen_descriptor,
None,
);
)
}
}

View file

@ -59,7 +59,7 @@ fn main() -> Result<(), Error> {
world.draw(pixels.get_frame());
// Prepare egui
gui.prepare();
gui.prepare(&window);
// Render everything together
let render_result = pixels.render_with(|encoder, render_target, context| {
@ -67,7 +67,8 @@ fn main() -> Result<(), Error> {
context.scaling_renderer.render(encoder, render_target);
// Render egui
gui.render(encoder, render_target, context);
gui.render(encoder, render_target, context)
.expect("egui render error");
});
// Basic error handling

View file

@ -12,7 +12,7 @@ default = ["optimize"]
[dependencies]
env_logger = "0.9"
imgui = "0.7"
imgui-wgpu = "0.16"
imgui-wgpu = { git = "https://github.com/kylc/imgui-wgpu-rs.git", rev = "fbbc0cfc7de25a4821980cb2170195c7b3b0fcf4" }
imgui-winit-support = { version = "0.7.1", default-features = false, features = ["winit-25"] }
log = "0.4"
pixels = { path = "../.." }

View file

@ -5,12 +5,10 @@
use fltk::{app, enums::Event, prelude::*, window::Window};
use log::error;
use pixels::{Error, Pixels, SurfaceTexture};
use std::{thread, time::Duration};
const WIDTH: u32 = 600;
const HEIGHT: u32 = 400;
const CIRCLE_RADIUS: i16 = 64;
const SLEEP: u64 = 16;
/// Representation of the application state. In this example, a circle will bounce around the screen.
struct World {
@ -59,10 +57,9 @@ fn main() -> Result<(), Error> {
{
app.quit();
}
win.redraw();
// Calls to redraw in the event loop require an explicit sleep
thread::sleep(Duration::from_millis(SLEEP));
app::flush();
app::awake();
}
Ok(())

View file

@ -8,7 +8,7 @@ use std::env;
pub struct PixelsBuilder<'req, 'dev, 'win, W: HasRawWindowHandle> {
request_adapter_options: Option<wgpu::RequestAdapterOptions<'req>>,
device_descriptor: wgpu::DeviceDescriptor<'dev>,
backend: wgpu::BackendBit,
backend: wgpu::Backends,
width: u32,
height: u32,
_pixel_aspect_ratio: f64,
@ -51,7 +51,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
PixelsBuilder {
request_adapter_options: None,
device_descriptor: wgpu::DeviceDescriptor::default(),
backend: wgpu::BackendBit::PRIMARY,
backend: wgpu::Backends::PRIMARY,
width,
height,
_pixel_aspect_ratio: 1.0,
@ -83,7 +83,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
/// Set which backends wgpu will attempt to use.
///
/// The default value is `PRIMARY`, which enables the well supported backends for wgpu.
pub fn wgpu_backend(mut self, backend: wgpu::BackendBit) -> PixelsBuilder<'req, 'dev, 'win, W> {
pub fn wgpu_backend(mut self, backend: wgpu::Backends) -> PixelsBuilder<'req, 'dev, 'win, W> {
self.backend = backend;
self
}
@ -159,19 +159,19 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
/// Set the render texture format.
///
/// The default value is chosen automatically by the swapchain (if it can) with a fallback to
/// The default value is chosen automatically by the surface (if it can) with a fallback to
/// `Bgra8UnormSrgb` (which is 4 unsigned bytes in `BGRA` order using the sRGB color space).
/// Setting this format correctly depends on the hardware/platform the pixel buffer is rendered
/// to. The chosen format can be retrieved later with [`Pixels::render_texture_format`].
///
/// This method controls the format of the swapchain frame buffer, which has strict texture
/// This method controls the format of the surface frame buffer, which has strict texture
/// format requirements. Applications will never interact directly with the pixel data of this
/// texture, but a view is provided to the `render_function` closure by [`Pixels::render_with`].
/// The render texture can only be used as the final render target at the end of all
/// post-processing shaders.
///
/// The [`ScalingRenderer`] also uses this format for its own render target. This is because it
/// assumes the render target is always the swapchain current frame. This needs to be kept in
/// assumes the render target is always the surface current frame. This needs to be kept in
/// mind when writing custom shaders for post-processing effects. There is a full example of a
/// [custom-shader](https://github.com/parasyte/pixels/tree/master/examples/custom-shader)
/// available that demonstrates how to deal with this.
@ -206,28 +206,19 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
));
let adapter = pollster::block_on(adapter).ok_or(Error::AdapterNotFound)?;
let (mut device, queue) =
let (device, queue) =
pollster::block_on(adapter.request_device(&self.device_descriptor, None))
.map_err(Error::DeviceNotFound)?;
let present_mode = self.present_mode;
let render_texture_format = self.render_texture_format.unwrap_or_else(|| {
adapter
.get_swap_chain_preferred_format(&surface)
surface
.get_preferred_format(&adapter)
.unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb)
});
// Create swap chain
let surface_size = self.surface_texture.size;
let swap_chain = create_swap_chain(
&mut device,
&surface,
render_texture_format,
&surface_size,
present_mode,
);
// Create the backing texture
let surface_size = self.surface_texture.size;
let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) =
create_backing_texture(
&device,
@ -249,7 +240,6 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
device,
queue,
surface,
swap_chain,
texture,
texture_extent,
texture_format: self.texture_format,
@ -257,34 +247,18 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W>
scaling_renderer,
};
Ok(Pixels {
let pixels = Pixels {
context,
surface_size,
present_mode,
render_texture_format,
pixels,
scaling_matrix_inverse,
})
}
}
};
pixels.reconfigure_surface();
pub(crate) fn create_swap_chain(
device: &mut wgpu::Device,
surface: &wgpu::Surface,
format: wgpu::TextureFormat,
surface_size: &SurfaceSize,
present_mode: wgpu::PresentMode,
) -> wgpu::SwapChain {
device.create_swap_chain(
surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format,
width: surface_size.width,
height: surface_size.height,
present_mode,
},
)
Ok(pixels)
}
}
pub(crate) fn create_backing_texture(
@ -321,7 +295,7 @@ pub(crate) fn create_backing_texture(
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: backing_texture_format,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
@ -366,7 +340,8 @@ const fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> f32 {
| Rg8Unorm
| Rg8Snorm
| Rg8Uint
| Rg8Sint => 2.0, // 16.0 / 8.0
| Rg8Sint
| Rgb9e5Ufloat => 2.0, // 16.0 / 8.0
// 32-bit formats, 8 bits per component
R32Uint
@ -426,10 +401,8 @@ const fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> f32 {
| Bc6hRgbSfloat
| Bc7RgbaUnorm
| Bc7RgbaUnormSrgb
| Etc2RgbA8Unorm
| Etc2RgbA8UnormSrgb
| EtcRgUnorm
| EtcRgSnorm
| EacRgUnorm
| EacRgSnorm
| Astc4x4RgbaUnorm
| Astc4x4RgbaUnormSrgb => 1.0, // 4.0 * 4.0 / 16.0

View file

@ -31,11 +31,10 @@
pub use crate::builder::PixelsBuilder;
pub use crate::renderers::ScalingRenderer;
pub use raw_window_handle;
use std::num::NonZeroU32;
pub use wgpu;
use raw_window_handle::HasRawWindowHandle;
use std::num::NonZeroU32;
use thiserror::Error;
pub use wgpu;
mod builder;
mod renderers;
@ -67,7 +66,6 @@ pub struct PixelsContext {
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`].
@ -114,9 +112,9 @@ pub enum Error {
/// Equivalent to [`wgpu::RequestDeviceError`]
#[error("No wgpu::Device found.")]
DeviceNotFound(wgpu::RequestDeviceError),
/// Equivalent to [`wgpu::SwapChainError`]
#[error("The GPU failed to acquire a swapchain frame.")]
Swapchain(wgpu::SwapChainError),
/// Equivalent to [`wgpu::SurfaceError`]
#[error("The GPU failed to acquire a surface frame.")]
Surface(wgpu::SurfaceError),
}
impl<'win, W: HasRawWindowHandle> SurfaceTexture<'win, W> {
@ -265,8 +263,8 @@ impl Pixels {
.transform
.inversed();
// Recreate the swap chain
self.recreate_swap_chain();
// Reconfigure the surface
self.reconfigure_surface();
// Update state for all render passes
self.context
@ -278,7 +276,7 @@ impl Pixels {
///
/// # Errors
///
/// Returns an error when [`wgpu::SwapChain::get_current_frame`] fails.
/// Returns an error when [`wgpu::Surface::get_current_frame`] fails.
///
/// # Example
///
@ -310,13 +308,13 @@ impl Pixels {
/// Draw this pixel buffer to the configured [`SurfaceTexture`] using a custom user-provided
/// 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 surface
/// which you can use to render to the screen, and a [`PixelsContext`] with all of the internal
/// `wgpu` context.
///
/// # Errors
///
/// Returns an error when [`wgpu::SwapChain::get_current_frame`] fails.
/// Returns an error when [`wgpu::Surface::get_current_frame`] fails.
///
/// # Example
///
@ -348,18 +346,18 @@ impl Pixels {
{
let frame = self
.context
.swap_chain
.surface
.get_current_frame()
.or_else(|err| match err {
wgpu::SwapChainError::Outdated => {
// Recreate the swap chain to mitigate race condition on drawing surface resize.
wgpu::SurfaceError::Outdated => {
// Reconfigure the surface to mitigate race condition on window resize.
// See https://github.com/parasyte/pixels/issues/121
self.recreate_swap_chain();
self.context.swap_chain.get_current_frame()
self.reconfigure_surface();
self.context.surface.get_current_frame()
}
err => Err(err),
})
.map_err(Error::Swapchain)?;
.map_err(Error::Surface)?;
let mut encoder =
self.context
.device
@ -375,6 +373,7 @@ impl Pixels {
texture: &self.context.texture,
mip_level: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
aspect: wgpu::TextureAspect::All,
},
&self.pixels,
wgpu::ImageDataLayout {
@ -385,23 +384,31 @@ impl Pixels {
self.context.texture_extent,
);
let view = frame
.output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
// Call the users render function.
(render_function)(&mut encoder, &frame.output.view, &self.context);
(render_function)(&mut encoder, &view, &self.context);
self.context.queue.submit(Some(encoder.finish()));
Ok(())
}
/// Recreate the swap chain.
/// Reconfigure the surface.
///
/// Call this when the surface or presentation mode needs to be changed.
pub(crate) fn recreate_swap_chain(&mut self) {
self.context.swap_chain = builder::create_swap_chain(
&mut self.context.device,
&self.context.surface,
self.render_texture_format,
&self.surface_size,
self.present_mode,
pub(crate) fn reconfigure_surface(&self) {
self.context.surface.configure(
&self.context.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.render_texture_format,
width: self.surface_size.width,
height: self.surface_size.height,
present_mode: self.present_mode,
},
);
}
@ -532,7 +539,7 @@ impl Pixels {
/// Get the render texture format.
///
/// This texture format may be chosen automatically by the swapchain. See
/// This texture format may be chosen automatically by the surface. See
/// [`PixelsBuilder::render_texture_format`] for more information.
pub fn render_texture_format(&self) -> wgpu::TextureFormat {
self.render_texture_format

View file

@ -54,11 +54,11 @@ impl ScalingRenderer {
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("pixels_scaling_renderer_vertex_buffer"),
contents: vertex_data_slice,
usage: wgpu::BufferUsage::VERTEX,
usage: wgpu::BufferUsages::VERTEX,
});
let vertex_buffer_layout = wgpu::VertexBufferLayout {
array_stride: (vertex_data_slice.len() / vertex_data.len()) as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
@ -82,7 +82,7 @@ impl ScalingRenderer {
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("pixels_scaling_renderer_matrix_uniform_buffer"),
contents: transform_bytes,
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
// Create bind group
@ -91,7 +91,7 @@ impl ScalingRenderer {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
multisampled: false,
@ -101,7 +101,7 @@ impl ScalingRenderer {
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
@ -110,7 +110,7 @@ impl ScalingRenderer {
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStage::VERTEX,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
@ -169,7 +169,7 @@ impl ScalingRenderer {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrite::ALL,
write_mask: wgpu::ColorWrites::ALL,
}],
}),
});