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