use std::sync::Arc;
use unlit_ecs::LocalWorld;
use unlit_wgpu::render_attachments::default_depth_stencil_format;
use unlit_wgpu::resources::{Resource, ResourceId};
use crate::renderer::Renderer;
fn context_device(world: &LocalWorld, 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 LocalWorld,
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,
sample_count: u32,
color_format: wgpu::TextureFormat,
depth_view: ResourceId,
msaa_view: Option<ResourceId>,
color_view: Option<ResourceId>,
}
impl WindowSurface {
pub fn new(
world: &LocalWorld,
renderer: &mut Renderer,
instance: &wgpu::Instance,
adapter: &wgpu::Adapter,
window: Arc<::winit::window::Window>,
surface: wgpu::Surface<'static>,
sample_count: u32,
) -> 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);
let (depth, msaa) = create_attachments(
&device,
color_format,
config.width,
config.height,
sample_count,
);
let mut graph = context_graph(world, renderer);
let depth_view = graph
.insert_strong(view_resource(&depth), &[])
.expect("a texture view has no dependencies");
let msaa_view = msaa.map(|texture| {
graph
.insert_strong(view_resource(&texture), &[])
.expect("a texture view has no dependencies")
});
drop(graph);
Self {
instance: instance.clone(),
window,
surface,
config,
sample_count,
color_format,
depth_view,
msaa_view,
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: &LocalWorld, 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);
let (depth, msaa) =
create_attachments(&device, self.color_format, width, height, self.sample_count);
let mut graph = context_graph(world, renderer);
graph
.replace(self.depth_view, view_resource(&depth))
.expect("the depth view is in the graph");
if let (Some(id), Some(texture)) = (self.msaa_view, msaa) {
graph
.replace(id, view_resource(&texture))
.expect("the multisample view is in the graph");
}
}
pub fn release(self, world: &LocalWorld, renderer: &mut Renderer) {
renderer.unset_render_target(world);
let mut graph = context_graph(world, renderer);
if let Some(id) = self.color_view {
graph.remove_drop(id);
}
graph.remove_drop(self.depth_view);
if let Some(id) = self.msaa_view {
graph.remove_drop(id);
}
}
pub fn acquire(&mut self, world: &LocalWorld, 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 {
Some(id) => {
context_graph(world, renderer)
.replace(id, view)
.expect("the color view is in the graph");
id
}
None => context_graph(world, renderer)
.insert_strong(view, &[])
.expect("a texture view has no dependencies"),
};
self.color_view = Some(color);
renderer.set_render_target(world, Some(color), Some(self.depth_view), self.msaa_view);
Some(Frame { surface_texture })
}
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,
}
impl Frame {
pub fn texture(&self) -> &wgpu::Texture {
&self.surface_texture.texture
}
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 format = srgb_format(&caps.formats).or_else(|| {
caps.formats
.iter()
.copied()
.find(|format| format.add_srgb_suffix() != *format)
});
let format = format.unwrap_or_else(|| {
*caps
.formats
.first()
.expect("the adapter can present to the surface")
});
let srgb = format.add_srgb_suffix();
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: (srgb != format).then_some(srgb).into_iter().collect(),
desired_maximum_frame_latency: 2,
}
}
fn srgb_format(formats: &[wgpu::TextureFormat]) -> Option<wgpu::TextureFormat> {
formats.iter().copied().find(wgpu::TextureFormat::is_srgb)
}
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 create_attachments(
device: &wgpu::Device,
color_format: wgpu::TextureFormat,
width: u32,
height: u32,
sample_count: u32,
) -> (wgpu::Texture, Option<wgpu::Texture>) {
let extent = wgpu::Extent3d {
width: width.max(1),
height: height.max(1),
depth_or_array_layers: 1,
};
let depth = device.create_texture(&wgpu::TextureDescriptor {
label: Some("unlit3d::winit::depth"),
size: extent,
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: default_depth_stencil_format(device),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT_ATTACHMENT,
view_formats: &[],
});
let msaa = (sample_count > 1).then(|| {
device.create_texture(&wgpu::TextureDescriptor {
label: Some("unlit3d::winit::msaa"),
size: extent,
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: color_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TRANSIENT_ATTACHMENT,
view_formats: &[],
})
});
(depth, msaa)
}
fn view_resource(texture: &wgpu::Texture) -> Resource {
Resource::from(texture.create_view(&wgpu::TextureViewDescriptor::default()))
}
fn color_view_resource(texture: &wgpu::Texture, format: wgpu::TextureFormat) -> Resource {
Resource::TextureView {
view: texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(format),
..Default::default()
}),
format,
}
}