#[allow(unused_imports)]
use std::{
cell::{
BorrowMutError,
Cell,
RefCell,
RefMut,
UnsafeCell,
},
sync::{
Arc,
mpsc::{
Receiver,
},
},
};
use thiserror::Error;
use winit::{
dpi::PhysicalSize,
error::OsError,
window::{ Window, WindowAttributes, },
event_loop::ActiveEventLoop,
};
use crate::{
RenderContext,
RenderContextBuilder,
BuildContextError,
wgpu,
};
fn clamp_config_size(config: &mut wgpu::SurfaceConfiguration, device: &wgpu::Device, mut size: PhysicalSize<u32>) {
let max_allowed_size = device.limits().max_texture_dimension_2d;
let max_config_size = size.width.max(size.height);
if max_config_size > max_allowed_size {
let ratio = max_allowed_size as f32 / max_config_size.max(1) as f32;
size.width = (size.width as f32 * ratio) as u32;
size.height = (size.height as f32 * ratio) as u32;
}
config.width = size.width.max(1);
config.height = size.height.max(1);
}
#[cfg(any(target_family = "wasm", doc))]
pub fn get_gl_canvas(id: &'static str) -> Result<web_sys::HtmlCanvasElement, GetGlCanvasError> {
use web_sys::wasm_bindgen::prelude::*;
let canvas = web_sys::window().ok_or(GetGlCanvasError::WindowDocument)?
.document().ok_or(GetGlCanvasError::WindowDocument)?
.get_element_by_id(id).ok_or(GetGlCanvasError::Element(id))?
.dyn_into::<web_sys::HtmlCanvasElement>().or(Err(GetGlCanvasError::DynInto))?;
Ok(canvas)
}
#[derive(Clone, Debug, Error)]
pub enum GetGlCanvasError {
#[error("Couldn't get page window or document")]
WindowDocument,
#[error("Couldn't find canvas element by id \"{0}\"")]
Element(&'static str),
#[error("Couldn't convert element to canvas object")]
DynInto,
}
#[derive(Clone, Debug, Error)]
pub enum BuildDeferredContextError {
#[error(transparent)]
BuildContext(#[from] BuildContextError),
#[error("Context builder thread dropped before finishing")]
BuilderThreadDied,
}
#[derive(Clone, Debug, Error)]
pub enum GetDeferredContextError {
#[error(transparent)]
Build(#[from] BuildDeferredContextError),
#[error("No display handle has been provided yet to construct an Instance")]
RequiresDisplayHandle,
#[error("No compatible surface has been provided yet to construct an Adapter")]
RequiresSurface,
#[error("No context available yet, waiting on builder thread to finish")]
StillBuilding,
}
#[derive(Clone, Debug, Error)]
pub enum SurfaceError {
#[error("Surface is suboptimal")]
Suboptimal,
#[error("Getting surface timed out")]
Timeout,
#[error("Surface is occluded")]
Occluded,
#[error("Surface is outdated")]
Outdated,
#[error("Surface has been lost")]
Lost,
#[error("Validation error raised by wgpu")]
Validation,
}
impl SurfaceError {
fn from(texture: wgpu::CurrentSurfaceTexture) -> Result<wgpu::SurfaceTexture, Self> {
match texture {
wgpu::CurrentSurfaceTexture::Success(t) => Ok(t),
wgpu::CurrentSurfaceTexture::Suboptimal(_) => Err(Self::Suboptimal),
wgpu::CurrentSurfaceTexture::Timeout => Err(Self::Timeout),
wgpu::CurrentSurfaceTexture::Occluded => Err(Self::Occluded),
wgpu::CurrentSurfaceTexture::Outdated => Err(Self::Outdated),
wgpu::CurrentSurfaceTexture::Lost => Err(Self::Lost),
wgpu::CurrentSurfaceTexture::Validation => Err(Self::Validation),
}
}
}
#[derive(Clone, Debug, Error)]
pub enum ConfigSurfaceError {
#[error(transparent)]
CreateSurface(#[from] wgpu::CreateSurfaceError),
#[error("Surface is already active")]
InUse,
#[error("Surface hasn't been created or configured yet")]
NotInitialized,
#[error(transparent)]
SurfaceError(#[from] SurfaceError),
#[error("Surface not supported by adapter - don't mix & match between RenderContexts!")]
Unsupported,
}
impl From<BorrowMutError> for ConfigSurfaceError {
fn from(_e: BorrowMutError) -> Self {
Self::InUse
}
}
#[derive(Debug, Error)]
pub enum CreateWindowError {
#[error(transparent)]
Window(#[from] OsError),
#[error(transparent)]
ConfigSurface(#[from] ConfigSurfaceError),
}
impl From<wgpu::CreateSurfaceError> for CreateWindowError {
fn from(e: wgpu::CreateSurfaceError) -> Self {
Self::ConfigSurface(e.into())
}
}
#[derive(Debug, Default)]
pub struct SurfaceConfigOptions {
pub present_mode: Option<wgpu::PresentMode>,
pub frame_latency: Option<u32>,
pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
}
#[derive(Debug)]
pub struct SurfaceWindow {
config_opts: SurfaceConfigOptions,
surface: RefCell<Option<Arc<wgpu::Surface<'static>>>>,
window: Arc<Window>,
target_size: Cell<Option<PhysicalSize<u32>>>,
reconfigure: Cell<bool>,
}
impl SurfaceWindow {
pub fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<Self, OsError> {
let window = Arc::new(event_loop.create_window(attributes)?);
Ok(Self {
config_opts,
surface: RefCell::new(None),
window,
target_size: Cell::new(None),
reconfigure: Cell::new(false),
})
}
pub fn try_get_config(&self) -> Option<wgpu::SurfaceConfiguration> {
unsafe { &*self.surface.as_ptr() }
.as_ref().and_then(|s| s.get_configuration())
}
pub fn window(&self) -> &Window {
&self.window
}
fn make_surface(&self, instance: &wgpu::Instance) -> Result<wgpu::Surface<'static>, wgpu::CreateSurfaceError> {
instance.create_surface(wgpu::SurfaceTarget::DisplayAndWindow(Box::new(self.window.clone())))
}
fn make_config(&self, surface: &wgpu::Surface<'static>, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Option<wgpu::SurfaceConfiguration> {
let mut config = surface.get_default_config(adapter, 0, 0)?;
if let Some(pm) = self.config_opts.present_mode { config.present_mode = pm; }
if let Some(fl) = self.config_opts.frame_latency { config.desired_maximum_frame_latency = fl; }
if let Some(am) = self.config_opts.alpha_mode { config.alpha_mode = am; }
let size = self.target_size.get()
.unwrap_or_else(|| self.get_size());
clamp_config_size(&mut config, device, size);
Some(config)
}
pub fn init_surface(&self, instance: &wgpu::Instance, adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue) -> Result<ActiveSurfaceWindow<'_>, ConfigSurfaceError> {
let mut surface_lock = self.surface.try_borrow_mut()?;
let reconfigure = self.reconfigure.replace(false);
let old_size = surface_lock.as_ref().and_then(|s| s.get_configuration()).map(|c| (c.width, c.height));
let mut resized = false;
if let Some(surface) = &*surface_lock && (surface.get_configuration().is_none() || reconfigure) {
let config = self.make_config(surface, adapter, device)
.ok_or(ConfigSurfaceError::Unsupported)?;
surface.configure(device, &config);
resized = old_size != Some((config.width, config.height));
}
let texture = match surface_lock.as_ref().map(|s| (s, s.get_current_texture())) {
Some((_, wgpu::CurrentSurfaceTexture::Success(t))) => t,
Some((_, wgpu::CurrentSurfaceTexture::Timeout)) => return Err(SurfaceError::Timeout.into()),
Some((_, wgpu::CurrentSurfaceTexture::Occluded)) => return Err(SurfaceError::Occluded.into()),
Some((surface, wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated)) => {
let config = self.make_config(surface, adapter, device)
.ok_or(ConfigSurfaceError::Unsupported)?;
surface.configure(device, &config);
resized = old_size != Some((config.width, config.height));
SurfaceError::from(surface.get_current_texture())?
},
None | Some((_, wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Validation)) => {
let new_surface = Arc::new(self.make_surface(instance)?);
let surface = surface_lock.insert(new_surface);
let config = self.make_config(surface, adapter, device)
.ok_or(ConfigSurfaceError::Unsupported)?;
surface.configure(device, &config);
resized = old_size != Some((config.width, config.height));
SurfaceError::from(surface.get_current_texture())?
},
};
let view = texture.texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(texture.texture.format()),
..Default::default()
});
Ok(ActiveSurfaceWindow {
#[cfg(feature = "wgpu-30")]
queue: queue.clone(),
surface: RefMut::map(surface_lock, |sl| sl.as_mut().expect("Surface should have been created")),
window: &self.window,
texture,
view,
resized,
})
}
pub fn init_with_context(&self, context: &RenderContext) -> Result<ActiveSurfaceWindow<'_>, ConfigSurfaceError> {
self.init_surface(&context.instance, &context.adapter, &context.device, &context.queue)
}
pub fn try_drop_surface(&self) -> Result<(), BorrowMutError> {
let _ = self.surface.try_borrow_mut()?.take();
Ok(())
}
pub fn drop_surface(&mut self) {
let _ = self.surface.get_mut().take();
}
pub fn resize(&self, size: Option<PhysicalSize<u32>>) {
self.target_size.set(size);
self.reconfigure();
}
pub fn reconfigure(&self) {
self.reconfigure.set(true);
}
pub fn get_size(&self) -> PhysicalSize<u32> {
self.window.inner_size()
}
}
#[must_use]
pub struct ActiveSurfaceWindow<'w> {
#[cfg(feature = "wgpu-30")]
queue: wgpu::Queue,
surface: RefMut<'w, Arc<wgpu::Surface<'static>>>,
window: &'w Window,
texture: wgpu::SurfaceTexture,
view: wgpu::TextureView,
resized: bool,
}
impl ActiveSurfaceWindow<'_> {
pub unsafe fn surface(&self) -> &wgpu::Surface<'static> {
&self.surface
}
pub fn config(&self) -> wgpu::SurfaceConfiguration {
self.surface.get_configuration()
.expect("Surface should have been configured before constructing ActiveSurfaceWindow")
}
pub fn window(&self) -> &Window {
self.window
}
pub fn get_size(&self) -> PhysicalSize<u32> {
self.window.inner_size()
}
pub fn get_config_size(&self) -> PhysicalSize<u32> {
let config = self.config();
PhysicalSize {
width: config.width,
height: config.height,
}
}
pub fn texture(&self) -> &wgpu::SurfaceTexture {
&self.texture
}
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn resized(&self) -> bool {
self.resized
}
pub fn as_colour_attachment(&self, clear: Option<wgpu::Color>) -> wgpu::RenderPassColorAttachment<'_> {
wgpu::RenderPassColorAttachment {
view: &self.view,
depth_slice: None,
resolve_target: None, ops: wgpu::Operations {
load: clear.map_or(wgpu::LoadOp::Load, wgpu::LoadOp::Clear),
store: wgpu::StoreOp::Store,
},
}
}
pub fn present_texture(self) {
self.window.pre_present_notify();
#[cfg(feature = "wgpu-29")]
self.texture.present();
#[cfg(feature = "wgpu-30")]
self.queue.present(self.texture);
}
}
#[derive(Debug)]
#[allow(dead_code)]
enum ContextState {
WaitingForDisplayHandle {
builder: RenderContextBuilder<crate::InstanceSettings>,
},
WaitingForSurface {
builder: RenderContextBuilder<wgpu::Instance>,
},
Building {
instance: wgpu::Instance,
receiver: Receiver<Result<RenderContext, BuildContextError>>,
},
Resolved {
context: Result<RenderContext, (wgpu::Instance, BuildDeferredContextError)>,
},
Switching,
}
impl ContextState {
fn build(builder: RenderContextBuilder<wgpu::Instance>) -> Self {
#[cfg(target_family = "wasm")]
{
if builder.compatible_surface.is_none() {
return Self::WaitingForSurface { builder, };
}
use std::sync::mpsc::sync_channel;
let (send, recv) = sync_channel::<Result<RenderContext, BuildContextError>>(1);
let instance = builder.get_instance();
web_sys::js_sys::futures::spawn_local(async move {
let context = builder.build().await;
let _ = send.send(context);
});
Self::Building { instance, receiver: recv, }
}
#[cfg(not(target_family = "wasm"))]
{
let instance = builder.get_instance();
let context = pollster::block_on(builder.build())
.map_err(|e| (instance, e.into()));
Self::Resolved { context, }
}
}
fn provide_display_handle_with(&mut self, get_display_handle: impl FnOnce() -> Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) -> wgpu::Instance {
let mut switching = std::mem::replace(self, Self::Switching);
let instance = match switching {
Self::WaitingForDisplayHandle { mut builder, } => {
builder.set_display_handle(get_display_handle());
let builder = builder.build_instance();
let instance = builder.get_instance();
switching = Self::build(builder);
instance
},
Self::WaitingForSurface { ref builder, } => builder.get_instance(),
Self::Building { ref instance, .. } => instance.clone(),
Self::Resolved { ref context, } => context.as_ref().map(|c| c.instance.clone())
.unwrap_or_else(|e| e.0.clone()),
Self::Switching => unreachable!(),
};
let _ = std::mem::replace(self, switching);
instance
}
fn provide_surface_with(&mut self, get_surface: impl FnOnce() -> Arc<wgpu::Surface<'static>>) {
let mut switching = std::mem::replace(self, Self::Switching);
match switching {
Self::WaitingForDisplayHandle { .. } => {
unreachable!("Display handle should have been provided before a surface");
},
Self::WaitingForSurface { mut builder, } => {
builder.set_compatible_surface(Some(get_surface()));
switching = Self::build(builder);
},
Self::Switching => unreachable!(),
_ => {},
}
let _ = std::mem::replace(self, switching);
}
fn poll_resolve_context(&mut self) -> Result<&RenderContext, GetDeferredContextError> {
match self {
Self::WaitingForDisplayHandle { .. } => Err(GetDeferredContextError::RequiresDisplayHandle),
Self::WaitingForSurface { .. } => Err(GetDeferredContextError::RequiresSurface),
Self::Building { instance, receiver, } => {
use std::sync::mpsc::TryRecvError;
match receiver.try_recv() {
Ok(context) => {
let context = context.map_err(|e| (instance.clone(), e.into()));
*self = Self::Resolved { context, };
let Self::Resolved { context, .. } = self else { unreachable!() };
Ok(context.as_ref().map_err(|e| e.1.clone())?)
},
Err(TryRecvError::Disconnected) => {
*self = Self::Resolved {
context: Err((instance.clone(), BuildDeferredContextError::BuilderThreadDied)),
};
Err(BuildDeferredContextError::BuilderThreadDied.into())
},
Err(TryRecvError::Empty) => Err(GetDeferredContextError::StillBuilding),
}
},
Self::Resolved { context, .. } => Ok(context.as_ref().map_err(|e| e.1.clone())?),
Self::Switching => unreachable!(),
}
}
}
#[derive(Debug)]
pub struct DeferredContext {
context: UnsafeCell<ContextState>,
}
impl DeferredContext {
pub fn new(builder: RenderContextBuilder<crate::InstanceSettings>) -> Self {
if builder.instance.display_handle.is_some() {
Self {
context: ContextState::build(builder.build_instance()).into(),
}
} else {
Self {
context: ContextState::WaitingForDisplayHandle { builder, }.into(),
}
}
}
pub fn new_with_instance(builder: RenderContextBuilder<wgpu::Instance>) -> Self {
Self {
context: ContextState::build(builder).into(),
}
}
pub fn create_window(&self, event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<SurfaceWindow, CreateWindowError> {
let context_state = unsafe { &mut *self.context.get() };
let instance = context_state.provide_display_handle_with(|| Box::new(event_loop.owned_display_handle()));
let mut window = SurfaceWindow::new(event_loop, attributes, config_opts)?;
let surface = Arc::new(window.make_surface(&instance)?);
context_state.provide_surface_with(|| surface.clone());
let _ = window.surface.get_mut().insert(surface);
Ok(window)
}
pub fn get_instance(&self) -> Option<wgpu::Instance> {
match unsafe { &*self.context.get() } {
ContextState::WaitingForDisplayHandle { .. } => None,
ContextState::WaitingForSurface { builder, } => Some(builder.get_instance()),
ContextState::Building { instance, .. } => Some(instance.clone()),
ContextState::Resolved { context: Ok(c), } => Some(c.instance.clone()),
ContextState::Resolved { context: Err((i, _)), } => Some(i.clone()),
ContextState::Switching => unreachable!(),
}
}
pub fn get_context(&self) -> Result<&RenderContext, GetDeferredContextError> {
let context_state = unsafe { &mut *self.context.get() };
context_state.poll_resolve_context()
}
}