1use std::ops::Deref;
18use std::ptr::NonNull;
19use std::sync::Arc;
20
21use wasdk::views::View;
22use wasdk::webgpu::{GpuCanvasConfiguration, GpuCanvasContext};
23use wgpu::custom::{
24 DispatchAdapter, DispatchDevice, DispatchSurfaceOutputDetail, DispatchTexture,
25 SurfaceInterface, SurfaceOutputDetailInterface,
26};
27use wgpu::rwh::{
28 DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawWindowHandle,
29 WebCanvasWindowHandle, WindowHandle,
30};
31use wgpu::{
32 CompositeAlphaMode, PresentMode, SurfaceCapabilities, SurfaceColorSpaces,
33 SurfaceFormatCapabilities, SurfaceStatus, TextureFormat, TextureUsages,
34};
35
36use crate::convert;
37use crate::device::WasdkDevice;
38use crate::gap::wasi_gap;
39use crate::texture::WasdkTexture;
40
41#[derive(Clone, Debug)]
52pub struct SharedView(Arc<View>);
53
54impl SharedView {
55 pub fn new(view: View) -> SharedView {
56 SharedView(Arc::new(view))
57 }
58}
59
60impl Deref for SharedView {
61 type Target = View;
62
63 fn deref(&self) -> &View {
64 &self.0
65 }
66}
67
68impl HasWindowHandle for SharedView {
69 #[allow(unsafe_code)]
71 fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
72 let handle = WebCanvasWindowHandle::new(NonNull::from(self).cast());
73 Ok(unsafe { WindowHandle::borrow_raw(handle.into()) })
75 }
76}
77
78impl HasDisplayHandle for SharedView {
79 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
81 Ok(DisplayHandle::web())
82 }
83}
84
85pub(crate) fn shared_view_of(handle: RawWindowHandle) -> NonNull<SharedView> {
89 match handle {
90 RawWindowHandle::WebCanvas(handle) => handle.obj.cast(),
91 other => wasi_gap(format_args!(
92 "a surface from a window handle other than a view's (a {other:?})"
93 )),
94 }
95}
96
97pub(crate) fn capabilities(format: TextureFormat) -> SurfaceCapabilities {
100 SurfaceCapabilities {
101 formats: vec![format],
102 format_capabilities: vec![SurfaceFormatCapabilities {
103 format,
104 color_spaces: SurfaceColorSpaces::SRGB,
105 }],
106 present_modes: vec![PresentMode::Fifo],
107 alpha_modes: vec![
108 CompositeAlphaMode::Opaque,
109 CompositeAlphaMode::PreMultiplied,
110 ],
111 usages: TextureUsages::RENDER_ATTACHMENT
112 | TextureUsages::TEXTURE_BINDING
113 | TextureUsages::COPY_SRC
114 | TextureUsages::COPY_DST,
115 }
116}
117
118fn check_present_mode(mode: PresentMode) {
121 match mode {
122 PresentMode::Fifo | PresentMode::AutoVsync | PresentMode::AutoNoVsync => {}
123 other => wasi_gap(format_args!(
124 "the present mode {other:?}: a view presents at its frames"
125 )),
126 }
127}
128
129#[derive(Debug)]
131pub struct WasdkSurface {
132 view: Arc<View>,
133 context: GpuCanvasContext,
134 format: TextureFormat,
136}
137
138impl WasdkSurface {
139 pub(crate) fn new(view: &SharedView, format: TextureFormat) -> WasdkSurface {
141 WasdkSurface {
142 view: Arc::clone(&view.0),
143 context: view.context(),
144 format,
145 }
146 }
147}
148
149impl SurfaceInterface for WasdkSurface {
150 fn get_capabilities(&self, _adapter: &DispatchAdapter) -> SurfaceCapabilities {
151 capabilities(self.format)
152 }
153
154 fn configure(&self, device: &DispatchDevice, config: &wgpu::SurfaceConfiguration) {
158 check_present_mode(config.present_mode);
159 self.view.set_canvas_size(config.width, config.height);
160 let (color_space, tone_mapping) = convert::color_space_to_wasi(config.color_space);
161 let view_formats = config
162 .view_formats
163 .iter()
164 .map(|format| convert::texture_format_to_wasi(*format))
165 .collect();
166 self.context.configure(&GpuCanvasConfiguration {
167 device: WasdkDevice::of_dispatch(device).gpu_device(),
168 format: convert::texture_format_to_wasi(config.format),
169 usage: Some(convert::texture_usages_to_wasi(config.usage)),
170 view_formats: Some(view_formats),
171 color_space,
172 tone_mapping,
173 alpha_mode: Some(convert::alpha_mode_to_wasi(config.alpha_mode)),
174 });
175 }
176
177 fn get_current_texture(
181 &self,
182 ) -> (
183 Option<DispatchTexture>,
184 SurfaceStatus,
185 DispatchSurfaceOutputDetail,
186 ) {
187 let texture = self.context.get_current_texture();
188 let frame = WasdkSurfaceOutputDetail {
189 view: Arc::clone(&self.view),
190 };
191 (
192 Some(DispatchTexture::custom(WasdkTexture::new(texture))),
193 SurfaceStatus::Good,
194 DispatchSurfaceOutputDetail::custom(frame),
195 )
196 }
197}
198
199#[derive(Debug)]
201pub struct WasdkSurfaceOutputDetail {
202 view: Arc<View>,
203}
204
205downcast!(WasdkSurfaceOutputDetail: dispatch DispatchSurfaceOutputDetail);
206
207impl WasdkSurfaceOutputDetail {
208 pub(crate) fn present(&self) {
210 self.view.present();
211 }
212}
213
214impl SurfaceOutputDetailInterface for WasdkSurfaceOutputDetail {
215 fn texture_discard(&self) {}
218
219 fn texture_release(&self) {}
220}
221
222#[cfg(test)]
223mod tests {
224 use wgpu::rwh::{AppKitWindowHandle, WebWindowHandle};
225
226 use super::*;
227
228 #[test]
229 fn a_view_serves_its_canvas_format_at_its_frames() {
230 let capabilities = capabilities(TextureFormat::Bgra8Unorm);
231 assert_eq!(capabilities.formats, [TextureFormat::Bgra8Unorm]);
232 assert_eq!(
233 capabilities.color_spaces(TextureFormat::Bgra8Unorm),
234 SurfaceColorSpaces::SRGB
235 );
236 assert_eq!(capabilities.present_modes, [PresentMode::Fifo]);
237 assert_eq!(capabilities.alpha_modes[0], CompositeAlphaMode::Opaque);
238 assert!(
239 capabilities
240 .usages
241 .contains(TextureUsages::RENDER_ATTACHMENT)
242 );
243 }
244
245 #[test]
246 fn an_automatic_present_mode_is_the_views_frames() {
247 check_present_mode(PresentMode::AutoNoVsync);
248 check_present_mode(PresentMode::Fifo);
249 }
250
251 #[test]
252 #[should_panic(expected = "Mailbox")]
253 fn a_present_mode_other_than_the_views_frames_is_a_gap() {
254 check_present_mode(PresentMode::Mailbox);
255 }
256
257 #[test]
260 fn a_shared_view_is_a_window_wgpu_makes_a_surface_for() {
261 fn surface_target<T: Into<wgpu::SurfaceTarget<'static>>>() {}
262 fn window<T: wgpu::WindowHandle + HasDisplayHandle>() {}
263 surface_target::<SharedView>();
264 window::<SharedView>();
265 }
266
267 #[test]
268 fn a_web_canvas_handle_points_at_the_shared_view() {
269 let pointer = NonNull::<SharedView>::dangling();
270 let handle = WebCanvasWindowHandle::new(pointer.cast());
271 assert_eq!(shared_view_of(handle.into()), pointer);
272 }
273
274 #[test]
275 #[should_panic(expected = "other than a view's")]
276 fn a_numbered_web_handle_is_not_a_view() {
277 shared_view_of(WebWindowHandle::new(1).into());
278 }
279
280 #[test]
281 #[should_panic(expected = "other than a view's")]
282 fn a_native_window_is_not_a_view() {
283 let window = NonNull::<u8>::dangling().cast();
284 shared_view_of(AppKitWindowHandle::new(window).into());
285 }
286}