wgpu_hal/gles/
web.rs

1use glow::HasContext;
2use parking_lot::{Mutex, RwLock};
3use wasm_bindgen::{JsCast, JsValue};
4
5use super::TextureFormatDesc;
6
7/// A wrapper around a [`glow::Context`] to provide a fake `lock()` api that makes it compatible
8/// with the `AdapterContext` API from the EGL implementation.
9pub struct AdapterContext {
10    pub glow_context: glow::Context,
11    pub webgl2_context: web_sys::WebGl2RenderingContext,
12}
13
14impl AdapterContext {
15    pub fn is_owned(&self) -> bool {
16        false
17    }
18
19    /// Obtain a lock to the EGL context and get handle to the [`glow::Context`] that can be used to
20    /// do rendering.
21    #[track_caller]
22    pub fn lock(&self) -> &glow::Context {
23        &self.glow_context
24    }
25}
26
27#[derive(Debug)]
28pub struct Instance;
29
30impl Instance {
31    pub fn create_surface_from_canvas(
32        &self,
33        canvas: web_sys::HtmlCanvasElement,
34    ) -> Result<Surface, crate::InstanceError> {
35        let result =
36            canvas.get_context_with_context_options("webgl2", &Self::create_context_options());
37        self.create_surface_from_context(Canvas::Canvas(canvas), result)
38    }
39
40    pub fn create_surface_from_offscreen_canvas(
41        &self,
42        canvas: web_sys::OffscreenCanvas,
43    ) -> Result<Surface, crate::InstanceError> {
44        let result =
45            canvas.get_context_with_context_options("webgl2", &Self::create_context_options());
46        self.create_surface_from_context(Canvas::Offscreen(canvas), result)
47    }
48
49    /// Common portion of public `create_surface_from_*` functions.
50    ///
51    /// Note: Analogous code also exists in the WebGPU backend at
52    /// `wgpu::backend::web::Context`.
53    fn create_surface_from_context(
54        &self,
55        canvas: Canvas,
56        context_result: Result<Option<js_sys::Object>, JsValue>,
57    ) -> Result<Surface, crate::InstanceError> {
58        let context_object: js_sys::Object = match context_result {
59            Ok(Some(context)) => context,
60            Ok(None) => {
61                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev>
62                // A getContext() call “returns null if contextId is not supported, or if the
63                // canvas has already been initialized with another context type”. Additionally,
64                // “not supported” could include “insufficient GPU resources” or “the GPU process
65                // previously crashed”. So, we must return it as an `Err` since it could occur
66                // for circumstances outside the application author's control.
67                return Err(crate::InstanceError::new(String::from(concat!(
68                    "canvas.getContext() returned null; ",
69                    "webgl2 not available or canvas already in use"
70                ))));
71            }
72            Err(js_error) => {
73                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
74                // A thrown exception indicates misuse of the canvas state.
75                return Err(crate::InstanceError::new(format!(
76                    "canvas.getContext() threw exception {js_error:?}",
77                )));
78            }
79        };
80
81        // Not returning this error because it is a type error that shouldn't happen unless
82        // the browser, JS builtin objects, or wasm bindings are misbehaving somehow.
83        let webgl2_context: web_sys::WebGl2RenderingContext = context_object
84            .dyn_into()
85            .expect("canvas context is not a WebGl2RenderingContext");
86
87        Ok(Surface {
88            canvas,
89            webgl2_context,
90            srgb_present_program: Mutex::new(None),
91            swapchain: RwLock::new(None),
92            texture: Mutex::new(None),
93            presentable: true,
94        })
95    }
96
97    fn create_context_options() -> js_sys::Object {
98        let context_options = js_sys::Object::new();
99        js_sys::Reflect::set(&context_options, &"antialias".into(), &JsValue::FALSE)
100            .expect("Cannot create context options");
101        context_options
102    }
103}
104
105#[cfg(send_sync)]
106unsafe impl Sync for Instance {}
107#[cfg(send_sync)]
108unsafe impl Send for Instance {}
109
110impl crate::Instance for Instance {
111    type A = super::Api;
112
113    unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
114        profiling::scope!("Init OpenGL (WebGL) Backend");
115        Ok(Instance)
116    }
117
118    unsafe fn enumerate_adapters(
119        &self,
120        surface_hint: Option<&Surface>,
121    ) -> Vec<crate::ExposedAdapter<super::Api>> {
122        if let Some(surface_hint) = surface_hint {
123            let gl = glow::Context::from_webgl2_context(surface_hint.webgl2_context.clone());
124
125            unsafe {
126                super::Adapter::expose(AdapterContext {
127                    glow_context: gl,
128                    webgl2_context: surface_hint.webgl2_context.clone(),
129                })
130            }
131            .into_iter()
132            .collect()
133        } else {
134            Vec::new()
135        }
136    }
137
138    unsafe fn create_surface(
139        &self,
140        _display_handle: raw_window_handle::RawDisplayHandle,
141        window_handle: raw_window_handle::RawWindowHandle,
142    ) -> Result<Surface, crate::InstanceError> {
143        let canvas: web_sys::HtmlCanvasElement = match window_handle {
144            raw_window_handle::RawWindowHandle::Web(handle) => web_sys::window()
145                .and_then(|win| win.document())
146                .expect("Cannot get document")
147                .query_selector(&format!("canvas[data-raw-handle=\"{}\"]", handle.id))
148                .expect("Cannot query for canvas")
149                .expect("Canvas is not found")
150                .dyn_into()
151                .expect("Failed to downcast to canvas type"),
152            raw_window_handle::RawWindowHandle::WebCanvas(handle) => {
153                let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
154                value.clone().unchecked_into()
155            }
156            raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => {
157                let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
158                let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into();
159
160                return self.create_surface_from_offscreen_canvas(canvas);
161            }
162            _ => {
163                return Err(crate::InstanceError::new(format!(
164                    "window handle {window_handle:?} is not a web handle"
165                )))
166            }
167        };
168
169        self.create_surface_from_canvas(canvas)
170    }
171}
172
173#[derive(Debug)]
174pub struct Surface {
175    canvas: Canvas,
176    pub(super) webgl2_context: web_sys::WebGl2RenderingContext,
177    pub(super) swapchain: RwLock<Option<Swapchain>>,
178    texture: Mutex<Option<glow::Texture>>,
179    pub(super) presentable: bool,
180    srgb_present_program: Mutex<Option<glow::Program>>,
181}
182
183impl Clone for Surface {
184    fn clone(&self) -> Self {
185        Self {
186            canvas: self.canvas.clone(),
187            webgl2_context: self.webgl2_context.clone(),
188            swapchain: RwLock::new(self.swapchain.read().clone()),
189            texture: Mutex::new(*self.texture.lock()),
190            presentable: self.presentable,
191            srgb_present_program: Mutex::new(*self.srgb_present_program.lock()),
192        }
193    }
194}
195
196#[cfg(send_sync)]
197unsafe impl Sync for Surface {}
198#[cfg(send_sync)]
199unsafe impl Send for Surface {}
200
201#[derive(Clone, Debug)]
202enum Canvas {
203    Canvas(web_sys::HtmlCanvasElement),
204    Offscreen(web_sys::OffscreenCanvas),
205}
206
207#[derive(Clone, Debug)]
208pub struct Swapchain {
209    pub(crate) extent: wgt::Extent3d,
210    // pub(crate) channel: f::ChannelType,
211    pub(super) format: wgt::TextureFormat,
212    pub(super) framebuffer: glow::Framebuffer,
213    pub(super) format_desc: TextureFormatDesc,
214}
215
216impl Surface {
217    pub(super) unsafe fn present(
218        &self,
219        _suf_texture: super::Texture,
220        context: &AdapterContext,
221    ) -> Result<(), crate::SurfaceError> {
222        let gl = &context.glow_context;
223        let swapchain = self.swapchain.read();
224        let swapchain = swapchain.as_ref().ok_or(crate::SurfaceError::Other(
225            "need to configure surface before presenting",
226        ))?;
227
228        if swapchain.format.is_srgb() {
229            // Important to set the viewport since we don't know in what state the user left it.
230            unsafe {
231                gl.viewport(
232                    0,
233                    0,
234                    swapchain.extent.width as _,
235                    swapchain.extent.height as _,
236                )
237            };
238            unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
239            unsafe { gl.bind_sampler(0, None) };
240            unsafe { gl.active_texture(glow::TEXTURE0) };
241            unsafe { gl.bind_texture(glow::TEXTURE_2D, *self.texture.lock()) };
242            unsafe { gl.use_program(*self.srgb_present_program.lock()) };
243            unsafe { gl.disable(glow::DEPTH_TEST) };
244            unsafe { gl.disable(glow::STENCIL_TEST) };
245            unsafe { gl.disable(glow::SCISSOR_TEST) };
246            unsafe { gl.disable(glow::BLEND) };
247            unsafe { gl.disable(glow::CULL_FACE) };
248            unsafe { gl.draw_buffers(&[glow::BACK]) };
249            unsafe { gl.draw_arrays(glow::TRIANGLES, 0, 3) };
250        } else {
251            unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(swapchain.framebuffer)) };
252            unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
253            // Note the Y-flipping here. GL's presentation is not flipped,
254            // but main rendering is. Therefore, we Y-flip the output positions
255            // in the shader, and also this blit.
256            unsafe {
257                gl.blit_framebuffer(
258                    0,
259                    swapchain.extent.height as i32,
260                    swapchain.extent.width as i32,
261                    0,
262                    0,
263                    0,
264                    swapchain.extent.width as i32,
265                    swapchain.extent.height as i32,
266                    glow::COLOR_BUFFER_BIT,
267                    glow::NEAREST,
268                )
269            };
270        }
271
272        Ok(())
273    }
274
275    unsafe fn create_srgb_present_program(gl: &glow::Context) -> glow::Program {
276        let program = unsafe { gl.create_program() }.expect("Could not create shader program");
277        let vertex =
278            unsafe { gl.create_shader(glow::VERTEX_SHADER) }.expect("Could not create shader");
279        unsafe { gl.shader_source(vertex, include_str!("./shaders/srgb_present.vert")) };
280        unsafe { gl.compile_shader(vertex) };
281        let fragment =
282            unsafe { gl.create_shader(glow::FRAGMENT_SHADER) }.expect("Could not create shader");
283        unsafe { gl.shader_source(fragment, include_str!("./shaders/srgb_present.frag")) };
284        unsafe { gl.compile_shader(fragment) };
285        unsafe { gl.attach_shader(program, vertex) };
286        unsafe { gl.attach_shader(program, fragment) };
287        unsafe { gl.link_program(program) };
288        unsafe { gl.delete_shader(vertex) };
289        unsafe { gl.delete_shader(fragment) };
290        unsafe { gl.bind_texture(glow::TEXTURE_2D, None) };
291
292        program
293    }
294
295    pub fn supports_srgb(&self) -> bool {
296        // present.frag takes care of handling srgb conversion
297        true
298    }
299}
300
301impl crate::Surface for Surface {
302    type A = super::Api;
303
304    unsafe fn configure(
305        &self,
306        device: &super::Device,
307        config: &crate::SurfaceConfiguration,
308    ) -> Result<(), crate::SurfaceError> {
309        match self.canvas {
310            Canvas::Canvas(ref canvas) => {
311                canvas.set_width(config.extent.width);
312                canvas.set_height(config.extent.height);
313            }
314            Canvas::Offscreen(ref canvas) => {
315                canvas.set_width(config.extent.width);
316                canvas.set_height(config.extent.height);
317            }
318        }
319
320        let gl = &device.shared.context.lock();
321
322        {
323            let mut swapchain = self.swapchain.write();
324            if let Some(swapchain) = swapchain.take() {
325                // delete all frame buffers already allocated
326                unsafe { gl.delete_framebuffer(swapchain.framebuffer) };
327            }
328        }
329        {
330            let mut srgb_present_program = self.srgb_present_program.lock();
331            if srgb_present_program.is_none() && config.format.is_srgb() {
332                *srgb_present_program = Some(unsafe { Self::create_srgb_present_program(gl) });
333            }
334        }
335        {
336            let mut texture = self.texture.lock();
337            if let Some(texture) = texture.take() {
338                unsafe { gl.delete_texture(texture) };
339            }
340
341            *texture = Some(unsafe { gl.create_texture() }.map_err(|error| {
342                log::error!("Internal swapchain texture creation failed: {error}");
343                crate::DeviceError::OutOfMemory
344            })?);
345
346            let desc = device.shared.describe_texture_format(config.format);
347            unsafe { gl.bind_texture(glow::TEXTURE_2D, *texture) };
348            unsafe {
349                gl.tex_parameter_i32(
350                    glow::TEXTURE_2D,
351                    glow::TEXTURE_MIN_FILTER,
352                    glow::NEAREST as _,
353                )
354            };
355            unsafe {
356                gl.tex_parameter_i32(
357                    glow::TEXTURE_2D,
358                    glow::TEXTURE_MAG_FILTER,
359                    glow::NEAREST as _,
360                )
361            };
362            unsafe {
363                gl.tex_storage_2d(
364                    glow::TEXTURE_2D,
365                    1,
366                    desc.internal,
367                    config.extent.width as i32,
368                    config.extent.height as i32,
369                )
370            };
371
372            let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
373                log::error!("Internal swapchain framebuffer creation failed: {error}");
374                crate::DeviceError::OutOfMemory
375            })?;
376            unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
377            unsafe {
378                gl.framebuffer_texture_2d(
379                    glow::READ_FRAMEBUFFER,
380                    glow::COLOR_ATTACHMENT0,
381                    glow::TEXTURE_2D,
382                    *texture,
383                    0,
384                )
385            };
386            unsafe { gl.bind_texture(glow::TEXTURE_2D, None) };
387
388            let mut swapchain = self.swapchain.write();
389            *swapchain = Some(Swapchain {
390                extent: config.extent,
391                // channel: config.format.base_format().1,
392                format: config.format,
393                format_desc: desc,
394                framebuffer,
395            });
396        }
397
398        Ok(())
399    }
400
401    unsafe fn unconfigure(&self, device: &super::Device) {
402        let gl = device.shared.context.lock();
403        {
404            let mut swapchain = self.swapchain.write();
405            if let Some(swapchain) = swapchain.take() {
406                unsafe { gl.delete_framebuffer(swapchain.framebuffer) };
407            }
408        }
409        if let Some(renderbuffer) = self.texture.lock().take() {
410            unsafe { gl.delete_texture(renderbuffer) };
411        }
412    }
413
414    unsafe fn acquire_texture(
415        &self,
416        _timeout_ms: Option<std::time::Duration>, //TODO
417        _fence: &super::Fence,
418    ) -> Result<Option<crate::AcquiredSurfaceTexture<super::Api>>, crate::SurfaceError> {
419        let swapchain = self.swapchain.read();
420        let sc = swapchain.as_ref().unwrap();
421        let texture = super::Texture {
422            inner: super::TextureInner::Texture {
423                raw: self.texture.lock().unwrap(),
424                target: glow::TEXTURE_2D,
425            },
426            drop_guard: None,
427            array_layer_count: 1,
428            mip_level_count: 1,
429            format: sc.format,
430            format_desc: sc.format_desc.clone(),
431            copy_size: crate::CopyExtent {
432                width: sc.extent.width,
433                height: sc.extent.height,
434                depth: 1,
435            },
436        };
437        Ok(Some(crate::AcquiredSurfaceTexture {
438            texture,
439            suboptimal: false,
440        }))
441    }
442
443    unsafe fn discard_texture(&self, _texture: super::Texture) {}
444}