From 0a5cbf40f5447adcc96635cc20fff28c8484b94a Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Wed, 3 Nov 2021 00:09:37 -0700 Subject: [PATCH] Experimental support for integration with existing `wgpu` apps. - Closes #107 --- examples/imgui-winit/src/gui.rs | 2 +- examples/imgui-winit/src/main.rs | 2 +- examples/minimal-egui/src/gui.rs | 8 +- examples/minimal-egui/src/main.rs | 2 +- src/builder.rs | 132 ++++++++++++++++++++---------- src/lib.rs | 81 ++++++++++++++---- 6 files changed, 158 insertions(+), 69 deletions(-) diff --git a/examples/imgui-winit/src/gui.rs b/examples/imgui-winit/src/gui.rs index 90aba9ea..c80592d6 100644 --- a/examples/imgui-winit/src/gui.rs +++ b/examples/imgui-winit/src/gui.rs @@ -120,7 +120,7 @@ impl Gui { }); self.renderer - .render(ui.render(), &context.queue, &context.device, &mut rpass) + .render(ui.render(), context.queue, context.device, &mut rpass) } /// Handle any outstanding events. diff --git a/examples/imgui-winit/src/main.rs b/examples/imgui-winit/src/main.rs index 9131595d..e1a40b33 100644 --- a/examples/imgui-winit/src/main.rs +++ b/examples/imgui-winit/src/main.rs @@ -70,7 +70,7 @@ fn main() -> Result<(), Error> { context.scaling_renderer.render(encoder, render_target); // Render Dear ImGui - gui.render(&window, encoder, render_target, context)?; + gui.render(&window, encoder, render_target, &context)?; Ok(()) }); diff --git a/examples/minimal-egui/src/gui.rs b/examples/minimal-egui/src/gui.rs index 10439996..40d971f6 100644 --- a/examples/minimal-egui/src/gui.rs +++ b/examples/minimal-egui/src/gui.rs @@ -88,12 +88,12 @@ impl Framework { ) -> Result<(), BackendError> { // Upload all resources to the GPU. self.rpass - .update_texture(&context.device, &context.queue, &self.egui_ctx.texture()); + .update_texture(context.device, context.queue, &self.egui_ctx.texture()); self.rpass - .update_user_textures(&context.device, &context.queue); + .update_user_textures(context.device, context.queue); self.rpass.update_buffers( - &context.device, - &context.queue, + context.device, + context.queue, &self.paint_jobs, &self.screen_descriptor, ); diff --git a/examples/minimal-egui/src/main.rs b/examples/minimal-egui/src/main.rs index 9086b09d..04f8b700 100644 --- a/examples/minimal-egui/src/main.rs +++ b/examples/minimal-egui/src/main.rs @@ -94,7 +94,7 @@ fn main() -> Result<(), Error> { context.scaling_renderer.render(encoder, render_target); // Render egui - framework.render(encoder, render_target, context)?; + framework.render(encoder, render_target, &context)?; Ok(()) }); diff --git a/src/builder.rs b/src/builder.rs index 4b3ff13d..2955035c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,5 @@ use crate::renderers::{ScalingMatrix, ScalingRenderer}; -use crate::SurfaceSize; -use crate::{Error, Pixels, PixelsContext, SurfaceTexture}; +use crate::{Borrower, Error, Pixels, PixelsInternalContext, SurfaceSize, SurfaceTexture}; use raw_window_handle::HasRawWindowHandle; /// A builder to help create customized pixel buffers. @@ -183,49 +182,14 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> self } - /// Create a pixel buffer from the options builder. - /// - /// # Errors - /// - /// Returns an error when a [`wgpu::Adapter`] cannot be found. - pub fn build(self) -> Result { - let instance = wgpu::Instance::new(self.backend); - - // TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture - let surface = unsafe { instance.create_surface(self.surface_texture.window) }; - let compatible_surface = Some(&surface); - let request_adapter_options = &self.request_adapter_options; - let adapter = - wgpu::util::initialize_adapter_from_env(&instance, self.backend).or_else(|| { - let future = - instance.request_adapter(&request_adapter_options.as_ref().map_or_else( - || wgpu::RequestAdapterOptions { - compatible_surface, - force_fallback_adapter: false, - power_preference: - wgpu::util::power_preference_from_env().unwrap_or_default(), - }, - |rao| wgpu::RequestAdapterOptions { - compatible_surface: rao.compatible_surface.or(compatible_surface), - force_fallback_adapter: false, - power_preference: rao.power_preference, - }, - )); - - pollster::block_on(future) - }); - let adapter = adapter.ok_or(Error::AdapterNotFound)?; - - let (device, queue) = - pollster::block_on(adapter.request_device(&self.device_descriptor, None)) - .map_err(Error::DeviceNotFound)?; - + fn build_impl<'pixels>( + self, + device: Borrower<'pixels, wgpu::Device>, + queue: Borrower<'pixels, wgpu::Queue>, + surface: Borrower<'pixels, wgpu::Surface>, + render_texture_format: wgpu::TextureFormat, + ) -> Pixels<'pixels> { let present_mode = self.present_mode; - let render_texture_format = self.render_texture_format.unwrap_or_else(|| { - surface - .get_preferred_format(&adapter) - .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb) - }); // Create the backing texture let surface_size = self.surface_texture.size; @@ -246,7 +210,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> pixels.resize_with(pixels_buffer_size, Default::default); // Instantiate the Pixels struct - let context = PixelsContext { + let context = PixelsInternalContext { device, queue, surface, @@ -267,8 +231,86 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> }; pixels.reconfigure_surface(); + pixels + } + + /// Create a pixel buffer from the options builder. + /// + /// # Errors + /// + /// Returns an error when a [`wgpu::Adapter`] cannot be found. + pub fn build<'pixels>(self) -> Result, Error> { + let instance = wgpu::Instance::new(self.backend); + + // TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture + let surface = unsafe { instance.create_surface(self.surface_texture.window) }; + let compatible_surface = Some(&surface); + let request_adapter_options = &self.request_adapter_options; + let adapter = + wgpu::util::initialize_adapter_from_env(&instance, self.backend).or_else(|| { + let future = + instance.request_adapter(&request_adapter_options.as_ref().map_or_else( + || wgpu::RequestAdapterOptions { + compatible_surface, + force_fallback_adapter: false, + power_preference: + wgpu::util::power_preference_from_env().unwrap_or_default(), + }, + |rao| wgpu::RequestAdapterOptions { + compatible_surface: rao.compatible_surface.or(compatible_surface), + force_fallback_adapter: false, + power_preference: rao.power_preference, + }, + )); + + pollster::block_on(future) + }); + let adapter = adapter.ok_or(Error::AdapterNotFound)?; + + let render_texture_format = self.render_texture_format.unwrap_or_else(|| { + surface + .get_preferred_format(&adapter) + .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb) + }); + + let (device, queue) = + pollster::block_on(adapter.request_device(&self.device_descriptor, None)) + .map_err(Error::DeviceNotFound)?; + + let pixels = self.build_impl( + Borrower::Owned(device), + Borrower::Owned(queue), + Borrower::Owned(surface), + render_texture_format, + ); + Ok(pixels) } + + /// Create a pixel buffer from the options builder and existing `wgpu` references. + /// + /// Use this builder method when integrating `Pixels` into an application that already uses + /// `wgpu`. + pub fn build_from_wgpu<'wgpu>( + self, + surface: &'wgpu wgpu::Surface, + adapter: &'wgpu wgpu::Adapter, + device: &'wgpu wgpu::Device, + queue: &'wgpu wgpu::Queue, + ) -> Pixels<'wgpu> { + let render_texture_format = self.render_texture_format.unwrap_or_else(|| { + surface + .get_preferred_format(adapter) + .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb) + }); + + self.build_impl( + Borrower::Borrowed(device), + Borrower::Borrowed(queue), + Borrower::Borrowed(surface), + render_texture_format, + ) + } } pub(crate) fn create_backing_texture( diff --git a/src/lib.rs b/src/lib.rs index 8589c2ee..aa8cb0b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,12 +36,31 @@ pub use crate::renderers::ScalingRenderer; pub use raw_window_handle; use raw_window_handle::HasRawWindowHandle; use std::num::NonZeroU32; +use std::ops::Deref; use thiserror::Error; pub use wgpu; mod builder; mod renderers; +/// A container like [`std::borrow::Cow`] except it doesn't implement [`Clone`] or [`ToOwned`]. +#[derive(Debug)] +enum Borrower<'a, T> { + Owned(T), + Borrowed(&'a T), +} + +impl Deref for Borrower<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + Borrower::Owned(value) => value, + Borrower::Borrowed(value) => value, + } + } +} + /// A logical texture for a window surface. #[derive(Debug)] pub struct SurfaceTexture<'win, W: HasRawWindowHandle> { @@ -58,24 +77,23 @@ struct SurfaceSize { /// Provides the internal state for custom shaders. /// -/// A reference to this struct is given to the `render_function` closure when using -/// [`Pixels::render_with`]. +/// This struct is given to the `render_function` closure when using [`Pixels::render_with`]. #[derive(Debug)] -pub struct PixelsContext { +pub struct PixelsContext<'ctx> { /// The `Device` allows creating GPU resources. - pub device: wgpu::Device, + pub device: &'ctx wgpu::Device, /// The `Queue` provides access to the GPU command queue. - pub queue: wgpu::Queue, - - surface: wgpu::Surface, + pub queue: &'ctx wgpu::Queue, /// This is the texture that your raw data is copied to by [`Pixels::render`] or /// [`Pixels::render_with`]. - pub texture: wgpu::Texture, + pub texture: &'ctx wgpu::Texture, /// Provides access to the texture size. pub texture_extent: wgpu::Extent3d, + + /// Provides the texture's format. pub texture_format: wgpu::TextureFormat, /// Defines the "data rate" for the raw texture data. This is effectively the "bytes per pixel" @@ -85,15 +103,44 @@ pub struct PixelsContext { pub texture_format_size: f32, /// A default renderer to scale the input texture to the screen size. - pub scaling_renderer: ScalingRenderer, + pub scaling_renderer: &'ctx ScalingRenderer, +} + +/// The internal pixels context owns or borrows `wgpu` state for [`Pixels`]. A view of this context +/// is provided to callers of [`Pixels::render_with`] via [`PixelsInternalContext::as_view`]. +#[derive(Debug)] +struct PixelsInternalContext<'wgpu> { + device: Borrower<'wgpu, wgpu::Device>, + queue: Borrower<'wgpu, wgpu::Queue>, + surface: Borrower<'wgpu, wgpu::Surface>, + texture: wgpu::Texture, + texture_extent: wgpu::Extent3d, + texture_format: wgpu::TextureFormat, + texture_format_size: f32, + scaling_renderer: ScalingRenderer, +} + +impl<'ctx> PixelsInternalContext<'ctx> { + /// Create a [`PixelsContext`] from this internal context. + fn as_view(&'ctx self) -> PixelsContext<'ctx> { + PixelsContext { + device: &self.device, + queue: &self.queue, + texture: &self.texture, + texture_extent: self.texture_extent, + texture_format: self.texture_format, + texture_format_size: self.texture_format_size, + scaling_renderer: &self.scaling_renderer, + } + } } /// Represents a 2D pixel buffer with an explicit image resolution. /// /// See [`PixelsBuilder`] for building a customized pixel buffer. #[derive(Debug)] -pub struct Pixels { - context: PixelsContext, +pub struct Pixels<'wgpu> { + context: PixelsInternalContext<'wgpu>, surface_size: SurfaceSize, present_mode: wgpu::PresentMode, render_texture_format: wgpu::TextureFormat, @@ -162,7 +209,7 @@ impl<'win, W: HasRawWindowHandle> SurfaceTexture<'win, W> { } } -impl Pixels { +impl<'wgpu> Pixels<'wgpu> { /// Create a pixel buffer instance with default options. /// /// Any ratio differences between the pixel buffer texture size and surface texture size will @@ -194,7 +241,7 @@ impl Pixels { width: u32, height: u32, surface_texture: SurfaceTexture<'_, W>, - ) -> Result { + ) -> Result, Error> { PixelsBuilder::new(width, height, surface_texture).build() } @@ -361,7 +408,7 @@ impl Pixels { F: FnOnce( &mut wgpu::CommandEncoder, &wgpu::TextureView, - &PixelsContext, + PixelsContext, ) -> Result<(), DynError>, { let frame = self @@ -409,7 +456,7 @@ impl Pixels { .create_view(&wgpu::TextureViewDescriptor::default()); // Call the user's render function. - (render_function)(&mut encoder, &view, &self.context)?; + (render_function)(&mut encoder, &view, self.context.as_view())?; self.context.queue.submit(Some(encoder.finish())); frame.present(); @@ -551,8 +598,8 @@ impl Pixels { } /// Provides access to the internal [`PixelsContext`]. - pub fn context(&self) -> &PixelsContext { - &self.context + pub fn context(&self) -> PixelsContext { + self.context.as_view() } /// Get the render texture format.