From 288da3675f355f7060f49bdae1f6b9ba8d6a4856 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 17 Jul 2021 08:21:49 -0700 Subject: [PATCH] Use the swapchain-preferred texture format by default (#182) - Fixes #140 - Adds a public method to get the current GPU framebuffer texture format (AKA the render texture format). - This wasn't as difficult as it seemed; the extra API is used by the examples to get the right texture format instead of being hardcoded. --- examples/custom-shader/src/main.rs | 4 +-- examples/custom-shader/src/renderers.rs | 18 +++++----- examples/egui-winit/src/gui.rs | 4 +-- examples/egui-winit/src/main.rs | 7 +--- examples/imgui-winit/src/gui.rs | 3 +- src/builder.rs | 48 +++++++++++++++++-------- src/lib.rs | 10 +++++- 7 files changed, 59 insertions(+), 35 deletions(-) diff --git a/examples/custom-shader/src/main.rs b/examples/custom-shader/src/main.rs index 2cf9d05..8a88801 100644 --- a/examples/custom-shader/src/main.rs +++ b/examples/custom-shader/src/main.rs @@ -45,7 +45,7 @@ fn main() -> Result<(), Error> { }; let mut world = World::new(); let mut time = 0.0; - let mut noise_renderer = NoiseRenderer::new(pixels.device(), WIDTH, HEIGHT); + let mut noise_renderer = NoiseRenderer::new(&pixels, WIDTH, HEIGHT); event_loop.run(move |event, _, control_flow| { // Draw the current frame @@ -82,7 +82,7 @@ fn main() -> Result<(), Error> { // Resize the window if let Some(size) = input.window_resized() { pixels.resize_surface(size.width, size.height); - noise_renderer.resize(pixels.device(), size.width, size.height); + noise_renderer.resize(&pixels, size.width, size.height); } // Update internal state and request a redraw diff --git a/examples/custom-shader/src/renderers.rs b/examples/custom-shader/src/renderers.rs index 1f8212b..e1927b8 100644 --- a/examples/custom-shader/src/renderers.rs +++ b/examples/custom-shader/src/renderers.rs @@ -11,13 +11,14 @@ pub(crate) struct NoiseRenderer { } impl NoiseRenderer { - pub(crate) fn new(device: &wgpu::Device, width: u32, height: u32) -> Self { + pub(crate) fn new(pixels: &pixels::Pixels, width: u32, height: u32) -> Self { + let device = pixels.device(); let shader = wgpu::include_wgsl!("../shaders/noise.wgsl"); let module = device.create_shader_module(&shader); // Create a texture view that will be used as input // This will be used as the render target for the default scaling renderer - let texture_view = create_texture_view(device, width, height); + let texture_view = create_texture_view(pixels, width, height); // Create a texture sampler with nearest neighbor let sampler = device.create_sampler(&wgpu::SamplerDescriptor { @@ -137,7 +138,7 @@ impl NoiseRenderer { module: &module, entry_point: "fs_main", targets: &[wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: pixels.render_texture_format(), blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, @@ -162,10 +163,10 @@ impl NoiseRenderer { &self.texture_view } - pub(crate) fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) { - self.texture_view = create_texture_view(device, width, height); + pub(crate) fn resize(&mut self, pixels: &pixels::Pixels, width: u32, height: u32) { + self.texture_view = create_texture_view(pixels, width, height); self.bind_group = create_bind_group( - device, + pixels.device(), &self.bind_group_layout, &self.texture_view, &self.sampler, @@ -203,7 +204,8 @@ impl NoiseRenderer { } } -fn create_texture_view(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView { +fn create_texture_view(pixels: &pixels::Pixels, width: u32, height: u32) -> wgpu::TextureView { + let device = pixels.device(); let texture_descriptor = wgpu::TextureDescriptor { label: None, size: pixels::wgpu::Extent3d { @@ -214,7 +216,7 @@ fn create_texture_view(device: &wgpu::Device, width: u32, height: u32) -> wgpu:: mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: pixels.render_texture_format(), usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, }; diff --git a/examples/egui-winit/src/gui.rs b/examples/egui-winit/src/gui.rs index 7a53637..d1d0810 100644 --- a/examples/egui-winit/src/gui.rs +++ b/examples/egui-winit/src/gui.rs @@ -19,7 +19,7 @@ pub(crate) struct Gui { impl Gui { /// Create egui. - pub(crate) fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self { + pub(crate) fn new(width: u32, height: u32, scale_factor: f64, pixels: &pixels::Pixels) -> Self { let platform = Platform::new(PlatformDescriptor { physical_width: width, physical_height: height, @@ -32,7 +32,7 @@ impl Gui { physical_height: height, scale_factor: scale_factor as f32, }; - let rpass = RenderPass::new(&context.device, wgpu::TextureFormat::Bgra8UnormSrgb, 1); + let rpass = RenderPass::new(pixels.device(), pixels.render_texture_format(), 1); Self { start_time: Instant::now(), diff --git a/examples/egui-winit/src/main.rs b/examples/egui-winit/src/main.rs index eefaf82..823f10e 100644 --- a/examples/egui-winit/src/main.rs +++ b/examples/egui-winit/src/main.rs @@ -43,12 +43,7 @@ fn main() -> Result<(), Error> { let scale_factor = window.scale_factor(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); let pixels = Pixels::new(WIDTH, HEIGHT, surface_texture)?; - let gui = Gui::new( - window_size.width, - window_size.height, - scale_factor, - pixels.context(), - ); + let gui = Gui::new(window_size.width, window_size.height, scale_factor, &pixels); (pixels, gui) }; diff --git a/examples/imgui-winit/src/gui.rs b/examples/imgui-winit/src/gui.rs index c4cb623..3e8194d 100644 --- a/examples/imgui-winit/src/gui.rs +++ b/examples/imgui-winit/src/gui.rs @@ -44,9 +44,8 @@ impl Gui { // Create Dear ImGui WGPU renderer let device = pixels.device(); let queue = pixels.queue(); - let texture_format = wgpu::TextureFormat::Bgra8UnormSrgb; let config = imgui_wgpu::RendererConfig { - texture_format, + texture_format: pixels.render_texture_format(), ..Default::default() }; let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config); diff --git a/src/builder.rs b/src/builder.rs index cabc36b..f93484d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,7 +15,7 @@ pub struct PixelsBuilder<'req, 'dev, 'win, W: HasRawWindowHandle> { present_mode: wgpu::PresentMode, surface_texture: SurfaceTexture<'win, W>, texture_format: wgpu::TextureFormat, - render_texture_format: wgpu::TextureFormat, + render_texture_format: Option, } impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> { @@ -58,7 +58,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> present_mode: wgpu::PresentMode::Fifo, surface_texture, texture_format: wgpu::TextureFormat::Rgba8UnormSrgb, - render_texture_format: wgpu::TextureFormat::Bgra8UnormSrgb, + render_texture_format: None, } } @@ -82,8 +82,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> /// Set which backends wgpu will attempt to use. /// - /// The default value of this is [`wgpu::BackendBit::PRIMARY`], which enables - /// the well supported backends for wgpu. + /// 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> { self.backend = backend; self @@ -144,9 +143,12 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> /// Set the texture format. /// - /// 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 - /// working with color values from popular image editing tools or web apps. + /// The default value is `Rgba8UnormSrgb`, which is 4 unsigned bytes in `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. + /// + /// This is the pixel format of the texture that most applications will interact with directly. + /// The format influences the structure of byte data that is returned by [`Pixels::get_frame`]. pub fn texture_format( mut self, texture_format: wgpu::TextureFormat, @@ -157,14 +159,27 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> /// Set the render texture format. /// - /// The default value is [`wgpu::TextureFormat::Bgra8UnormSrgb`], which is 4 unsigned bytes in - /// `BGRA` order using the SRGB color space. This format depends on the hardware/platform the - /// pixel buffer is rendered to/for. + /// The default value is chosen automatically by the swapchain (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 + /// 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 + /// 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. pub fn render_texture_format( mut self, texture_format: wgpu::TextureFormat, ) -> PixelsBuilder<'req, 'dev, 'win, W> { - self.render_texture_format = texture_format; + self.render_texture_format = Some(texture_format); self } @@ -196,13 +211,18 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> .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) + .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb) + }); // Create swap chain let surface_size = self.surface_texture.size; let swap_chain = create_swap_chain( &mut device, &surface, - self.render_texture_format, + render_texture_format, &surface_size, present_mode, ); @@ -217,7 +237,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> self.texture_format, // Render texture values &surface_size, - self.render_texture_format, + render_texture_format, ); // Create the pixel buffer @@ -241,9 +261,9 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> context, surface_size, present_mode, + render_texture_format, pixels, scaling_matrix_inverse, - render_texture_format: self.render_texture_format, }) } } diff --git a/src/lib.rs b/src/lib.rs index 671f93a..aead3c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -525,8 +525,16 @@ impl Pixels { &self.context.texture } - /// Provides access to the internal [`PixelsContext`] + /// Provides access to the internal [`PixelsContext`]. pub fn context(&self) -> &PixelsContext { &self.context } + + /// Get the render texture format. + /// + /// This texture format may be chosen automatically by the swapchain. See + /// [`PixelsBuilder::render_texture_format`] for more information. + pub fn render_texture_format(&self) -> wgpu::TextureFormat { + self.render_texture_format + } }