use std::ops::Deref;
use std::ptr::NonNull;
use std::sync::Arc;
use wasdk::views::View;
use wasdk::webgpu::{GpuCanvasConfiguration, GpuCanvasContext};
use wgpu::custom::{
DispatchAdapter, DispatchDevice, DispatchSurfaceOutputDetail, DispatchTexture,
SurfaceInterface, SurfaceOutputDetailInterface,
};
use wgpu::rwh::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawWindowHandle,
WebCanvasWindowHandle, WindowHandle,
};
use wgpu::{
CompositeAlphaMode, PresentMode, SurfaceCapabilities, SurfaceColorSpaces,
SurfaceFormatCapabilities, SurfaceStatus, TextureFormat, TextureUsages,
};
use crate::convert;
use crate::device::WasdkDevice;
use crate::gap::wasi_gap;
use crate::texture::WasdkTexture;
#[derive(Clone, Debug)]
pub struct SharedView(Arc<View>);
impl SharedView {
pub fn new(view: View) -> SharedView {
SharedView(Arc::new(view))
}
}
impl Deref for SharedView {
type Target = View;
fn deref(&self) -> &View {
&self.0
}
}
impl HasWindowHandle for SharedView {
#[allow(unsafe_code)]
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
let handle = WebCanvasWindowHandle::new(NonNull::from(self).cast());
Ok(unsafe { WindowHandle::borrow_raw(handle.into()) })
}
}
impl HasDisplayHandle for SharedView {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
Ok(DisplayHandle::web())
}
}
pub(crate) fn shared_view_of(handle: RawWindowHandle) -> NonNull<SharedView> {
match handle {
RawWindowHandle::WebCanvas(handle) => handle.obj.cast(),
other => wasi_gap(format_args!(
"a surface from a window handle other than a view's (a {other:?})"
)),
}
}
pub(crate) fn capabilities(format: TextureFormat) -> SurfaceCapabilities {
SurfaceCapabilities {
formats: vec![format],
format_capabilities: vec![SurfaceFormatCapabilities {
format,
color_spaces: SurfaceColorSpaces::SRGB,
}],
present_modes: vec![PresentMode::Fifo],
alpha_modes: vec![
CompositeAlphaMode::Opaque,
CompositeAlphaMode::PreMultiplied,
],
usages: TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_SRC
| TextureUsages::COPY_DST,
}
}
fn check_present_mode(mode: PresentMode) {
match mode {
PresentMode::Fifo | PresentMode::AutoVsync | PresentMode::AutoNoVsync => {}
other => wasi_gap(format_args!(
"the present mode {other:?}: a view presents at its frames"
)),
}
}
#[derive(Debug)]
pub struct WasdkSurface {
view: Arc<View>,
context: GpuCanvasContext,
format: TextureFormat,
}
impl WasdkSurface {
pub(crate) fn new(view: &SharedView, format: TextureFormat) -> WasdkSurface {
WasdkSurface {
view: Arc::clone(&view.0),
context: view.context(),
format,
}
}
}
impl SurfaceInterface for WasdkSurface {
fn get_capabilities(&self, _adapter: &DispatchAdapter) -> SurfaceCapabilities {
capabilities(self.format)
}
fn configure(&self, device: &DispatchDevice, config: &wgpu::SurfaceConfiguration) {
check_present_mode(config.present_mode);
self.view.set_canvas_size(config.width, config.height);
let (color_space, tone_mapping) = convert::color_space_to_wasi(config.color_space);
let view_formats = config
.view_formats
.iter()
.map(|format| convert::texture_format_to_wasi(*format))
.collect();
self.context.configure(&GpuCanvasConfiguration {
device: WasdkDevice::of_dispatch(device).gpu_device(),
format: convert::texture_format_to_wasi(config.format),
usage: Some(convert::texture_usages_to_wasi(config.usage)),
view_formats: Some(view_formats),
color_space,
tone_mapping,
alpha_mode: Some(convert::alpha_mode_to_wasi(config.alpha_mode)),
});
}
fn get_current_texture(
&self,
) -> (
Option<DispatchTexture>,
SurfaceStatus,
DispatchSurfaceOutputDetail,
) {
let texture = self.context.get_current_texture();
let frame = WasdkSurfaceOutputDetail {
view: Arc::clone(&self.view),
};
(
Some(DispatchTexture::custom(WasdkTexture::new(texture))),
SurfaceStatus::Good,
DispatchSurfaceOutputDetail::custom(frame),
)
}
}
#[derive(Debug)]
pub struct WasdkSurfaceOutputDetail {
view: Arc<View>,
}
downcast!(WasdkSurfaceOutputDetail: dispatch DispatchSurfaceOutputDetail);
impl WasdkSurfaceOutputDetail {
pub(crate) fn present(&self) {
self.view.present();
}
}
impl SurfaceOutputDetailInterface for WasdkSurfaceOutputDetail {
fn texture_discard(&self) {}
fn texture_release(&self) {}
}
#[cfg(test)]
mod tests {
use wgpu::rwh::{AppKitWindowHandle, WebWindowHandle};
use super::*;
#[test]
fn a_view_serves_its_canvas_format_at_its_frames() {
let capabilities = capabilities(TextureFormat::Bgra8Unorm);
assert_eq!(capabilities.formats, [TextureFormat::Bgra8Unorm]);
assert_eq!(
capabilities.color_spaces(TextureFormat::Bgra8Unorm),
SurfaceColorSpaces::SRGB
);
assert_eq!(capabilities.present_modes, [PresentMode::Fifo]);
assert_eq!(capabilities.alpha_modes[0], CompositeAlphaMode::Opaque);
assert!(
capabilities
.usages
.contains(TextureUsages::RENDER_ATTACHMENT)
);
}
#[test]
fn an_automatic_present_mode_is_the_views_frames() {
check_present_mode(PresentMode::AutoNoVsync);
check_present_mode(PresentMode::Fifo);
}
#[test]
#[should_panic(expected = "Mailbox")]
fn a_present_mode_other_than_the_views_frames_is_a_gap() {
check_present_mode(PresentMode::Mailbox);
}
#[test]
fn a_shared_view_is_a_window_wgpu_makes_a_surface_for() {
fn surface_target<T: Into<wgpu::SurfaceTarget<'static>>>() {}
fn window<T: wgpu::WindowHandle + HasDisplayHandle>() {}
surface_target::<SharedView>();
window::<SharedView>();
}
#[test]
fn a_web_canvas_handle_points_at_the_shared_view() {
let pointer = NonNull::<SharedView>::dangling();
let handle = WebCanvasWindowHandle::new(pointer.cast());
assert_eq!(shared_view_of(handle.into()), pointer);
}
#[test]
#[should_panic(expected = "other than a view's")]
fn a_numbered_web_handle_is_not_a_view() {
shared_view_of(WebWindowHandle::new(1).into());
}
#[test]
#[should_panic(expected = "other than a view's")]
fn a_native_window_is_not_a_view() {
let window = NonNull::<u8>::dangling().cast();
shared_view_of(AppKitWindowHandle::new(window).into());
}
}