1use std::cell::Cell;
2use std::rc::Rc;
3
4use js_sys::{Error, JsString, Uint8Array};
5use num_enum::{IntoPrimitive, TryFromPrimitive};
6use web_sys::{
7 HtmlImageElement, OesTextureHalfFloat, WebGlRenderingContext as Context, WebGlTexture,
8};
9
10use super::gl::Gl;
11use super::gl::GlError;
12use super::settings::Settings;
13
14#[repr(i32)]
15#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
16pub enum TextureFilter {
17 Nearest = Context::NEAREST as i32,
18 Linear = Context::LINEAR as i32,
19}
20
21impl Default for TextureFilter {
22 fn default() -> Self {
23 TextureFilter::Linear
24 }
25}
26
27#[repr(u32)]
28#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
29pub enum TextureType {
30 Byte = Context::UNSIGNED_BYTE,
31 Float = Context::FLOAT,
32 HalfFloat = OesTextureHalfFloat::HALF_FLOAT_OES,
33}
34
35#[repr(u32)]
36#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
37pub enum TextureFormat {
38 Alpha = Context::ALPHA,
39 Luminance = Context::LUMINANCE,
40 LuminanceAlpha = Context::LUMINANCE_ALPHA,
41 Rgb = Context::RGB,
42 Rgba = Context::RGBA,
43}
44
45impl TextureFormat {
46 pub fn channels(self) -> u32 {
47 match self {
48 TextureFormat::Alpha => 1,
49 TextureFormat::Luminance => 1,
50 TextureFormat::LuminanceAlpha => 2,
51 TextureFormat::Rgb => 3,
52 TextureFormat::Rgba => 4,
53 }
54 }
55}
56
57#[derive(Debug)]
58pub enum TextureContent {
59 None,
60 Image(HtmlImageElement),
61 Bytes(Vec<u8>),
62}
63
64pub const TEXTURES_COUNT: u32 = 16;
65
66#[derive(Debug)]
67struct TextureInfo {
68 gl: Gl,
69 handle: WebGlTexture,
70 width: u32,
71 height: u32,
72 data_type: TextureType,
73 format: TextureFormat,
74 filter: Cell<TextureFilter>,
75}
76
77impl PartialEq<TextureInfo> for TextureInfo {
78 fn eq(&self, other: &TextureInfo) -> bool {
79 self.handle == other.handle
80 }
81}
82
83impl Eq for TextureInfo {}
84
85impl Drop for TextureInfo {
86 fn drop(&mut self) {
87 self.gl.context().delete_texture(Some(&self.handle))
88 }
89}
90
91#[derive(Clone, Debug, PartialEq, Eq)]
92pub struct Texture {
93 data: Rc<TextureInfo>,
94}
95
96impl Texture {
97 pub fn new(
98 gl: Gl,
99 width: u32,
100 height: u32,
101 data_type: TextureType,
102 format: TextureFormat,
103 data: TextureContent,
104 ) -> Result<Texture, GlError> {
105 let handle = gl
106 .context()
107 .create_texture()
108 .ok_or_else(|| GlError::UnknownError(Some("Texture creation failed".into())))?;
109
110 let result = Texture {
111 data: Rc::new(TextureInfo {
112 gl: gl.clone(),
113 handle: handle.clone(),
114 filter: Default::default(),
115 width,
116 height,
117 data_type,
118 format,
119 }),
120 };
121
122 gl.apply(
123 Gl::settings().active_texture(0).texture(0, result.clone()),
124 || {
125 gl.context().tex_parameteri(
126 Context::TEXTURE_2D,
127 Context::TEXTURE_WRAP_S,
128 Context::CLAMP_TO_EDGE as i32,
129 );
130 gl.context().tex_parameteri(
131 Context::TEXTURE_2D,
132 Context::TEXTURE_WRAP_T,
133 Context::CLAMP_TO_EDGE as i32,
134 );
135 gl.context().tex_parameteri(
136 Context::TEXTURE_2D,
137 Context::TEXTURE_MAG_FILTER,
138 TextureFilter::default().into(),
139 );
140 gl.context().tex_parameteri(
141 Context::TEXTURE_2D,
142 Context::TEXTURE_MIN_FILTER,
143 TextureFilter::default().into(),
144 );
145 },
146 );
147
148 match data {
149 TextureContent::None => result.init_buffer()?,
150 TextureContent::Image(image) => result.write_image(&image)?,
151 TextureContent::Bytes(bytes) => result.write_bytes(&bytes)?,
152 }
153
154 Ok(result)
155 }
156
157 pub fn gl(&self) -> Gl {
158 self.data.gl.clone()
159 }
160
161 pub fn width(&self) -> u32 {
162 self.data.width
163 }
164 pub fn height(&self) -> u32 {
165 self.data.height
166 }
167 pub fn data_type(&self) -> TextureType {
168 self.data.data_type
169 }
170 pub fn format(&self) -> TextureFormat {
171 self.data.format
172 }
173
174 pub(crate) fn handle(&self) -> &WebGlTexture {
175 &self.data.handle
176 }
177
178 pub fn size(&self) -> (u32, u32) {
179 (self.width(), self.height())
180 }
181
182 pub fn filter(&self) -> TextureFilter {
183 self.data.filter.get()
184 }
185
186 pub fn set_filter(&self, filter: TextureFilter) {
187 if self.filter() != filter {
188 let ref gl = self.data.gl;
189 let context = gl.context();
190 gl.apply(
191 Gl::settings().texture(0, self.clone()).active_texture(0),
192 || {
193 context.tex_parameteri(
194 Context::TEXTURE_2D,
195 Context::TEXTURE_MAG_FILTER,
196 filter.into(),
197 );
198 context.tex_parameteri(
199 Context::TEXTURE_2D,
200 Context::TEXTURE_MIN_FILTER,
201 filter.into(),
202 );
203 self.data.filter.set(filter);
204 },
205 );
206 }
207 }
208
209 pub fn write_image(&self, image: &HtmlImageElement) -> Result<(), GlError> {
210 let gl = self.gl();
211 let format: u32 = self.format().into();
212
213 gl.apply(
214 Gl::settings().active_texture(0).texture(0, self.clone()),
215 || {
216 gl.context()
217 .tex_image_2d_with_u32_and_u32_and_image(
218 Context::TEXTURE_2D,
219 0,
220 format as i32,
221 format,
222 self.data_type().into(),
223 image,
224 )
225 .map_err(|e| GlError::WritePixelsError(Some(JsString::from(e).into())))
226 },
227 )?;
228
229 Ok(())
230 }
231
232 pub fn write_bytes(&self, bytes: &Vec<u8>) -> Result<(), GlError> {
233 let gl = self.gl();
234 let format: u32 = self.format().into();
235
236 gl.apply(
237 Gl::settings().active_texture(0).texture(0, self.clone()),
238 || {
239 gl.context()
240 .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
241 Context::TEXTURE_2D,
242 0,
243 format as i32,
244 self.width() as i32,
245 self.height() as i32,
246 0,
247 format,
248 self.data_type().into(),
249 Some(bytes),
250 )
251 .map_err(|e| GlError::WritePixelsError(Some(JsString::from(e).into())))
252 },
253 )?;
254
255 Ok(())
256 }
257
258 fn init_buffer(&self) -> Result<(), GlError> {
259 let gl = self.gl();
260 let format: u32 = self.format().into();
261
262 gl.apply(
263 Gl::settings().active_texture(0).texture(0, self.clone()),
264 || {
265 gl.context()
266 .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
267 Context::TEXTURE_2D,
268 0,
269 format as i32,
270 self.width() as i32,
271 self.height() as i32,
272 0,
273 format,
274 self.data_type().into(),
275 None,
276 )
277 .map_err(|e| GlError::InitTextureBufferError(Some(JsString::from(e).into())))
278 },
279 )?;
280
281 Ok(())
282 }
283
284 pub fn read_pixels_into_array(&self, array: &mut [u8]) -> Result<(), GlError> {
286 let size = self.width() * self.height() * 4;
287 if array.len() as u32 != size {
288 Err(GlError::InvalidBufferSize {
289 expected: size,
290 received: array.len() as u32,
291 })
292 } else {
293 let gl = self.gl();
294
295 gl.apply(
296 Gl::settings().frame_buffer(gl.frame_buffer_with_color(self.clone())?),
297 || {
298 gl.context()
299 .read_pixels_with_opt_u8_array(
300 0,
301 0,
302 self.width() as i32,
303 self.height() as i32,
304 TextureFormat::Rgba.into(),
305 TextureType::Byte.into(),
306 Some(array),
307 )
308 .map_err(|error_object| {
309 let error: Error = error_object.into();
310 GlError::ReadPixelsError(Some(error.message().into()))
311 })
312 },
313 )?;
314 Ok(())
315 }
316 }
317
318 pub fn read_pixels_into_buffer(&self, buffer: &Uint8Array) -> Result<(), GlError> {
320 if self.data_type() != TextureType::Byte {
321 Err(GlError::ReadPixelsError(Some(format!(
322 "Invalid texture data type {:?}",
323 self.data_type()
324 ))))
325 } else if buffer.length() != self.width() * self.height() * self.format().channels() {
326 Err(GlError::InvalidBufferSize {
327 expected: self.width() * self.height() * self.format().channels(),
328 received: buffer.length(),
329 })
330 } else {
331 let gl = self.gl();
332
333 gl.apply(
334 Gl::settings().frame_buffer(gl.frame_buffer_with_color(self.clone())?),
335 || {
336 gl.context()
337 .read_pixels_with_opt_array_buffer_view(
338 0,
339 0,
340 self.width() as i32,
341 self.height() as i32,
342 TextureFormat::Rgba.into(),
343 TextureType::Byte.into(),
344 Some(buffer),
345 )
346 .map_err(|error_object| {
347 let error: Error = error_object.into();
348 GlError::ReadPixelsError(Some(error.message().into()))
349 })
350 },
351 )?;
352 Ok(())
353 }
354 }
355
356 pub fn read_pixels_array(&self) -> Result<Vec<u8>, GlError> {
357 let mut result = Vec::with_capacity((self.width() * self.height() * 4) as usize);
358 self.read_pixels_into_array(&mut result)?;
359 return Ok(result);
360 }
361
362 pub fn read_pixels_buffer(&self) -> Result<Uint8Array, GlError> {
363 let result = Uint8Array::new_with_length(self.width() * self.height() * 4);
364 self.read_pixels_into_buffer(&result)?;
365 return Ok(result);
366 }
367
368 pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) -> Result<(), GlError> {
369 let gl = self.gl();
370
371 gl.apply(
372 Gl::settings()
373 .clear_color(r, g, b, a)
374 .viewport(0, 0, self.width() as i32, self.height() as i32)
375 .frame_buffer(gl.frame_buffer_with_color(self.clone())?),
376 || gl.clear_color_buffer(),
377 );
378
379 Ok(())
380 }
381}