1use std::cell::Cell;
8use std::cmp;
9
10use canvas_traits::webgl::{
11 TexDataType, TexFormat, TexParameter, TexParameterBool, TexParameterInt, WebGLCommand,
12 WebGLError, WebGLResult, WebGLTextureId, WebGLVersion, webgl_channel,
13};
14use dom_struct::dom_struct;
15
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants;
18use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
21#[cfg(feature = "webxr")]
22use crate::dom::bindings::root::Dom;
23use crate::dom::bindings::root::{DomRoot, MutNullableDom};
24use crate::dom::webgl::validations::types::TexImageTarget;
25use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
26use crate::dom::webgl::webglobject::WebGLObject;
27use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
28#[cfg(feature = "webxr")]
29use crate::dom::xrsession::XRSession;
30use crate::script_runtime::CanGc;
31
32pub(crate) enum TexParameterValue {
33 Float(f32),
34 Int(i32),
35 Bool(bool),
36}
37
38#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41#[derive(JSTraceable, MallocSizeOf)]
42enum WebGLTextureOwner {
43 WebGL,
44 #[cfg(feature = "webxr")]
45 WebXR(Dom<XRSession>),
46}
47
48const MAX_LEVEL_COUNT: usize = 31;
49const MAX_FACE_COUNT: usize = 6;
50
51#[dom_struct]
52pub(crate) struct WebGLTexture {
53 webgl_object: WebGLObject,
54 #[no_trace]
55 id: WebGLTextureId,
56 target: Cell<Option<u32>>,
58 is_deleted: Cell<bool>,
59 owner: WebGLTextureOwner,
60 #[ignore_malloc_size_of = "Arrays are cumbersome"]
62 image_info_array: DomRefCell<[Option<ImageInfo>; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>,
63 face_count: Cell<u8>,
65 base_mipmap_level: u32,
66 min_filter: Cell<u32>,
68 mag_filter: Cell<u32>,
69 attached_framebuffer: MutNullableDom<WebGLFramebuffer>,
71 immutable_levels: Cell<Option<u32>>,
73}
74
75impl WebGLTexture {
76 fn new_inherited(
77 context: &WebGLRenderingContext,
78 id: WebGLTextureId,
79 #[cfg(feature = "webxr")] owner: Option<&XRSession>,
80 ) -> Self {
81 Self {
82 webgl_object: WebGLObject::new_inherited(context),
83 id,
84 target: Cell::new(None),
85 is_deleted: Cell::new(false),
86 #[cfg(feature = "webxr")]
87 owner: owner
88 .map(|session| WebGLTextureOwner::WebXR(Dom::from_ref(session)))
89 .unwrap_or(WebGLTextureOwner::WebGL),
90 #[cfg(not(feature = "webxr"))]
91 owner: WebGLTextureOwner::WebGL,
92 immutable_levels: Cell::new(None),
93 face_count: Cell::new(0),
94 base_mipmap_level: 0,
95 min_filter: Cell::new(constants::NEAREST_MIPMAP_LINEAR),
96 mag_filter: Cell::new(constants::LINEAR),
97 image_info_array: DomRefCell::new([None; MAX_LEVEL_COUNT * MAX_FACE_COUNT]),
98 attached_framebuffer: Default::default(),
99 }
100 }
101
102 pub(crate) fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
103 let (sender, receiver) = webgl_channel().unwrap();
104 context.send_command(WebGLCommand::CreateTexture(sender));
105 receiver
106 .recv()
107 .unwrap()
108 .map(|id| WebGLTexture::new(context, id, CanGc::note()))
109 }
110
111 pub(crate) fn new(
112 context: &WebGLRenderingContext,
113 id: WebGLTextureId,
114 can_gc: CanGc,
115 ) -> DomRoot<Self> {
116 reflect_dom_object(
117 Box::new(WebGLTexture::new_inherited(
118 context,
119 id,
120 #[cfg(feature = "webxr")]
121 None,
122 )),
123 &*context.global(),
124 can_gc,
125 )
126 }
127
128 #[cfg(feature = "webxr")]
129 pub(crate) fn new_webxr(
130 context: &WebGLRenderingContext,
131 id: WebGLTextureId,
132 session: &XRSession,
133 can_gc: CanGc,
134 ) -> DomRoot<Self> {
135 reflect_dom_object(
136 Box::new(WebGLTexture::new_inherited(context, id, Some(session))),
137 &*context.global(),
138 can_gc,
139 )
140 }
141}
142
143impl WebGLTexture {
144 pub(crate) fn id(&self) -> WebGLTextureId {
145 self.id
146 }
147
148 pub(crate) fn bind(&self, target: u32) -> WebGLResult<()> {
150 if self.is_invalid() {
151 return Err(WebGLError::InvalidOperation);
152 }
153
154 if let Some(previous_target) = self.target.get() {
155 if target != previous_target {
156 return Err(WebGLError::InvalidOperation);
157 }
158 } else {
159 let face_count = match target {
161 constants::TEXTURE_2D | constants::TEXTURE_2D_ARRAY | constants::TEXTURE_3D => 1,
162 constants::TEXTURE_CUBE_MAP => 6,
163 _ => return Err(WebGLError::InvalidEnum),
164 };
165 self.face_count.set(face_count);
166 self.target.set(Some(target));
167 }
168
169 self.upcast()
170 .send_command(WebGLCommand::BindTexture(target, Some(self.id)));
171
172 Ok(())
173 }
174
175 #[allow(clippy::too_many_arguments)]
176 pub(crate) fn initialize(
177 &self,
178 target: TexImageTarget,
179 width: u32,
180 height: u32,
181 depth: u32,
182 internal_format: TexFormat,
183 level: u32,
184 data_type: Option<TexDataType>,
185 ) -> WebGLResult<()> {
186 let image_info = ImageInfo {
187 width,
188 height,
189 depth,
190 internal_format,
191 data_type,
192 };
193
194 let face_index = self.face_index_for_target(&target);
195 self.set_image_infos_at_level_and_face(level, face_index, image_info);
196
197 if let Some(fb) = self.attached_framebuffer.get() {
198 fb.update_status();
199 }
200
201 Ok(())
202 }
203
204 pub(crate) fn generate_mipmap(&self) -> WebGLResult<()> {
205 let target = match self.target.get() {
206 Some(target) => target,
207 None => {
208 error!("Cannot generate mipmap on texture that has no target!");
209 return Err(WebGLError::InvalidOperation);
210 },
211 };
212
213 let base_image_info = self.base_image_info().ok_or(WebGLError::InvalidOperation)?;
214
215 let is_cubic = target == constants::TEXTURE_CUBE_MAP;
216 if is_cubic && !self.is_cube_complete() {
217 return Err(WebGLError::InvalidOperation);
218 }
219
220 if !base_image_info.is_power_of_two() {
221 return Err(WebGLError::InvalidOperation);
222 }
223
224 if base_image_info.is_compressed_format() {
225 return Err(WebGLError::InvalidOperation);
226 }
227
228 self.upcast()
229 .send_command(WebGLCommand::GenerateMipmap(target));
230
231 if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 {
232 return Err(WebGLError::InvalidOperation);
233 }
234
235 let last_level = self.base_mipmap_level + base_image_info.get_max_mimap_levels() - 1;
236 self.populate_mip_chain(self.base_mipmap_level, last_level)
237 }
238
239 pub(crate) fn delete(&self, operation_fallibility: Operation) {
240 if !self.is_deleted.get() {
241 self.is_deleted.set(true);
242
243 let webgl_object = self.upcast();
252 if let Some(context) = webgl_object.context() {
253 if let Some(fb) = context.get_draw_framebuffer_slot().get() {
254 let _ = fb.detach_texture(self);
255 }
256 if let Some(fb) = context.get_read_framebuffer_slot().get() {
257 let _ = fb.detach_texture(self);
258 }
259 }
260
261 #[cfg(feature = "webxr")]
263 if let WebGLTextureOwner::WebXR(_) = self.owner {
264 return;
265 }
266
267 webgl_object
268 .send_with_fallibility(WebGLCommand::DeleteTexture(self.id), operation_fallibility);
269 }
270 }
271
272 pub(crate) fn is_invalid(&self) -> bool {
273 #[cfg(feature = "webxr")]
275 if let WebGLTextureOwner::WebXR(ref session) = self.owner {
276 if session.is_outside_raf() {
277 return true;
278 }
279 }
280 self.is_deleted.get()
281 }
282
283 pub(crate) fn is_immutable(&self) -> bool {
284 self.immutable_levels.get().is_some()
285 }
286
287 pub(crate) fn target(&self) -> Option<u32> {
288 self.target.get()
289 }
290
291 pub(crate) fn maybe_get_tex_parameter(&self, param: TexParameter) -> Option<TexParameterValue> {
292 match param {
293 TexParameter::Int(TexParameterInt::TextureImmutableLevels) => Some(
294 TexParameterValue::Int(self.immutable_levels.get().unwrap_or(0) as i32),
295 ),
296 TexParameter::Bool(TexParameterBool::TextureImmutableFormat) => {
297 Some(TexParameterValue::Bool(self.is_immutable()))
298 },
299 _ => None,
300 }
301 }
302
303 pub(crate) fn tex_parameter(&self, param: u32, value: TexParameterValue) -> WebGLResult<()> {
306 let target = self.target().unwrap();
307
308 let (int_value, float_value) = match value {
309 TexParameterValue::Int(int_value) => (int_value, int_value as f32),
310 TexParameterValue::Float(float_value) => (float_value as i32, float_value),
311 TexParameterValue::Bool(_) => unreachable!("no settable tex params should be booleans"),
312 };
313
314 let Some(context) = self.upcast().context() else {
315 return Err(WebGLError::ContextLost);
316 };
317 let is_webgl2 = context.webgl_version() == WebGLVersion::WebGL2;
318
319 let update_filter = |filter: &Cell<u32>| {
320 if filter.get() == int_value as u32 {
321 return Ok(());
322 }
323 filter.set(int_value as u32);
324 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
325 Ok(())
326 };
327 if is_webgl2 {
328 match param {
329 constants::TEXTURE_BASE_LEVEL | constants::TEXTURE_MAX_LEVEL => {
330 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
331 return Ok(());
332 },
333 constants::TEXTURE_COMPARE_FUNC => match int_value as u32 {
334 constants::LEQUAL |
335 constants::GEQUAL |
336 constants::LESS |
337 constants::GREATER |
338 constants::EQUAL |
339 constants::NOTEQUAL |
340 constants::ALWAYS |
341 constants::NEVER => {
342 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
343 return Ok(());
344 },
345 _ => return Err(WebGLError::InvalidEnum),
346 },
347 constants::TEXTURE_COMPARE_MODE => match int_value as u32 {
348 constants::COMPARE_REF_TO_TEXTURE | constants::NONE => {
349 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
350 return Ok(());
351 },
352 _ => return Err(WebGLError::InvalidEnum),
353 },
354 constants::TEXTURE_MAX_LOD | constants::TEXTURE_MIN_LOD => {
355 context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
356 return Ok(());
357 },
358 constants::TEXTURE_WRAP_R => match int_value as u32 {
359 constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
360 self.upcast()
361 .send_command(WebGLCommand::TexParameteri(target, param, int_value));
362 return Ok(());
363 },
364 _ => return Err(WebGLError::InvalidEnum),
365 },
366 _ => {},
367 }
368 }
369 match param {
370 constants::TEXTURE_MIN_FILTER => match int_value as u32 {
371 constants::NEAREST |
372 constants::LINEAR |
373 constants::NEAREST_MIPMAP_NEAREST |
374 constants::LINEAR_MIPMAP_NEAREST |
375 constants::NEAREST_MIPMAP_LINEAR |
376 constants::LINEAR_MIPMAP_LINEAR => update_filter(&self.min_filter),
377 _ => Err(WebGLError::InvalidEnum),
378 },
379 constants::TEXTURE_MAG_FILTER => match int_value as u32 {
380 constants::NEAREST | constants::LINEAR => update_filter(&self.mag_filter),
381 _ => Err(WebGLError::InvalidEnum),
382 },
383 constants::TEXTURE_WRAP_S | constants::TEXTURE_WRAP_T => match int_value as u32 {
384 constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
385 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
386 Ok(())
387 },
388 _ => Err(WebGLError::InvalidEnum),
389 },
390 EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT => {
391 if float_value < 1. || !float_value.is_normal() {
393 return Err(WebGLError::InvalidValue);
394 }
395 context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
396 Ok(())
397 },
398 _ => Err(WebGLError::InvalidEnum),
399 }
400 }
401
402 pub(crate) fn min_filter(&self) -> u32 {
403 self.min_filter.get()
404 }
405
406 pub(crate) fn mag_filter(&self) -> u32 {
407 self.mag_filter.get()
408 }
409
410 pub(crate) fn is_using_linear_filtering(&self) -> bool {
411 let filters = [self.min_filter.get(), self.mag_filter.get()];
412 filters.iter().any(|filter| {
413 matches!(
414 *filter,
415 constants::LINEAR |
416 constants::NEAREST_MIPMAP_LINEAR |
417 constants::LINEAR_MIPMAP_NEAREST |
418 constants::LINEAR_MIPMAP_LINEAR
419 )
420 })
421 }
422
423 pub(crate) fn populate_mip_chain(&self, first_level: u32, last_level: u32) -> WebGLResult<()> {
424 let base_image_info = self
425 .image_info_at_face(0, first_level)
426 .ok_or(WebGLError::InvalidOperation)?;
427
428 let mut ref_width = base_image_info.width;
429 let mut ref_height = base_image_info.height;
430
431 if ref_width == 0 || ref_height == 0 {
432 return Err(WebGLError::InvalidOperation);
433 }
434
435 for level in (first_level + 1)..last_level {
436 if ref_width == 1 && ref_height == 1 {
437 break;
438 }
439
440 ref_width = cmp::max(1, ref_width / 2);
441 ref_height = cmp::max(1, ref_height / 2);
442
443 let image_info = ImageInfo {
444 width: ref_width,
445 height: ref_height,
446 depth: 0,
447 internal_format: base_image_info.internal_format,
448 data_type: base_image_info.data_type,
449 };
450
451 self.set_image_infos_at_level(level, image_info);
452 }
453 Ok(())
454 }
455
456 fn is_cube_complete(&self) -> bool {
457 debug_assert_eq!(self.face_count.get(), 6);
458
459 let image_info = match self.base_image_info() {
460 Some(info) => info,
461 None => return false,
462 };
463
464 let ref_width = image_info.width;
465 let ref_format = image_info.internal_format;
466
467 for face in 0..self.face_count.get() {
468 let current_image_info = match self.image_info_at_face(face, self.base_mipmap_level) {
469 Some(info) => info,
470 None => return false,
471 };
472
473 if current_image_info.internal_format != ref_format ||
475 current_image_info.width != ref_width ||
476 current_image_info.height != ref_width
477 {
478 return false;
479 }
480 }
481
482 true
483 }
484
485 fn face_index_for_target(&self, target: &TexImageTarget) -> u8 {
486 match *target {
487 TexImageTarget::CubeMapPositiveX => 0,
488 TexImageTarget::CubeMapNegativeX => 1,
489 TexImageTarget::CubeMapPositiveY => 2,
490 TexImageTarget::CubeMapNegativeY => 3,
491 TexImageTarget::CubeMapPositiveZ => 4,
492 TexImageTarget::CubeMapNegativeZ => 5,
493 _ => 0,
494 }
495 }
496
497 pub(crate) fn image_info_for_target(
498 &self,
499 target: &TexImageTarget,
500 level: u32,
501 ) -> Option<ImageInfo> {
502 let face_index = self.face_index_for_target(target);
503 self.image_info_at_face(face_index, level)
504 }
505
506 pub(crate) fn image_info_at_face(&self, face: u8, level: u32) -> Option<ImageInfo> {
507 let pos = (level * self.face_count.get() as u32) + face as u32;
508 self.image_info_array.borrow()[pos as usize]
509 }
510
511 fn set_image_infos_at_level(&self, level: u32, image_info: ImageInfo) {
512 for face in 0..self.face_count.get() {
513 self.set_image_infos_at_level_and_face(level, face, image_info);
514 }
515 }
516
517 fn set_image_infos_at_level_and_face(&self, level: u32, face: u8, image_info: ImageInfo) {
518 debug_assert!(face < self.face_count.get());
519 let pos = (level * self.face_count.get() as u32) + face as u32;
520 self.image_info_array.borrow_mut()[pos as usize] = Some(image_info);
521 }
522
523 fn base_image_info(&self) -> Option<ImageInfo> {
524 assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT);
525
526 self.image_info_at_face(0, self.base_mipmap_level)
527 }
528
529 pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
530 self.attached_framebuffer.set(Some(fb));
531 }
532
533 pub(crate) fn detach_from_framebuffer(&self) {
534 self.attached_framebuffer.set(None);
535 }
536
537 pub(crate) fn storage(
538 &self,
539 target: TexImageTarget,
540 levels: u32,
541 internal_format: TexFormat,
542 width: u32,
543 height: u32,
544 depth: u32,
545 ) -> WebGLResult<()> {
546 assert!(!self.is_immutable());
548 assert!(self.target().is_some());
549
550 let target_id = target.as_gl_constant();
551 let command = match target {
552 TexImageTarget::Texture2D | TexImageTarget::CubeMap => {
553 WebGLCommand::TexStorage2D(target_id, levels, internal_format, width, height)
554 },
555 TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => {
556 WebGLCommand::TexStorage3D(target_id, levels, internal_format, width, height, depth)
557 },
558 _ => unreachable!(), };
560 self.upcast().send_command(command);
561
562 let mut width = width;
563 let mut height = height;
564 let mut depth = depth;
565 for level in 0..levels {
566 let image_info = ImageInfo {
567 width,
568 height,
569 depth,
570 internal_format,
571 data_type: None,
572 };
573 self.set_image_infos_at_level(level, image_info);
574
575 width = cmp::max(1, width / 2);
576 height = cmp::max(1, height / 2);
577 depth = cmp::max(1, depth / 2);
578 }
579
580 self.immutable_levels.set(Some(levels));
581
582 if let Some(fb) = self.attached_framebuffer.get() {
583 fb.update_status();
584 }
585
586 Ok(())
587 }
588}
589
590impl Drop for WebGLTexture {
591 fn drop(&mut self) {
592 self.delete(Operation::Fallible);
593 }
594}
595
596#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
597pub(crate) struct ImageInfo {
598 width: u32,
599 height: u32,
600 depth: u32,
601 #[no_trace]
602 internal_format: TexFormat,
603 #[no_trace]
604 data_type: Option<TexDataType>,
605}
606
607impl ImageInfo {
608 pub(crate) fn width(&self) -> u32 {
609 self.width
610 }
611
612 pub(crate) fn height(&self) -> u32 {
613 self.height
614 }
615
616 pub(crate) fn internal_format(&self) -> TexFormat {
617 self.internal_format
618 }
619
620 pub(crate) fn data_type(&self) -> Option<TexDataType> {
621 self.data_type
622 }
623
624 fn is_power_of_two(&self) -> bool {
625 self.width.is_power_of_two() &&
626 self.height.is_power_of_two() &&
627 self.depth.is_power_of_two()
628 }
629
630 fn get_max_mimap_levels(&self) -> u32 {
631 let largest = cmp::max(cmp::max(self.width, self.height), self.depth);
632 if largest == 0 {
633 return 0;
634 }
635 (largest as f64).log2() as u32 + 1
637 }
638
639 fn is_compressed_format(&self) -> bool {
640 self.internal_format.is_compressed()
641 }
642}
643
644#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
645pub(crate) enum TexCompressionValidation {
646 None,
647 S3TC,
648}
649
650#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
651pub(crate) struct TexCompression {
652 #[no_trace]
653 pub(crate) format: TexFormat,
654 pub(crate) bytes_per_block: u8,
655 pub(crate) block_width: u8,
656 pub(crate) block_height: u8,
657 pub(crate) validation: TexCompressionValidation,
658}