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#[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 #[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 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 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 return Err(crate::InstanceError::new(format!(
81 "canvas.getContext() threw exception {js_error:?}",
82 )));
83 }
84 };
85
86 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 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 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(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 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 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 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 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 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>, _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}