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