#![deny(clippy::all)]
#![forbid(unsafe_code)]
use std::cell::RefCell;
use std::rc::Rc;
pub use crate::macros::*;
pub use crate::render_pass::{BoxedRenderPass, Device, Queue, RenderPass};
use crate::renderers::Renderer;
use thiserror::Error;
pub use wgpu;
use wgpu::{Extent3d, TextureView};
mod macros;
mod render_pass;
mod renderers;
type RenderPassFactory = Box<dyn Fn(Device, Queue, &TextureView, &Extent3d) -> BoxedRenderPass>;
#[derive(Debug)]
pub struct SurfaceTexture {
surface: wgpu::Surface,
width: u32,
height: u32,
}
#[derive(Debug)]
pub struct Pixels {
device: Rc<wgpu::Device>,
queue: Rc<RefCell<wgpu::Queue>>,
swap_chain: wgpu::SwapChain,
surface_texture: SurfaceTexture,
present_mode: wgpu::PresentMode,
renderers: Vec<BoxedRenderPass>,
texture: wgpu::Texture,
texture_extent: wgpu::Extent3d,
texture_format_size: u32,
pixels: Vec<u8>,
scaling_matrix_inverse: ultraviolet::Mat4,
}
pub struct PixelsBuilder<'req> {
request_adapter_options: Option<wgpu::RequestAdapterOptions<'req>>,
device_descriptor: wgpu::DeviceDescriptor,
backend: wgpu::BackendBit,
width: u32,
height: u32,
pixel_aspect_ratio: f64,
present_mode: wgpu::PresentMode,
surface_texture: SurfaceTexture,
texture_format: wgpu::TextureFormat,
renderer_factories: Vec<RenderPassFactory>,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("No suitable `wgpu::Adapter` found")]
AdapterNotFound,
#[error("The GPU timed out when attempting to acquire the next texture or if a previous output is still alive.")]
Timeout,
}
impl SurfaceTexture {
pub fn new(width: u32, height: u32, surface: wgpu::Surface) -> SurfaceTexture {
assert!(width > 0);
assert!(height > 0);
SurfaceTexture {
surface,
width,
height,
}
}
}
impl Pixels {
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture) -> Result<Pixels, Error> {
PixelsBuilder::new(width, height, surface_texture).build()
}
pub fn resize(&mut self, width: u32, height: u32) {
self.surface_texture.width = width;
self.surface_texture.height = height;
self.scaling_matrix_inverse = renderers::ScalingMatrix::new(
(
self.texture_extent.width as f32,
self.texture_extent.height as f32,
),
(width as f32, height as f32),
)
.transform
.inversed();
self.swap_chain = self.device.create_swap_chain(
&self.surface_texture.surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: self.surface_texture.width,
height: self.surface_texture.height,
present_mode: self.present_mode,
},
);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
for renderer in self.renderers.iter_mut() {
renderer.resize(&mut encoder, width, height);
}
self.queue.borrow_mut().submit(&[encoder.finish()]);
}
pub fn render(&mut self) -> Result<(), Error> {
let frame = self
.swap_chain
.get_next_texture()
.map_err(|_| Error::Timeout)?;
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let mapped = self.device.create_buffer_mapped(&wgpu::BufferDescriptor {
label: None,
size: self.pixels.len() as u64,
usage: wgpu::BufferUsage::COPY_SRC,
});
mapped.data.copy_from_slice(&self.pixels);
let buffer = mapped.finish();
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: &buffer,
offset: 0,
bytes_per_row: self.texture_extent.width * self.texture_format_size,
rows_per_image: self.texture_extent.height,
},
wgpu::TextureCopyView {
texture: &self.texture,
mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
},
self.texture_extent,
);
for renderer in self.renderers.iter() {
renderer.render(&mut encoder, &frame.view);
}
self.queue.borrow_mut().submit(&[encoder.finish()]);
Ok(())
}
pub fn get_frame(&mut self) -> &mut [u8] {
&mut self.pixels
}
pub fn window_pos_to_pixel(
&self,
physical_position: (f32, f32),
) -> Result<(usize, usize), (isize, isize)> {
let physical_width = self.surface_texture.width as f32;
let physical_height = self.surface_texture.height as f32;
let pixels_width = self.texture_extent.width as f32;
let pixels_height = self.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 pos = (
pos.x / pos.w + pixels_width / 2.0,
-pos.y / pos.w + pixels_height / 2.0,
);
let pixel_x = pos.0.floor() as isize;
let pixel_y = pos.1.floor() as isize;
if pixel_x < 0
|| pixel_x >= self.texture_extent.width as isize
|| pixel_y < 0
|| pixel_y >= self.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.max(0).min(self.texture_extent.width as isize - 1) as usize,
pos.1.max(0).min(self.texture_extent.height as isize - 1) as usize,
)
}
}
impl<'req> PixelsBuilder<'req> {
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture) -> PixelsBuilder<'req> {
assert!(width > 0);
assert!(height > 0);
PixelsBuilder {
request_adapter_options: None,
device_descriptor: wgpu::DeviceDescriptor::default(),
backend: wgpu::BackendBit::PRIMARY,
width,
height,
pixel_aspect_ratio: 1.0,
present_mode: wgpu::PresentMode::Mailbox,
surface_texture,
texture_format: wgpu::TextureFormat::Rgba8UnormSrgb,
renderer_factories: Vec::new(),
}
}
pub const fn request_adapter_options(
mut self,
request_adapter_options: wgpu::RequestAdapterOptions<'req>,
) -> PixelsBuilder {
self.request_adapter_options = Some(request_adapter_options);
self
}
pub const fn device_descriptor(
mut self,
device_descriptor: wgpu::DeviceDescriptor,
) -> PixelsBuilder<'req> {
self.device_descriptor = device_descriptor;
self
}
pub const fn wgpu_backend(mut self, backend: wgpu::BackendBit) -> PixelsBuilder<'req> {
self.backend = backend;
self
}
pub fn pixel_aspect_ratio(mut self, pixel_aspect_ratio: f64) -> PixelsBuilder<'req> {
assert!(pixel_aspect_ratio > 0.0);
self.pixel_aspect_ratio = pixel_aspect_ratio;
self
}
pub fn enable_vsync(mut self, enable_vsync: bool) -> PixelsBuilder<'req> {
self.present_mode = if enable_vsync {
wgpu::PresentMode::Mailbox
} else {
wgpu::PresentMode::Immediate
};
self
}
pub const fn texture_format(
mut self,
texture_format: wgpu::TextureFormat,
) -> PixelsBuilder<'req> {
self.texture_format = texture_format;
self
}
pub fn add_render_pass(
mut self,
factory: impl Fn(Device, Queue, &TextureView, &Extent3d) -> BoxedRenderPass + 'static,
) -> PixelsBuilder<'req> {
self.renderer_factories.push(Box::new(factory));
self
}
pub fn build(self) -> Result<Pixels, Error> {
let adapter = futures_executor::block_on(wgpu::Adapter::request(
&self
.request_adapter_options
.unwrap_or(wgpu::RequestAdapterOptions {
compatible_surface: Some(&self.surface_texture.surface),
power_preference: wgpu::PowerPreference::Default,
}),
self.backend,
))
.ok_or(Error::AdapterNotFound)?;
let (device, queue) =
futures_executor::block_on(adapter.request_device(&self.device_descriptor));
let device = Rc::new(device);
let queue = Rc::new(RefCell::new(queue));
let width = self.width;
let height = self.height;
let texture_extent = wgpu::Extent3d {
width,
height,
depth: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: texture_extent,
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.texture_format,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
});
let texture_view = texture.create_default_view();
let texture_format_size = get_texture_format_size(self.texture_format);
let capacity = (width * height * texture_format_size) as usize;
let mut pixels = Vec::with_capacity(capacity);
pixels.resize_with(capacity, Default::default);
let present_mode = self.present_mode;
let surface_texture = self.surface_texture;
let swap_chain = device.create_swap_chain(
&surface_texture.surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: surface_texture.width,
height: surface_texture.height,
present_mode,
},
);
let scaling_matrix_inverse = renderers::ScalingMatrix::new(
(width as f32, height as f32),
(surface_texture.width as f32, surface_texture.height as f32),
)
.transform
.inversed();
let mut renderers = vec![Renderer::factory(
device.clone(),
queue.clone(),
&texture_view,
&texture_extent,
)];
renderers.extend(self.renderer_factories.iter().map(|f| {
f(
device.clone(),
queue.clone(),
&texture_view,
&texture_extent,
)
}));
Ok(Pixels {
device,
queue,
swap_chain,
surface_texture,
present_mode,
renderers,
texture,
texture_extent,
texture_format_size,
pixels,
scaling_matrix_inverse,
})
}
}
fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> u32 {
match texture_format {
wgpu::TextureFormat::R8Unorm
| wgpu::TextureFormat::R8Snorm
| wgpu::TextureFormat::R8Uint
| wgpu::TextureFormat::R8Sint => 1,
wgpu::TextureFormat::R16Uint
| wgpu::TextureFormat::R16Sint
| wgpu::TextureFormat::R16Float
| wgpu::TextureFormat::Rg8Unorm
| wgpu::TextureFormat::Rg8Snorm
| wgpu::TextureFormat::Rg8Uint
| wgpu::TextureFormat::Rg8Sint => 2,
wgpu::TextureFormat::R32Uint
| wgpu::TextureFormat::R32Sint
| wgpu::TextureFormat::R32Float
| wgpu::TextureFormat::Rg16Uint
| wgpu::TextureFormat::Rg16Sint
| wgpu::TextureFormat::Rg16Float
| wgpu::TextureFormat::Rgba8Unorm
| wgpu::TextureFormat::Rgba8UnormSrgb
| wgpu::TextureFormat::Rgba8Snorm
| wgpu::TextureFormat::Rgba8Uint
| wgpu::TextureFormat::Rgba8Sint
| wgpu::TextureFormat::Bgra8Unorm
| wgpu::TextureFormat::Bgra8UnormSrgb
| wgpu::TextureFormat::Rgb10a2Unorm
| wgpu::TextureFormat::Rg11b10Float
| wgpu::TextureFormat::Depth32Float
| wgpu::TextureFormat::Depth24Plus
| wgpu::TextureFormat::Depth24PlusStencil8 => 4,
wgpu::TextureFormat::Rg32Uint
| wgpu::TextureFormat::Rg32Sint
| wgpu::TextureFormat::Rg32Float
| wgpu::TextureFormat::Rgba16Uint
| wgpu::TextureFormat::Rgba16Sint
| wgpu::TextureFormat::Rgba16Float => 8,
wgpu::TextureFormat::Rgba32Uint
| wgpu::TextureFormat::Rgba32Sint
| wgpu::TextureFormat::Rgba32Float => 16,
}
}