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