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::<WebGLObject>()
170 .context()
171 .send_command(WebGLCommand::BindTexture(target, Some(self.id)));
172
173 Ok(())
174 }
175
176 #[allow(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 Ok(())
203 }
204
205 pub(crate) fn generate_mipmap(&self) -> WebGLResult<()> {
206 let target = match self.target.get() {
207 Some(target) => target,
208 None => {
209 error!("Cannot generate mipmap on texture that has no target!");
210 return Err(WebGLError::InvalidOperation);
211 },
212 };
213
214 let base_image_info = self.base_image_info().ok_or(WebGLError::InvalidOperation)?;
215
216 let is_cubic = target == constants::TEXTURE_CUBE_MAP;
217 if is_cubic && !self.is_cube_complete() {
218 return Err(WebGLError::InvalidOperation);
219 }
220
221 if !base_image_info.is_power_of_two() {
222 return Err(WebGLError::InvalidOperation);
223 }
224
225 if base_image_info.is_compressed_format() {
226 return Err(WebGLError::InvalidOperation);
227 }
228
229 self.upcast::<WebGLObject>()
230 .context()
231 .send_command(WebGLCommand::GenerateMipmap(target));
232
233 if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 {
234 return Err(WebGLError::InvalidOperation);
235 }
236
237 let last_level = self.base_mipmap_level + base_image_info.get_max_mimap_levels() - 1;
238 self.populate_mip_chain(self.base_mipmap_level, last_level)
239 }
240
241 pub(crate) fn delete(&self, operation_fallibility: Operation) {
242 if !self.is_deleted.get() {
243 self.is_deleted.set(true);
244 let context = self.upcast::<WebGLObject>().context();
245
246 if let Some(fb) = context.get_draw_framebuffer_slot().get() {
255 let _ = fb.detach_texture(self);
256 }
257 if let Some(fb) = context.get_read_framebuffer_slot().get() {
258 let _ = fb.detach_texture(self);
259 }
260
261 #[cfg(feature = "webxr")]
263 if let WebGLTextureOwner::WebXR(_) = self.owner {
264 return;
265 }
266
267 let cmd = WebGLCommand::DeleteTexture(self.id);
268 match operation_fallibility {
269 Operation::Fallible => context.send_command_ignored(cmd),
270 Operation::Infallible => context.send_command(cmd),
271 }
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 context = self.upcast::<WebGLObject>().context();
318 let is_webgl2 = context.webgl_version() == WebGLVersion::WebGL2;
319
320 let update_filter = |filter: &Cell<u32>| {
321 if filter.get() == int_value as u32 {
322 return Ok(());
323 }
324 filter.set(int_value as u32);
325 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
326 Ok(())
327 };
328 if is_webgl2 {
329 match param {
330 constants::TEXTURE_BASE_LEVEL | constants::TEXTURE_MAX_LEVEL => {
331 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
332 return Ok(());
333 },
334 constants::TEXTURE_COMPARE_FUNC => match int_value as u32 {
335 constants::LEQUAL |
336 constants::GEQUAL |
337 constants::LESS |
338 constants::GREATER |
339 constants::EQUAL |
340 constants::NOTEQUAL |
341 constants::ALWAYS |
342 constants::NEVER => {
343 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
344 return Ok(());
345 },
346 _ => return Err(WebGLError::InvalidEnum),
347 },
348 constants::TEXTURE_COMPARE_MODE => match int_value as u32 {
349 constants::COMPARE_REF_TO_TEXTURE | constants::NONE => {
350 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
351 return Ok(());
352 },
353 _ => return Err(WebGLError::InvalidEnum),
354 },
355 constants::TEXTURE_MAX_LOD | constants::TEXTURE_MIN_LOD => {
356 context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
357 return Ok(());
358 },
359 constants::TEXTURE_WRAP_R => match int_value as u32 {
360 constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
361 self.upcast::<WebGLObject>()
362 .context()
363 .send_command(WebGLCommand::TexParameteri(target, param, int_value));
364 return Ok(());
365 },
366 _ => return Err(WebGLError::InvalidEnum),
367 },
368 _ => {},
369 }
370 }
371 match param {
372 constants::TEXTURE_MIN_FILTER => match int_value as u32 {
373 constants::NEAREST |
374 constants::LINEAR |
375 constants::NEAREST_MIPMAP_NEAREST |
376 constants::LINEAR_MIPMAP_NEAREST |
377 constants::NEAREST_MIPMAP_LINEAR |
378 constants::LINEAR_MIPMAP_LINEAR => update_filter(&self.min_filter),
379 _ => Err(WebGLError::InvalidEnum),
380 },
381 constants::TEXTURE_MAG_FILTER => match int_value as u32 {
382 constants::NEAREST | constants::LINEAR => update_filter(&self.mag_filter),
383 _ => Err(WebGLError::InvalidEnum),
384 },
385 constants::TEXTURE_WRAP_S | constants::TEXTURE_WRAP_T => match int_value as u32 {
386 constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
387 context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
388 Ok(())
389 },
390 _ => Err(WebGLError::InvalidEnum),
391 },
392 EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT => {
393 if float_value < 1. || !float_value.is_normal() {
395 return Err(WebGLError::InvalidValue);
396 }
397 context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
398 Ok(())
399 },
400 _ => Err(WebGLError::InvalidEnum),
401 }
402 }
403
404 pub(crate) fn min_filter(&self) -> u32 {
405 self.min_filter.get()
406 }
407
408 pub(crate) fn mag_filter(&self) -> u32 {
409 self.mag_filter.get()
410 }
411
412 pub(crate) fn is_using_linear_filtering(&self) -> bool {
413 let filters = [self.min_filter.get(), self.mag_filter.get()];
414 filters.iter().any(|filter| {
415 matches!(
416 *filter,
417 constants::LINEAR |
418 constants::NEAREST_MIPMAP_LINEAR |
419 constants::LINEAR_MIPMAP_NEAREST |
420 constants::LINEAR_MIPMAP_LINEAR
421 )
422 })
423 }
424
425 pub(crate) fn populate_mip_chain(&self, first_level: u32, last_level: u32) -> WebGLResult<()> {
426 let base_image_info = self
427 .image_info_at_face(0, first_level)
428 .ok_or(WebGLError::InvalidOperation)?;
429
430 let mut ref_width = base_image_info.width;
431 let mut ref_height = base_image_info.height;
432
433 if ref_width == 0 || ref_height == 0 {
434 return Err(WebGLError::InvalidOperation);
435 }
436
437 for level in (first_level + 1)..last_level {
438 if ref_width == 1 && ref_height == 1 {
439 break;
440 }
441
442 ref_width = cmp::max(1, ref_width / 2);
443 ref_height = cmp::max(1, ref_height / 2);
444
445 let image_info = ImageInfo {
446 width: ref_width,
447 height: ref_height,
448 depth: 0,
449 internal_format: base_image_info.internal_format,
450 data_type: base_image_info.data_type,
451 };
452
453 self.set_image_infos_at_level(level, image_info);
454 }
455 Ok(())
456 }
457
458 fn is_cube_complete(&self) -> bool {
459 debug_assert_eq!(self.face_count.get(), 6);
460
461 let image_info = match self.base_image_info() {
462 Some(info) => info,
463 None => return false,
464 };
465
466 let ref_width = image_info.width;
467 let ref_format = image_info.internal_format;
468
469 for face in 0..self.face_count.get() {
470 let current_image_info = match self.image_info_at_face(face, self.base_mipmap_level) {
471 Some(info) => info,
472 None => return false,
473 };
474
475 if current_image_info.internal_format != ref_format ||
477 current_image_info.width != ref_width ||
478 current_image_info.height != ref_width
479 {
480 return false;
481 }
482 }
483
484 true
485 }
486
487 fn face_index_for_target(&self, target: &TexImageTarget) -> u8 {
488 match *target {
489 TexImageTarget::CubeMapPositiveX => 0,
490 TexImageTarget::CubeMapNegativeX => 1,
491 TexImageTarget::CubeMapPositiveY => 2,
492 TexImageTarget::CubeMapNegativeY => 3,
493 TexImageTarget::CubeMapPositiveZ => 4,
494 TexImageTarget::CubeMapNegativeZ => 5,
495 _ => 0,
496 }
497 }
498
499 pub(crate) fn image_info_for_target(
500 &self,
501 target: &TexImageTarget,
502 level: u32,
503 ) -> Option<ImageInfo> {
504 let face_index = self.face_index_for_target(target);
505 self.image_info_at_face(face_index, level)
506 }
507
508 pub(crate) fn image_info_at_face(&self, face: u8, level: u32) -> Option<ImageInfo> {
509 let pos = (level * self.face_count.get() as u32) + face as u32;
510 self.image_info_array.borrow()[pos as usize]
511 }
512
513 fn set_image_infos_at_level(&self, level: u32, image_info: ImageInfo) {
514 for face in 0..self.face_count.get() {
515 self.set_image_infos_at_level_and_face(level, face, image_info);
516 }
517 }
518
519 fn set_image_infos_at_level_and_face(&self, level: u32, face: u8, image_info: ImageInfo) {
520 debug_assert!(face < self.face_count.get());
521 let pos = (level * self.face_count.get() as u32) + face as u32;
522 self.image_info_array.borrow_mut()[pos as usize] = Some(image_info);
523 }
524
525 fn base_image_info(&self) -> Option<ImageInfo> {
526 assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT);
527
528 self.image_info_at_face(0, self.base_mipmap_level)
529 }
530
531 pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
532 self.attached_framebuffer.set(Some(fb));
533 }
534
535 pub(crate) fn detach_from_framebuffer(&self) {
536 self.attached_framebuffer.set(None);
537 }
538
539 pub(crate) fn storage(
540 &self,
541 target: TexImageTarget,
542 levels: u32,
543 internal_format: TexFormat,
544 width: u32,
545 height: u32,
546 depth: u32,
547 ) -> WebGLResult<()> {
548 assert!(!self.is_immutable());
550 assert!(self.target().is_some());
551
552 let target_id = target.as_gl_constant();
553 let command = match target {
554 TexImageTarget::Texture2D | TexImageTarget::CubeMap => {
555 WebGLCommand::TexStorage2D(target_id, levels, internal_format, width, height)
556 },
557 TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => {
558 WebGLCommand::TexStorage3D(target_id, levels, internal_format, width, height, depth)
559 },
560 _ => unreachable!(), };
562 self.upcast::<WebGLObject>().context().send_command(command);
563
564 let mut width = width;
565 let mut height = height;
566 let mut depth = depth;
567 for level in 0..levels {
568 let image_info = ImageInfo {
569 width,
570 height,
571 depth,
572 internal_format,
573 data_type: None,
574 };
575 self.set_image_infos_at_level(level, image_info);
576
577 width = cmp::max(1, width / 2);
578 height = cmp::max(1, height / 2);
579 depth = cmp::max(1, depth / 2);
580 }
581
582 self.immutable_levels.set(Some(levels));
583
584 if let Some(fb) = self.attached_framebuffer.get() {
585 fb.update_status();
586 }
587
588 Ok(())
589 }
590}
591
592impl Drop for WebGLTexture {
593 fn drop(&mut self) {
594 self.delete(Operation::Fallible);
595 }
596}
597
598#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
599pub(crate) struct ImageInfo {
600 width: u32,
601 height: u32,
602 depth: u32,
603 #[no_trace]
604 internal_format: TexFormat,
605 #[no_trace]
606 data_type: Option<TexDataType>,
607}
608
609impl ImageInfo {
610 pub(crate) fn width(&self) -> u32 {
611 self.width
612 }
613
614 pub(crate) fn height(&self) -> u32 {
615 self.height
616 }
617
618 pub(crate) fn internal_format(&self) -> TexFormat {
619 self.internal_format
620 }
621
622 pub(crate) fn data_type(&self) -> Option<TexDataType> {
623 self.data_type
624 }
625
626 fn is_power_of_two(&self) -> bool {
627 self.width.is_power_of_two() &&
628 self.height.is_power_of_two() &&
629 self.depth.is_power_of_two()
630 }
631
632 fn get_max_mimap_levels(&self) -> u32 {
633 let largest = cmp::max(cmp::max(self.width, self.height), self.depth);
634 if largest == 0 {
635 return 0;
636 }
637 (largest as f64).log2() as u32 + 1
639 }
640
641 fn is_compressed_format(&self) -> bool {
642 self.internal_format.is_compressed()
643 }
644}
645
646#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
647pub(crate) enum TexCompressionValidation {
648 None,
649 S3TC,
650}
651
652#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
653pub(crate) struct TexCompression {
654 #[no_trace]
655 pub(crate) format: TexFormat,
656 pub(crate) bytes_per_block: u8,
657 pub(crate) block_width: u8,
658 pub(crate) block_height: u8,
659 pub(crate) validation: TexCompressionValidation,
660}