Skip to main content

gpu/
web_swap.rs

1// web_swap -- a canvas as a swapchain in the browser (through web/gpu.js)
2//
3//   Surface::native(&instance, nw, window)   the OffscreenCanvas plat::win_gpu_child brought from the
4//        page (lib/plat/web_gpu.rs); Surface::new asks for it, NotReady until it is here
5//   device.create_swapchain(&surface, &SwapchainDesc)   getContext("webgpu"), configure: the format the
6//        browser prefers (bgra8unorm or rgba8unorm), opaque, sRGB; copy_src adds COPY_SRC; srgb a view in
7//        the -srgb format.  The present mode and the image count are not ours: the browser composes the
8//        newest image at its next frame, present_mode() says Mailbox
9//   sc.try_acquire()   getCurrentTexture.  NotReady never: the image is there at once, and a second
10//        frame in one turn of the worker's event loop draws over the first, which nobody saw
11//   sc.present(frame, [cmd, ..])   queue.submit.  There is no present: the browser takes the image when
12//        the worker returns to its event loop (OffscreenCanvas of a worker: no main thread involved)
13//
14// A swapchain cannot be read back into this turn: Buffer::read later (web_res.rs).
15
16use alloc::rc::Rc;
17use core::cell::Cell;
18
19use crate::web_dev::{call, Device, Handle, CALL_CONTEXT, CALL_CURRENT, CALL_RESERVE, CALL_RESIZE, CALL_RGBA};
20use crate::*;
21
22pub(crate) struct SurfInner {
23    canvas: Handle,
24    window: i32,
25}
26
27/// a canvas as a target for swapchains
28#[derive(Clone)]
29pub struct Surface(pub(crate) Rc<SurfInner>);
30
31impl Surface {
32    /// (Linux: the window's size for a rebuild on a worker; the browser knows it)
33    pub fn track_size(&self) {}
34
35    /// the canvas of plat window `window` (NotReady: it is on its way from the page)
36    pub fn new(instance: &Instance, window: i32) -> Result<Surface> {
37        let nw = plat::win_gpu_child(window).ok_or(GpuError::NotReady)?;
38        Surface::native(instance, nw, window)
39    }
40
41    /// the canvas of plat::win_gpu_child, whose size is that of plat window `window`
42    pub fn native(_instance: &Instance, nw: plat::NativeWindow, window: i32) -> Result<Surface> {
43        Ok(Surface(Rc::new(SurfInner { canvas: Handle { h: nw.take(), destroy: false }, window })))
44    }
45}
46
47/// a frame being drawn
48pub struct Frame {
49    size: (u32, u32),
50}
51
52/// what Swapchain::try_acquire found
53#[must_use]
54pub enum TryAcquire {
55    Frame(Frame),
56    /// (never in the browser)
57    NotReady,
58    /// the swapchain does not fit the window any more: Swapchain::rebuild first
59    Stale,
60    /// the window has no area
61    NoArea,
62}
63
64impl Frame {
65    /// (the browser has one current image)
66    pub fn index(&self) -> u32 {
67        0
68    }
69    pub fn size(&self) -> (u32, u32) {
70        self.size
71    }
72}
73
74/// images shown on a canvas
75pub struct Swapchain {
76    dev: Device,
77    surf: Surface,
78    ctx: Handle,
79    size: Cell<(u32, u32)>,
80    format: Format,
81    texture: Texture,
82    view: TextureView,
83    stale: Cell<bool>,
84}
85
86impl Device {
87    pub fn create_swapchain(&self, surface: &Surface, desc: &SwapchainDesc) -> Result<Swapchain> {
88        self.d.check()?;
89        let (w, h) = plat::win_size(surface.0.window);
90        let (w, h) = (w.max(0) as u32, h.max(0) as u32);
91        if w == 0 || h == 0 {
92            return Err(GpuError::NotReady);
93        }
94        let ctx = call(CALL_CONTEXT, &[self.d.h, surface.0.canvas.h, w, h, desc.copy_src as u32, desc.srgb as u32]);
95        if ctx == 0 {
96            return Err(GpuError::SurfaceLost);
97        }
98        let rgba = call(CALL_RGBA, &[]) != 0;
99        let format = match (rgba, desc.srgb) {
100            (false, false) => Format::Bgra8Unorm,
101            (false, true) => Format::Bgra8Srgb,
102            (true, false) => Format::Rgba8Unorm,
103            (true, true) => Format::Rgba8Srgb,
104        };
105        let usage = TextureUsage::COLOR_TARGET | if desc.copy_src { TextureUsage::COPY_SRC } else { TextureUsage::NONE };
106        let view = TextureView::for_frame(call(CALL_RESERVE, &[]), format, w, h);
107        let texture = Texture::for_frame(call(CALL_RESERVE, &[]), view.clone(), format, w, h, usage);
108        Ok(Swapchain { dev: self.clone(), surf: surface.clone(), ctx: Handle { h: ctx, destroy: false }, size: Cell::new((w, h)), format, texture, view, stale: Cell::new(false) })
109    }
110}
111
112impl Swapchain {
113    /// the canvas takes the window's size now
114    pub fn rebuild(&mut self) -> Result<()> {
115        let (w, h) = plat::win_size(self.surf.0.window);
116        let (w, h) = (w.max(0) as u32, h.max(0) as u32);
117        if w != 0 && h != 0 {
118            call(CALL_RESIZE, &[self.ctx.h, w, h]);
119            let usage = self.texture.usage();
120            let view = TextureView::for_frame(call(CALL_RESERVE, &[]), self.format, w, h);
121            self.texture = Texture::for_frame(call(CALL_RESERVE, &[]), view.clone(), self.format, w, h, usage);
122            self.view = view;
123        }
124        self.size.set((w, h));
125        self.stale.set(false);
126        Ok(())
127    }
128
129    /// the image to draw into (the same one until the worker returns to its event loop)
130    pub fn try_acquire(&mut self) -> Result<TryAcquire> {
131        self.dev.d.check()?;
132        let (w, h) = plat::win_size(self.surf.0.window);
133        if w <= 0 || h <= 0 {
134            return Ok(TryAcquire::NoArea);
135        }
136        if self.stale.get() || self.size.get() != (w as u32, h as u32) {
137            return Ok(TryAcquire::Stale);
138        }
139        match call(CALL_CURRENT, &[self.ctx.h, self.texture.handle(), self.view.handle()]) {
140            0 => Err(GpuError::SurfaceLost),
141            _ => Ok(TryAcquire::Frame(Frame { size: self.size.get() })),
142        }
143    }
144
145    /// the image to draw into; None while the window has no area
146    pub fn acquire(&mut self) -> Result<Option<Frame>> {
147        match self.try_acquire()? {
148            TryAcquire::Frame(f) => Ok(Some(f)),
149            TryAcquire::Stale => {
150                self.rebuild()?;
151                match self.try_acquire()? {
152                    TryAcquire::Frame(f) => Ok(Some(f)),
153                    _ => Ok(None),
154                }
155            }
156            _ => Ok(None),
157        }
158    }
159
160    /// the frame's image as a render target
161    pub fn view(&self, _frame: &Frame) -> &TextureView {
162        &self.view
163    }
164
165    /// the frame's image (copies: SwapchainDesc::copy_src)
166    pub fn texture(&self, _frame: &Frame) -> &Texture {
167        &self.texture
168    }
169
170    /// submit the frame's commands; the browser shows the image when the worker's turn ends
171    pub fn present<'a, I: IntoIterator<Item = CommandBuffer<'a>>>(&self, _frame: Frame, cmds: I) -> Result<SubmitId> {
172        self.dev.queue().submit(cmds)
173    }
174
175    /// (Linux: the display has answered for the images; here nothing to wait for)
176    pub fn prepare(&mut self) -> Result<bool> {
177        Ok(true)
178    }
179    /// (Linux: a frame reaches the window after the GPU finished it; here the browser does that)
180    pub fn flush(&self) -> Result<bool> {
181        Ok(true)
182    }
183    /// (Linux: a frame is on its way to the window; here never)
184    pub fn busy(&self) -> bool {
185        false
186    }
187    /// when a frame that found NotReady should try again: retry (the caller's time)
188    pub fn next_look(&self, retry: u64) -> u64 {
189        retry
190    }
191    /// (Linux: what the next image changes, for the compositor; here the whole image counts)
192    pub fn damage(&self, _rects: Option<&[[i32; 4]]>) {}
193    /// the next acquire takes the window's size
194    pub fn resize(&mut self) {
195        self.stale.set(true);
196    }
197    /// (the browser's: no present modes)
198    pub fn set_present_mode(&mut self, _m: PresentMode) {}
199    pub fn present_mode(&self) -> PresentMode {
200        PresentMode::Mailbox
201    }
202    pub fn format(&self) -> Format {
203        self.format
204    }
205    /// the images' size
206    pub fn size(&self) -> (u32, u32) {
207        self.size.get()
208    }
209    pub fn image_count(&self) -> usize {
210        1
211    }
212}