Skip to content

Commit

Permalink
Experimental support for integration with existing wgpu apps.
Browse files Browse the repository at this point in the history
- Closes #107
  • Loading branch information
parasyte committed Nov 3, 2021
1 parent 5b00f7e commit 0a5cbf4
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 69 deletions.
2 changes: 1 addition & 1 deletion examples/imgui-winit/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion examples/imgui-winit/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
});
Expand Down
8 changes: 4 additions & 4 deletions examples/minimal-egui/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal-egui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
});
Expand Down
132 changes: 87 additions & 45 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<Pixels, 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 (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;
Expand All @@ -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,
Expand All @@ -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<Pixels<'pixels>, 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(
Expand Down
81 changes: 64 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> 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> {
Expand All @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -194,7 +241,7 @@ impl Pixels {
width: u32,
height: u32,
surface_texture: SurfaceTexture<'_, W>,
) -> Result<Pixels, Error> {
) -> Result<Pixels<'wgpu>, Error> {
PixelsBuilder::new(width, height, surface_texture).build()
}

Expand Down Expand Up @@ -361,7 +408,7 @@ impl Pixels {
F: FnOnce(
&mut wgpu::CommandEncoder,
&wgpu::TextureView,
&PixelsContext,
PixelsContext,
) -> Result<(), DynError>,
{
let frame = self
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 0a5cbf4

Please sign in to comment.