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