Skip to main content

wgpu_hal/gles/
web.rs

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