luminance_web_sys/
lib.rs

1use luminance::context::GraphicsContext;
2use luminance::framebuffer::{Framebuffer, FramebufferError};
3use luminance::texture::Dim2;
4use luminance_webgl::webgl2::{StateQueryError, WebGL2};
5use std::fmt;
6use wasm_bindgen::{JsCast as _, JsValue};
7use web_sys::{Document, HtmlCanvasElement, Window};
8
9/// web-sys errors that might occur while initializing and using the platform.
10#[non_exhaustive]
11#[derive(Debug)]
12pub enum WebSysWebGL2SurfaceError {
13  CannotGrabWindow,
14  CannotGrabDocument,
15  NotSuchCanvasElement(String),
16  CannotGrabWebGL2Context,
17  NoAvailableWebGL2Context,
18  StateQueryError(StateQueryError),
19}
20
21impl WebSysWebGL2SurfaceError {
22  fn cannot_grab_window() -> Self {
23    WebSysWebGL2SurfaceError::CannotGrabWindow
24  }
25
26  fn cannot_grab_document() -> Self {
27    WebSysWebGL2SurfaceError::CannotGrabDocument
28  }
29
30  fn not_such_canvas_element(name: impl Into<String>) -> Self {
31    WebSysWebGL2SurfaceError::NotSuchCanvasElement(name.into())
32  }
33
34  fn cannot_grab_webgl2_context() -> Self {
35    WebSysWebGL2SurfaceError::CannotGrabWebGL2Context
36  }
37
38  fn no_available_webgl2_context() -> Self {
39    WebSysWebGL2SurfaceError::NoAvailableWebGL2Context
40  }
41}
42
43impl fmt::Display for WebSysWebGL2SurfaceError {
44  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
45    match *self {
46      WebSysWebGL2SurfaceError::CannotGrabWindow => f.write_str("cannot grab the window node"),
47      WebSysWebGL2SurfaceError::CannotGrabDocument => f.write_str("cannot grab the document node"),
48      WebSysWebGL2SurfaceError::NotSuchCanvasElement(ref name) => {
49        write!(f, "cannot grab canvas named {}", name)
50      }
51      WebSysWebGL2SurfaceError::CannotGrabWebGL2Context => {
52        f.write_str("cannot grab WebGL2 context")
53      }
54      WebSysWebGL2SurfaceError::NoAvailableWebGL2Context => {
55        f.write_str("no available WebGL2 context")
56      }
57      WebSysWebGL2SurfaceError::StateQueryError(ref e) => {
58        write!(f, "WebGL2 state query error: {}", e)
59      }
60    }
61  }
62}
63
64impl std::error::Error for WebSysWebGL2SurfaceError {}
65
66impl From<StateQueryError> for WebSysWebGL2SurfaceError {
67  fn from(e: StateQueryError) -> Self {
68    WebSysWebGL2SurfaceError::StateQueryError(e)
69  }
70}
71
72/// web-sys surface for WebGL2.
73pub struct WebSysWebGL2Surface {
74  pub window: Window,
75  pub document: Document,
76  pub canvas: HtmlCanvasElement,
77  backend: WebGL2,
78}
79
80impl WebSysWebGL2Surface {
81  /// Create a new [`WebSysWebGL2Surface`] based on the name of the DOM canvas element named by
82  /// `canvas_name`.
83  pub fn new(canvas_name: impl AsRef<str>) -> Result<Self, WebSysWebGL2SurfaceError> {
84    let (window, document, canvas) = Self::get_canvas(canvas_name)?;
85    Self::from_canvas(window, document, canvas)
86  }
87
88  /// Create a new [`WebSysWebGL2Surface`] based on the name of the DOM canvas element named by `canvas_name` and pass
89  /// along a list of parameters when creating the WebGL context.
90  pub fn new_with_params(
91    canvas_name: impl AsRef<str>,
92    params: impl AsRef<JsValue>,
93  ) -> Result<Self, WebSysWebGL2SurfaceError> {
94    let (window, document, canvas) = Self::get_canvas(canvas_name)?;
95    Self::from_canvas_with_params(window, document, canvas, params)
96  }
97
98  /// Create a new [`WebSysWebGL2Surface`] based on a given [`HtmlCanvasElement`].
99  pub fn from_canvas(
100    window: Window,
101    document: Document,
102    canvas: HtmlCanvasElement,
103  ) -> Result<Self, WebSysWebGL2SurfaceError> {
104    let webgl2 = canvas
105      .get_context("webgl2")
106      .map_err(|_| WebSysWebGL2SurfaceError::cannot_grab_webgl2_context())?
107      .ok_or_else(|| WebSysWebGL2SurfaceError::no_available_webgl2_context())?;
108    let ctx = webgl2
109      .dyn_into()
110      .map_err(|_| WebSysWebGL2SurfaceError::no_available_webgl2_context())?;
111
112    // create the backend object and return the whole object
113    let backend = WebGL2::new(ctx)?;
114
115    Ok(Self {
116      window,
117      document,
118      canvas,
119      backend,
120    })
121  }
122
123  /// Obtain a canvas from its name, as well as the document it is attached in.
124  fn get_canvas(
125    canvas_name: impl AsRef<str>,
126  ) -> Result<(Window, Document, HtmlCanvasElement), WebSysWebGL2SurfaceError> {
127    let window = web_sys::window().ok_or_else(|| WebSysWebGL2SurfaceError::cannot_grab_window())?;
128
129    let document = window
130      .document()
131      .ok_or_else(|| WebSysWebGL2SurfaceError::cannot_grab_document())?;
132
133    let canvas_name = canvas_name.as_ref();
134    let canvas = document
135      .get_element_by_id(canvas_name)
136      .ok_or_else(|| WebSysWebGL2SurfaceError::not_such_canvas_element(canvas_name))?;
137
138    let canvas = canvas
139      .dyn_into::<HtmlCanvasElement>()
140      .map_err(|_| WebSysWebGL2SurfaceError::not_such_canvas_element(canvas_name))?;
141
142    Ok((window, document, canvas))
143  }
144
145  /// Create a new [`WebSysWebGL2Surface`] based on a given [`HtmlCanvasElement`] and pass along a list of parameters
146  /// when creating the WebGL context.
147  pub fn from_canvas_with_params(
148    window: Window,
149    document: Document,
150    canvas: HtmlCanvasElement,
151    params: impl AsRef<JsValue>,
152  ) -> Result<Self, WebSysWebGL2SurfaceError> {
153    let webgl2 = canvas
154      .get_context_with_context_options("webgl2", params.as_ref())
155      .map_err(|_| WebSysWebGL2SurfaceError::cannot_grab_webgl2_context())?
156      .ok_or_else(|| WebSysWebGL2SurfaceError::no_available_webgl2_context())?;
157    let ctx = webgl2
158      .dyn_into()
159      .map_err(|_| WebSysWebGL2SurfaceError::no_available_webgl2_context())?;
160
161    // create the backend object and return the whole object
162    let backend = WebGL2::new(ctx)?;
163
164    Ok(Self {
165      window,
166      document,
167      canvas,
168      backend,
169    })
170  }
171
172  /// Get the back buffer.
173  pub fn back_buffer(&mut self) -> Result<Framebuffer<WebGL2, Dim2, (), ()>, FramebufferError> {
174    let dim = [self.canvas.width(), self.canvas.height()];
175    Framebuffer::back_buffer(self, dim)
176  }
177}
178
179unsafe impl GraphicsContext for WebSysWebGL2Surface {
180  type Backend = WebGL2;
181
182  fn backend(&mut self) -> &mut Self::Backend {
183    &mut self.backend
184  }
185}