use std::sync::Arc;
use unlit_ecs::World;
use unlit_wgpu::resources::{ResHandle, TextureExt, TextureView};
use crate::renderer::Renderer;
fn context_device(world: &World, renderer: &Renderer) -> wgpu::Device {
world
.get::<wgpu::Device>(renderer.context().device)
.expect("the context's device resource exists")
.clone()
}
fn context_graph<'w>(
world: &'w World,
renderer: &Renderer,
) -> impl core::ops::DerefMut<Target = unlit_wgpu::resources::ResourceGraph> + 'w {
world
.get_mut::<unlit_wgpu::resources::ResourceGraph>(renderer.context().graph)
.expect("the context's resource graph exists")
}
pub struct WindowSurface {
instance: wgpu::Instance,
window: Arc<::winit::window::Window>,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
color_format: wgpu::TextureFormat,
color_view: Option<ResHandle<TextureView>>,
}
impl WindowSurface {
pub fn new(
world: &World,
renderer: &mut Renderer,
instance: &wgpu::Instance,
adapter: &wgpu::Adapter,
window: Arc<::winit::window::Window>,
surface: wgpu::Surface<'static>,
) -> Self {
let size = window.inner_size();
let config = surface_config(&surface, adapter, size.width, size.height);
let color_format = color_format(&config);
let device = context_device(world, renderer);
surface.configure(&device, &config);
Self {
instance: instance.clone(),
window,
surface,
config,
color_format,
color_view: None,
}
}
pub fn size(&self) -> (u32, u32) {
(self.config.width, self.config.height)
}
pub fn format(&self) -> wgpu::TextureFormat {
self.config.format
}
pub fn color_format(&self) -> wgpu::TextureFormat {
self.color_format
}
pub fn window(&self) -> &Arc<::winit::window::Window> {
&self.window
}
pub fn resize(&mut self, world: &World, renderer: &mut Renderer, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
if (width, height) == (self.config.width, self.config.height) {
return;
}
self.config.width = width;
self.config.height = height;
let device = context_device(world, renderer);
self.surface.configure(&device, &self.config);
}
pub fn release(self, world: &World, renderer: &mut Renderer) {
drop(self);
context_graph(world, renderer).maintain();
}
pub fn acquire(&mut self, world: &World, renderer: &mut Renderer) -> Option<Frame> {
let device = context_device(world, renderer);
let surface_texture = match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(texture)
| wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&device, &self.config);
return None;
}
wgpu::CurrentSurfaceTexture::Lost => {
self.recreate_surface(&device);
return None;
}
wgpu::CurrentSurfaceTexture::Timeout
| wgpu::CurrentSurfaceTexture::Occluded
| wgpu::CurrentSurfaceTexture::Validation => return None,
};
let view = color_view_resource(&surface_texture.texture, self.color_format);
let color = match self.color_view.as_ref() {
Some(id) => {
context_graph(world, renderer)
.replace(id, view)
.expect("the color view is in the graph");
id.clone()
}
None => context_graph(world, renderer).insert(view, None),
};
self.color_view = Some(color.clone());
context_graph(world, renderer).maintain();
Some(Frame {
surface_texture,
color_view: color,
})
}
fn recreate_surface(&mut self, device: &wgpu::Device) {
if let Ok(surface) = self.instance.create_surface(self.window.clone()) {
self.surface = surface;
self.surface.configure(device, &self.config);
}
}
}
pub struct Frame {
surface_texture: wgpu::SurfaceTexture,
color_view: ResHandle<TextureView>,
}
impl Frame {
pub fn texture(&self) -> &wgpu::Texture {
&self.surface_texture.texture
}
pub fn color_view(&self) -> &ResHandle<TextureView> {
&self.color_view
}
pub fn present(self, queue: &wgpu::Queue) {
queue.present(self.surface_texture);
}
}
fn surface_config(
surface: &wgpu::Surface<'_>,
adapter: &wgpu::Adapter,
width: u32,
height: u32,
) -> wgpu::SurfaceConfiguration {
let caps = surface.get_capabilities(adapter);
let view_formats_supported = adapter
.get_downlevel_capabilities()
.flags
.contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS);
let (format, view_formats) = surface_format(&caps.formats, view_formats_supported);
wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
color_space: wgpu::SurfaceColorSpace::Auto,
width: width.max(1),
height: height.max(1),
present_mode: caps
.present_modes
.first()
.copied()
.unwrap_or(wgpu::PresentMode::Fifo),
alpha_mode: caps
.alpha_modes
.first()
.copied()
.unwrap_or(wgpu::CompositeAlphaMode::Auto),
view_formats,
desired_maximum_frame_latency: 2,
}
}
fn surface_format(
formats: &[wgpu::TextureFormat],
view_formats_supported: bool,
) -> (wgpu::TextureFormat, Vec<wgpu::TextureFormat>) {
let srgb = formats.iter().copied().find(wgpu::TextureFormat::is_srgb);
let format = srgb
.or_else(|| {
formats
.iter()
.copied()
.find(|format| format.add_srgb_suffix() != *format)
})
.unwrap_or_else(|| {
*formats
.first()
.expect("the adapter can present to the surface")
});
let view_formats = (view_formats_supported && !format.is_srgb())
.then(|| format.add_srgb_suffix())
.filter(|srgb| *srgb != format)
.into_iter()
.collect();
(format, view_formats)
}
fn color_format(config: &wgpu::SurfaceConfiguration) -> wgpu::TextureFormat {
if config.format.is_srgb() {
return config.format;
}
config
.view_formats
.iter()
.copied()
.find(|format| *format != config.format)
.unwrap_or(config.format)
}
fn color_view_resource(texture: &wgpu::Texture, format: wgpu::TextureFormat) -> TextureView {
TextureExt::create_view(
texture,
&wgpu::TextureViewDescriptor {
format: Some(format),
..Default::default()
},
)
}
#[cfg(test)]
mod tests {
use super::*;
const GLES_FORMATS: &[wgpu::TextureFormat] = &[
wgpu::TextureFormat::Rgba8Unorm,
wgpu::TextureFormat::Bgra8Unorm,
];
fn surface_configuration(format: wgpu::TextureFormat) -> wgpu::SurfaceConfiguration {
wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
color_space: wgpu::SurfaceColorSpace::Auto,
width: 1,
height: 1,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: Vec::new(),
desired_maximum_frame_latency: 2,
}
}
#[test]
fn an_srgb_surface_format_is_used_as_it_is() {
let formats = [
wgpu::TextureFormat::Bgra8Unorm,
wgpu::TextureFormat::Bgra8UnormSrgb,
];
for supported in [true, false] {
let (format, views) = surface_format(&formats, supported);
assert_eq!(format, wgpu::TextureFormat::Bgra8UnormSrgb);
assert!(views.is_empty(), "an sRGB format needs no view");
}
}
#[test]
fn a_surface_without_srgb_is_viewed_as_srgb_where_views_are_supported() {
let (format, views) = surface_format(GLES_FORMATS, true);
assert_eq!(format, wgpu::TextureFormat::Rgba8Unorm);
assert_eq!(views, vec![wgpu::TextureFormat::Rgba8UnormSrgb]);
}
#[test]
fn a_surface_without_srgb_advertises_no_view_where_they_are_unsupported() {
let (format, views) = surface_format(GLES_FORMATS, false);
assert_eq!(format, wgpu::TextureFormat::Rgba8Unorm);
assert!(views.is_empty());
let config = wgpu::SurfaceConfiguration {
format,
view_formats: views,
..surface_configuration(format)
};
assert_eq!(color_format(&config), wgpu::TextureFormat::Rgba8Unorm);
}
#[test]
fn a_surface_with_no_srgb_counterpart_is_presented_as_it_is() {
let formats = [wgpu::TextureFormat::Rgba16Float];
for supported in [true, false] {
let (format, views) = surface_format(&formats, supported);
assert_eq!(format, wgpu::TextureFormat::Rgba16Float);
assert!(views.is_empty(), "there is no sRGB view of an fp16 format");
}
}
}