1use glow::HasContext;
2use parking_lot::{Mutex, RwLock};
3use wasm_bindgen::{JsCast, JsValue};
4
5use super::TextureFormatDesc;
6
7pub 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 #[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 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 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 return Err(crate::InstanceError::new(format!(
76 "canvas.getContext() threw exception {js_error:?}",
77 )));
78 }
79 };
80
81 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(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 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 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 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 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 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>, _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}