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