use crate::window::{PresentMode, WindowError};
const BLIT_USAGE: wgpu::TextureUsages = wgpu::TextureUsages::TEXTURE_BINDING;
pub(crate) struct WindowSurface {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
indirect: Option<Indirect>,
}
struct Indirect {
blitter: wgpu::util::TextureBlitter,
target: wgpu::Texture,
target_view: wgpu::TextureView,
usage: wgpu::TextureUsages,
}
impl WindowSurface {
pub(crate) fn new(
instance: &wgpu::Instance,
surface: wgpu::Surface<'static>,
width: u32,
height: u32,
present_mode: PresentMode,
target_usage: Option<wgpu::TextureUsages>,
) -> Result<Self, WindowError> {
pollster::block_on(Self::new_async(
instance,
surface,
width,
height,
present_mode,
target_usage,
))
}
pub(crate) async fn new_async(
instance: &wgpu::Instance,
surface: wgpu::Surface<'static>,
width: u32,
height: u32,
present_mode: PresentMode,
target_usage: Option<wgpu::TextureUsages>,
) -> Result<Self, WindowError> {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.map_err(|_| WindowError::NoAdapter)?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("hephaestus.window.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
experimental_features: wgpu::ExperimentalFeatures::default(),
})
.await
.map_err(|e| WindowError::DeviceRequest(e.to_string()))?;
let capabilities = surface.get_capabilities(&adapter);
let format = pick_surface_format(&capabilities.formats)
.ok_or(WindowError::UnsupportedSurfaceFormat)?;
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: width.max(1),
height: height.max(1),
present_mode: present_mode.to_wgpu(),
desired_maximum_frame_latency: 2,
alpha_mode: wgpu::CompositeAlphaMode::Opaque,
view_formats: vec![],
};
surface.configure(&device, &config);
let indirect = target_usage.map(|usage| {
let usage = usage | BLIT_USAGE;
let (target, target_view) = create_target(&device, config.width, config.height, usage);
Indirect {
blitter: wgpu::util::TextureBlitter::new(&device, format),
target,
target_view,
usage,
}
});
Ok(Self {
device,
queue,
surface,
config,
indirect,
})
}
pub(crate) fn device(&self) -> &wgpu::Device {
&self.device
}
pub(crate) fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub(crate) fn format(&self) -> wgpu::TextureFormat {
self.config.format
}
pub(crate) fn size(&self) -> (u32, u32) {
(self.config.width, self.config.height)
}
pub(crate) fn resize(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
if self.config.width == width && self.config.height == height {
return;
}
self.config.width = width;
self.config.height = height;
self.surface.configure(&self.device, &self.config);
if let Some(indirect) = self.indirect.as_mut() {
let (target, view) = create_target(&self.device, width, height, indirect.usage);
indirect.target = target;
indirect.target_view = view;
}
}
pub(crate) fn draw_frame(
&self,
render: impl FnOnce(&wgpu::TextureView) -> Result<(), WindowError>,
pre_present: impl FnOnce(),
) -> Result<bool, WindowError> {
let frame = match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame)
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
wgpu::CurrentSurfaceTexture::Timeout
| wgpu::CurrentSurfaceTexture::Occluded
| wgpu::CurrentSurfaceTexture::Outdated => return Ok(false),
wgpu::CurrentSurfaceTexture::Lost => {
self.surface.configure(&self.device, &self.config);
return Ok(false);
}
wgpu::CurrentSurfaceTexture::Validation => {
return Err(WindowError::Surface(
"surface validation error while acquiring a frame".into(),
))
}
};
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
match self.indirect.as_ref() {
Some(indirect) => {
render(&indirect.target_view)?;
let mut encoder =
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("hephaestus.window.blit"),
});
indirect
.blitter
.copy(&self.device, &mut encoder, &indirect.target_view, &view);
self.queue.submit([encoder.finish()]);
}
None => render(&view)?,
}
pre_present();
frame.present();
Ok(true)
}
}
fn create_target(
device: &wgpu::Device,
width: u32,
height: u32,
usage: wgpu::TextureUsages,
) -> (wgpu::Texture, wgpu::TextureView) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("hephaestus.window.target"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view)
}
fn pick_surface_format(formats: &[wgpu::TextureFormat]) -> Option<wgpu::TextureFormat> {
formats.iter().copied().find(|f| {
matches!(
f,
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use wgpu::TextureFormat;
#[test]
fn prefers_the_non_srgb_format_the_surface_offers() {
let formats = [TextureFormat::Bgra8UnormSrgb, TextureFormat::Bgra8Unorm];
assert_eq!(
pick_surface_format(&formats),
Some(TextureFormat::Bgra8Unorm)
);
}
#[test]
fn takes_the_first_acceptable_format_in_order() {
let formats = [TextureFormat::Rgba8Unorm, TextureFormat::Bgra8Unorm];
assert_eq!(
pick_surface_format(&formats),
Some(TextureFormat::Rgba8Unorm)
);
}
#[test]
fn rejects_an_srgb_only_surface() {
let formats = [TextureFormat::Bgra8UnormSrgb, TextureFormat::Rgba8UnormSrgb];
assert_eq!(pick_surface_format(&formats), None);
}
}