use std::sync::Arc;
use winit::window::Window;
use crate::Error;
use crate::math::UVec2;
pub(crate) const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub(crate) struct Gpu {
instance: wgpu::Instance,
window: Arc<Window>,
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>,
format: wgpu::TextureFormat,
physical_size: UVec2,
}
impl Gpu {
pub(crate) async fn new(instance: wgpu::Instance, window: Arc<Window>) -> Result<Self, Error> {
let surface = instance
.create_surface(Arc::clone(&window))
.map_err(|error| Error::msg(format!("no rendering surface for the window: {error}")))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
})
.await
.map_err(|error| Error::msg(format!("no usable graphics adapter: {error}")))?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("mirage-engine"),
..Default::default()
})
.await
.map_err(|error| {
Error::msg(format!("the graphics adapter refused a device: {error}"))
})?;
let capabilities = surface.get_capabilities(&adapter);
let format = capabilities
.formats
.first()
.copied()
.ok_or_else(|| Error::msg("the rendering surface supports no texture format"))?;
let physical_size = physical_size(&window);
let mut gpu = Self {
instance,
window,
device,
queue,
surface,
format,
physical_size,
};
gpu.configure_surface();
Ok(gpu)
}
pub(crate) fn device(&self) -> &wgpu::Device {
&self.device
}
pub(crate) fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub(crate) fn window(&self) -> Arc<Window> {
Arc::clone(&self.window)
}
pub(crate) fn target_format(&self) -> wgpu::TextureFormat {
self.format.add_srgb_suffix()
}
pub(crate) fn overlay_format(&self) -> wgpu::TextureFormat {
self.format.remove_srgb_suffix()
}
pub(crate) fn physical_size(&self) -> UVec2 {
self.physical_size
}
pub(crate) fn request_frame(&self) {
if self.drawable_size().is_some() {
self.window.request_redraw();
}
}
pub(crate) fn resize(&mut self, physical_size: UVec2) {
if physical_size == self.physical_size {
return;
}
self.physical_size = physical_size;
self.configure_surface();
}
pub(crate) fn begin_frame(&mut self) -> Option<Frame> {
let size = self.drawable_size()?;
let surface = self.acquire_surface_texture()?;
let view = |format| {
surface.texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(format),
..Default::default()
})
};
let target = Target::new(
view(self.target_format()),
view(self.overlay_format()),
size,
);
Some(Frame { surface, target })
}
pub(crate) fn present(&self, frame: Frame) {
self.window.pre_present_notify();
frame.surface.present();
}
fn acquire_surface_texture(&mut self) -> Option<wgpu::SurfaceTexture> {
match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame) => Some(frame),
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => None,
wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
drop(frame);
self.configure_surface();
None
}
wgpu::CurrentSurfaceTexture::Outdated => {
self.configure_surface();
None
}
wgpu::CurrentSurfaceTexture::Lost => {
self.rebuild_surface();
None
}
wgpu::CurrentSurfaceTexture::Validation => {
log::error!("the graphics driver rejected the request for a frame");
None
}
}
}
fn drawable_size(&self) -> Option<UVec2> {
let size = self.physical_size;
(size.x > 0 && size.y > 0).then_some(size)
}
fn configure_surface(&mut self) {
let Some(size) = self.drawable_size() else {
return;
};
self.surface.configure(
&self.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.format,
view_formats: vec![self.target_format(), self.overlay_format()],
alpha_mode: wgpu::CompositeAlphaMode::Auto,
width: size.x,
height: size.y,
desired_maximum_frame_latency: 2,
present_mode: wgpu::PresentMode::AutoVsync,
},
);
}
fn rebuild_surface(&mut self) {
match self.instance.create_surface(Arc::clone(&self.window)) {
Ok(surface) => {
self.surface = surface;
self.configure_surface();
}
Err(error) => log::error!("the rendering surface could not be rebuilt: {error}"),
}
}
}
pub(crate) struct Target {
color: wgpu::TextureView,
encoded: wgpu::TextureView,
size: UVec2,
}
impl Target {
pub(crate) fn new(color: wgpu::TextureView, encoded: wgpu::TextureView, size: UVec2) -> Self {
Self {
color,
encoded,
size,
}
}
pub(crate) fn color(&self) -> &wgpu::TextureView {
&self.color
}
pub(crate) fn encoded(&self) -> &wgpu::TextureView {
&self.encoded
}
pub(crate) fn size(&self) -> UVec2 {
self.size
}
pub(crate) fn aspect(&self) -> f32 {
let size = self.size();
size.x as f32 / size.y as f32
}
}
pub(crate) struct Frame {
surface: wgpu::SurfaceTexture,
target: Target,
}
impl Frame {
pub(crate) fn target(&self) -> &Target {
&self.target
}
}
pub(crate) fn depth_texture(device: &wgpu::Device, size: UVec2, samples: u32) -> wgpu::Texture {
device.create_texture(&wgpu::TextureDescriptor {
label: Some("mirage-engine depth"),
size: wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: samples,
dimension: wgpu::TextureDimension::D2,
format: DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
})
}
fn physical_size(window: &Window) -> UVec2 {
let size = window.inner_size();
UVec2::new(size.width, size.height)
}