#![deny(clippy::all)]
#![forbid(unsafe_code)]
pub use crate::builder::{check_texture_size, PixelsBuilder};
pub use crate::renderers::ScalingRenderer;
pub use raw_window_handle;
use thiserror::Error;
pub use wgpu;
mod builder;
mod renderers;
#[derive(Debug)]
pub struct SurfaceTexture<W: wgpu::WindowHandle> {
window: W,
size: SurfaceSize,
}
#[derive(Debug)]
struct SurfaceSize {
width: u32,
height: u32,
}
#[derive(Debug, Copy, Clone)]
pub enum ScalingMode {
PixelPerfect,
Fill,
}
#[derive(Debug)]
pub struct PixelsContext<'win> {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
surface: wgpu::Surface<'win>,
pub texture: wgpu::Texture,
pub texture_extent: wgpu::Extent3d,
pub texture_format: wgpu::TextureFormat,
pub texture_format_size: f32,
pub scaling_renderer: ScalingRenderer,
pub surface_capabilities: wgpu::SurfaceCapabilities,
}
#[derive(Debug)]
pub struct Pixels<'win> {
context: PixelsContext<'win>,
surface_size: SurfaceSize,
present_mode: wgpu::PresentMode,
render_texture_format: wgpu::TextureFormat,
surface_texture_format: wgpu::TextureFormat,
blend_state: wgpu::BlendState,
alpha_mode: wgpu::CompositeAlphaMode,
adapter: wgpu::Adapter,
pixels: Vec<u8>,
scaling_matrix_inverse: ultraviolet::Mat4,
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("No suitable `wgpu::Adapter` found.")]
AdapterNotFound,
#[error("No wgpu::Device found.")]
DeviceNotFound(#[from] wgpu::RequestDeviceError),
#[error("The GPU failed to acquire a surface frame.")]
Surface(#[from] wgpu::SurfaceError),
#[error("Unable to create a surface.")]
CreateSurface(#[from] wgpu::CreateSurfaceError),
#[error("Alpha mode `{0:?}` is not supported by the surface.")]
InvalidAlphaMode(wgpu::CompositeAlphaMode),
#[error("Texture creation failed: {0}")]
InvalidTexture(#[from] TextureError),
#[error("User-defined error.")]
UserDefined(#[from] DynError),
}
type DynError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum TextureError {
#[error("Texture width is invalid: {0}")]
TextureWidth(u32),
#[error("Texture height is invalid: {0}")]
TextureHeight(u32),
}
impl<W: wgpu::WindowHandle> SurfaceTexture<W> {
pub fn new(width: u32, height: u32, window: W) -> Self {
assert!(width > 0);
assert!(height > 0);
let size = SurfaceSize { width, height };
Self { window, size }
}
}
impl<'win> Pixels<'win> {
#[cfg(not(target_arch = "wasm32"))]
pub fn new<W: wgpu::WindowHandle + 'win>(
width: u32,
height: u32,
surface_texture: SurfaceTexture<W>,
) -> Result<Self, Error> {
PixelsBuilder::new(width, height, surface_texture).build()
}
pub async fn new_async<W: wgpu::WindowHandle + 'win>(
width: u32,
height: u32,
surface_texture: SurfaceTexture<W>,
) -> Result<Self, Error> {
PixelsBuilder::new(width, height, surface_texture)
.build_async()
.await
}
pub fn clear_color(&mut self, color: wgpu::Color) {
self.context.scaling_renderer.clear_color = color;
}
pub fn set_scaling_mode(&mut self, scaling_mode: ScalingMode) {
self.context.scaling_renderer.scaling_mode = scaling_mode;
}
pub fn adapter(&self) -> &wgpu::Adapter {
&self.adapter
}
pub fn resize_buffer(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) =
builder::create_backing_texture(
&self.context.device,
width,
height,
self.context.texture_format,
&self.surface_size,
self.render_texture_format,
self.context.scaling_renderer.clear_color,
self.blend_state,
self.context.scaling_renderer.scaling_mode,
)?;
self.scaling_matrix_inverse = scaling_matrix_inverse;
self.context.texture_extent = texture_extent;
self.context.texture = texture;
self.context.scaling_renderer = scaling_renderer;
self.pixels
.resize_with(pixels_buffer_size, Default::default);
Ok(())
}
pub fn resize_surface(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
check_texture_size(&self.context.device, width, height)?;
self.surface_size.width = width;
self.surface_size.height = height;
self.scaling_matrix_inverse = renderers::ScalingMatrix::new(
(
self.context.texture_extent.width as f32,
self.context.texture_extent.height as f32,
),
(width as f32, height as f32),
self.context.scaling_renderer.scaling_mode,
)
.transform
.inversed();
self.reconfigure_surface();
self.context
.scaling_renderer
.resize(&self.context.queue, width, height);
Ok(())
}
pub fn enable_vsync(&mut self, enable_vsync: bool) {
self.present_mode = if enable_vsync {
wgpu::PresentMode::AutoVsync
} else {
wgpu::PresentMode::AutoNoVsync
};
self.reconfigure_surface();
}
pub fn present_mode(&self) -> wgpu::PresentMode {
self.present_mode
}
pub fn set_present_mode(&mut self, present_mode: wgpu::PresentMode) {
self.present_mode = present_mode;
self.reconfigure_surface();
}
pub fn render(&self) -> Result<(), Error> {
self.render_with(|encoder, render_target, context| {
context.scaling_renderer.render(encoder, render_target);
Ok(())
})
}
pub fn render_with<F>(&self, render_function: F) -> Result<(), Error>
where
F: FnOnce(
&mut wgpu::CommandEncoder,
&wgpu::TextureView,
&PixelsContext,
) -> Result<(), DynError>,
{
let frame = self.context.surface.get_current_texture().or_else(|_| {
self.reconfigure_surface();
self.context.surface.get_current_texture()
})?;
let mut encoder =
self.context
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("pixels_command_encoder"),
});
let bytes_per_row =
(self.context.texture_extent.width as f32 * self.context.texture_format_size) as u32;
self.context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &self.context.texture,
mip_level: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
aspect: wgpu::TextureAspect::All,
},
&self.pixels,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(self.context.texture_extent.height),
},
self.context.texture_extent,
);
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
(render_function)(&mut encoder, &view, &self.context)?;
self.context.queue.submit(Some(encoder.finish()));
frame.present();
Ok(())
}
pub(crate) fn reconfigure_surface(&self) {
self.context.surface.configure(
&self.context.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.surface_texture_format,
width: self.surface_size.width,
height: self.surface_size.height,
present_mode: self.present_mode,
desired_maximum_frame_latency: 2,
alpha_mode: self.alpha_mode,
view_formats: vec![],
},
);
}
pub fn frame_mut(&mut self) -> &mut [u8] {
&mut self.pixels
}
pub fn frame(&self) -> &[u8] {
&self.pixels
}
pub fn window_pos_to_pixel(
&self,
physical_position: (f32, f32),
) -> Result<(usize, usize), (isize, isize)> {
let physical_width = self.surface_size.width as f32;
let physical_height = self.surface_size.height as f32;
let pixels_width = self.context.texture_extent.width as f32;
let pixels_height = self.context.texture_extent.height as f32;
let pos = ultraviolet::Vec4::new(
(physical_position.0 / physical_width - 0.5) * pixels_width,
(physical_position.1 / physical_height - 0.5) * pixels_height,
0.0,
1.0,
);
let pos = self.scaling_matrix_inverse * pos;
let offset_width = pixels_width.min(physical_width) / 2.0;
let offset_height = pixels_height.min(physical_height) / 2.0;
let pixel_x = (pos.x / pos.w + offset_width).floor() as isize;
let pixel_y = (pos.y / pos.w + offset_height).floor() as isize;
if pixel_x < 0
|| pixel_x >= self.context.texture_extent.width as isize
|| pixel_y < 0
|| pixel_y >= self.context.texture_extent.height as isize
{
Err((pixel_x, pixel_y))
} else {
Ok((pixel_x as usize, pixel_y as usize))
}
}
pub fn clamp_pixel_pos(&self, pos: (isize, isize)) -> (usize, usize) {
(
pos.0
.clamp(0, self.context.texture_extent.width as isize - 1) as usize,
pos.1
.clamp(0, self.context.texture_extent.height as isize - 1) as usize,
)
}
pub fn device(&self) -> &wgpu::Device {
&self.context.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.context.queue
}
pub fn texture(&self) -> &wgpu::Texture {
&self.context.texture
}
pub fn context(&self) -> &PixelsContext<'win> {
&self.context
}
pub fn surface_texture_format(&self) -> wgpu::TextureFormat {
self.surface_texture_format
}
pub fn render_texture_format(&self) -> wgpu::TextureFormat {
self.render_texture_format
}
}