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#[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
72pub struct WebSysWebGL2Surface {
74 pub window: Window,
75 pub document: Document,
76 pub canvas: HtmlCanvasElement,
77 backend: WebGL2,
78}
79
80impl WebSysWebGL2Surface {
81 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 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 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 let backend = WebGL2::new(ctx)?;
114
115 Ok(Self {
116 window,
117 document,
118 canvas,
119 backend,
120 })
121 }
122
123 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 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 let backend = WebGL2::new(ctx)?;
163
164 Ok(Self {
165 window,
166 document,
167 canvas,
168 backend,
169 })
170 }
171
172 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}