diff --git a/Cargo.toml b/Cargo.toml index 84466dc..f96147d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/README.md b/README.md index 0fb7630..8deed78 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/examples/custom-shader/src/renderers.rs b/examples/custom-shader/src/renderers.rs index e1927b8..89ea0fa 100644 --- a/examples/custom-shader/src/renderers.rs +++ b/examples/custom-shader/src/renderers.rs @@ -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 diff --git a/examples/egui-winit/Cargo.toml b/examples/egui-winit/Cargo.toml index 09803ab..324d660 100644 --- a/examples/egui-winit/Cargo.toml +++ b/examples/egui-winit/Cargo.toml @@ -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 = "../.." } diff --git a/examples/egui-winit/src/gui.rs b/examples/egui-winit/src/gui.rs index d1d0810..757efd1 100644 --- a/examples/egui-winit/src/gui.rs +++ b/examples/egui-winit/src/gui.rs @@ -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, - ); + ) } } diff --git a/examples/egui-winit/src/main.rs b/examples/egui-winit/src/main.rs index 823f10e..bc5e8a0 100644 --- a/examples/egui-winit/src/main.rs +++ b/examples/egui-winit/src/main.rs @@ -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 diff --git a/examples/imgui-winit/Cargo.toml b/examples/imgui-winit/Cargo.toml index d314d42..4dcf283 100644 --- a/examples/imgui-winit/Cargo.toml +++ b/examples/imgui-winit/Cargo.toml @@ -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 = "../.." } diff --git a/examples/minimal-fltk/src/main.rs b/examples/minimal-fltk/src/main.rs index 31f1859..701ee07 100644 --- a/examples/minimal-fltk/src/main.rs +++ b/examples/minimal-fltk/src/main.rs @@ -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(()) diff --git a/src/builder.rs b/src/builder.rs index f93484d..cf8f440 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,7 +8,7 @@ use std::env; pub struct PixelsBuilder<'req, 'dev, 'win, W: HasRawWindowHandle> { request_adapter_options: Option>, 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 diff --git a/src/lib.rs b/src/lib.rs index aead3c7..f137062 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/renderers.rs b/src/renderers.rs index ba84029..c22fa83 100644 --- a/src/renderers.rs +++ b/src/renderers.rs @@ -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, }], }), });