1use std::cell::Cell;
7
8use canvas_traits::webgl::{
9 WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLFramebufferId,
10 WebGLRenderbufferId, WebGLResult, WebGLTextureId, WebGLVersion, webgl_channel,
11};
12use dom_struct::dom_struct;
13#[cfg(feature = "webxr")]
14use euclid::Size2D;
15use script_bindings::weakref::WeakRef;
16#[cfg(feature = "webxr")]
17use webxr_api::Viewport;
18
19use crate::dom::bindings::cell::DomRefCell;
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};
23#[cfg(feature = "webxr")]
24use crate::dom::bindings::root::MutNullableDom;
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::webgl::webglobject::WebGLObject;
27use crate::dom::webgl::webglrenderbuffer::WebGLRenderbuffer;
28use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
29use crate::dom::webgl::webgltexture::WebGLTexture;
30#[cfg(feature = "webxr")]
31use crate::dom::xrsession::XRSession;
32use crate::script_runtime::CanGc;
33
34pub(crate) enum CompleteForRendering {
35 Complete,
36 Incomplete,
37 MissingColorAttachment,
38}
39
40fn log2(n: u32) -> u32 {
41 31 - n.leading_zeros()
42}
43
44#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
45#[derive(Clone, JSTraceable, MallocSizeOf)]
46enum WebGLFramebufferAttachment {
47 Renderbuffer(Dom<WebGLRenderbuffer>),
48 Texture {
49 texture: Dom<WebGLTexture>,
50 level: i32,
51 },
52}
53
54impl WebGLFramebufferAttachment {
55 fn needs_initialization(&self) -> bool {
56 match *self {
57 WebGLFramebufferAttachment::Renderbuffer(ref r) => !r.is_initialized(),
58 WebGLFramebufferAttachment::Texture { .. } => false,
59 }
60 }
61
62 fn mark_initialized(&self) {
63 match *self {
64 WebGLFramebufferAttachment::Renderbuffer(ref r) => r.mark_initialized(),
65 WebGLFramebufferAttachment::Texture { .. } => (),
66 }
67 }
68
69 fn root(&self) -> WebGLFramebufferAttachmentRoot {
70 match self {
71 WebGLFramebufferAttachment::Renderbuffer(rb) => {
72 WebGLFramebufferAttachmentRoot::Renderbuffer(DomRoot::from_ref(rb))
73 },
74 WebGLFramebufferAttachment::Texture { texture, .. } => {
75 WebGLFramebufferAttachmentRoot::Texture(DomRoot::from_ref(texture))
76 },
77 }
78 }
79
80 fn detach(&self) {
81 match self {
82 WebGLFramebufferAttachment::Renderbuffer(rb) => rb.detach_from_framebuffer(),
83 WebGLFramebufferAttachment::Texture { texture, .. } => {
84 texture.detach_from_framebuffer()
85 },
86 }
87 }
88}
89
90#[derive(Clone, JSTraceable, MallocSizeOf)]
91pub(crate) enum WebGLFramebufferAttachmentRoot {
92 Renderbuffer(DomRoot<WebGLRenderbuffer>),
93 Texture(DomRoot<WebGLTexture>),
94}
95
96#[derive(JSTraceable, MallocSizeOf)]
97struct DroppableWebGLFramebuffer {
98 #[no_trace]
99 id: WebGLFramebufferId,
100 is_deleted: Cell<bool>,
101 context: WeakRef<WebGLRenderingContext>,
102}
103
104impl DroppableWebGLFramebuffer {
105 fn new(
106 id: WebGLFramebufferId,
107 is_deleted: Cell<bool>,
108 context: WeakRef<WebGLRenderingContext>,
109 ) -> Self {
110 Self {
111 id,
112 is_deleted,
113 context,
114 }
115 }
116}
117
118impl DroppableWebGLFramebuffer {
119 pub(crate) fn id(&self) -> WebGLFramebufferId {
120 self.id
121 }
122 pub(crate) fn is_deleted(&self) -> bool {
123 self.is_deleted.get()
124 }
125
126 pub(crate) fn set_deleted(&self, deleted: bool) {
127 self.is_deleted.set(deleted);
128 }
129
130 pub(crate) fn delete(&self, operation_fallibility: Operation) {
131 if !self.is_deleted() {
132 self.set_deleted(true);
133 if let Some(context) = self.context.root() {
134 let cmd = WebGLCommand::DeleteFramebuffer(self.id());
135 match operation_fallibility {
136 Operation::Fallible => context.send_command_ignored(cmd),
137 Operation::Infallible => context.send_command(cmd),
138 }
139 }
140 }
141 }
142}
143
144impl Drop for DroppableWebGLFramebuffer {
145 fn drop(&mut self) {
146 self.delete(Operation::Fallible);
147 }
148}
149
150#[dom_struct]
151pub(crate) struct WebGLFramebuffer {
152 webgl_object: WebGLObject,
153 #[no_trace]
154 webgl_version: WebGLVersion,
155 target: Cell<Option<u32>>,
156 size: Cell<Option<(i32, i32)>>,
157 status: Cell<u32>,
158 colors: Vec<DomRefCell<Option<WebGLFramebufferAttachment>>>,
161 depth: DomRefCell<Option<WebGLFramebufferAttachment>>,
162 stencil: DomRefCell<Option<WebGLFramebufferAttachment>>,
163 depthstencil: DomRefCell<Option<WebGLFramebufferAttachment>>,
164 color_read_buffer: DomRefCell<u32>,
165 color_draw_buffers: DomRefCell<Vec<u32>>,
166 is_initialized: Cell<bool>,
167 #[cfg(feature = "webxr")]
170 xr_session: MutNullableDom<XRSession>,
171 droppable: DroppableWebGLFramebuffer,
172}
173
174impl WebGLFramebuffer {
175 fn new_inherited(context: &WebGLRenderingContext, id: WebGLFramebufferId) -> Self {
176 Self {
177 webgl_object: WebGLObject::new_inherited(context),
178 webgl_version: context.webgl_version(),
179 target: Cell::new(None),
180 size: Cell::new(None),
181 status: Cell::new(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT),
182 colors: vec![DomRefCell::new(None); context.limits().max_color_attachments as usize],
183 depth: DomRefCell::new(None),
184 stencil: DomRefCell::new(None),
185 depthstencil: DomRefCell::new(None),
186 color_read_buffer: DomRefCell::new(constants::COLOR_ATTACHMENT0),
187 color_draw_buffers: DomRefCell::new(vec![constants::COLOR_ATTACHMENT0]),
188 is_initialized: Cell::new(false),
189 #[cfg(feature = "webxr")]
190 xr_session: Default::default(),
191 droppable: DroppableWebGLFramebuffer::new(id, Cell::new(false), WeakRef::new(context)),
192 }
193 }
194
195 pub(crate) fn maybe_new(
196 context: &WebGLRenderingContext,
197 can_gc: CanGc,
198 ) -> Option<DomRoot<Self>> {
199 let (sender, receiver) = webgl_channel().unwrap();
200 context.send_command(WebGLCommand::CreateFramebuffer(sender));
201 let id = receiver.recv().unwrap()?;
202 let framebuffer = WebGLFramebuffer::new(context, id, can_gc);
203 Some(framebuffer)
204 }
205
206 #[cfg(feature = "webxr")]
209 pub(crate) fn maybe_new_webxr(
210 session: &XRSession,
211 context: &WebGLRenderingContext,
212 size: Size2D<i32, Viewport>,
213 can_gc: CanGc,
214 ) -> Option<DomRoot<Self>> {
215 let framebuffer = Self::maybe_new(context, can_gc)?;
216 framebuffer.size.set(Some((size.width, size.height)));
217 framebuffer.status.set(constants::FRAMEBUFFER_COMPLETE);
218 framebuffer.xr_session.set(Some(session));
219 Some(framebuffer)
220 }
221
222 pub(crate) fn new(
223 context: &WebGLRenderingContext,
224 id: WebGLFramebufferId,
225 can_gc: CanGc,
226 ) -> DomRoot<Self> {
227 reflect_dom_object(
228 Box::new(WebGLFramebuffer::new_inherited(context, id)),
229 &*context.global(),
230 can_gc,
231 )
232 }
233}
234
235impl WebGLFramebuffer {
236 pub(crate) fn id(&self) -> WebGLFramebufferId {
237 self.droppable.id()
238 }
239
240 #[cfg(feature = "webxr")]
241 fn is_in_xr_session(&self) -> bool {
242 self.xr_session.get().is_some()
243 }
244
245 #[cfg(not(feature = "webxr"))]
246 fn is_in_xr_session(&self) -> bool {
247 false
248 }
249
250 pub(crate) fn validate_transparent(&self) -> WebGLResult<()> {
251 if self.is_in_xr_session() {
252 Err(WebGLError::InvalidOperation)
253 } else {
254 Ok(())
255 }
256 }
257
258 pub(crate) fn bind(&self, target: u32) {
259 if !self.is_in_xr_session() {
260 self.update_status();
264 }
265
266 self.target.set(Some(target));
267 self.upcast::<WebGLObject>()
268 .context()
269 .send_command(WebGLCommand::BindFramebuffer(
270 target,
271 WebGLFramebufferBindingRequest::Explicit(self.id()),
272 ));
273 }
274
275 pub(crate) fn delete(&self, operation_fallibility: Operation) {
276 self.droppable.delete(operation_fallibility);
277 }
278
279 pub(crate) fn is_deleted(&self) -> bool {
280 self.droppable.is_deleted()
284 }
285
286 pub(crate) fn size(&self) -> Option<(i32, i32)> {
287 self.size.get()
288 }
289
290 pub(crate) fn get_attachment_formats(
291 &self,
292 ) -> WebGLResult<(Option<u32>, Option<u32>, Option<u32>)> {
293 if self.check_status() != constants::FRAMEBUFFER_COMPLETE {
294 return Err(WebGLError::InvalidFramebufferOperation);
295 }
296 let color = match self.attachment(constants::COLOR_ATTACHMENT0) {
297 Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
298 _ => None,
299 };
300 let depth = match self.attachment(constants::DEPTH_ATTACHMENT) {
301 Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
302 _ => None,
303 };
304 let stencil = match self.attachment(constants::STENCIL_ATTACHMENT) {
305 Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
306 _ => None,
307 };
308 Ok((color, depth, stencil))
309 }
310
311 fn check_attachment_constraints<'a>(
312 &self,
313 attachment: &Option<WebGLFramebufferAttachment>,
314 mut constraints: impl Iterator<Item = &'a u32>,
315 fb_size: &mut Option<(i32, i32)>,
316 ) -> Result<(), u32> {
317 let (format, size) = match attachment {
319 Some(WebGLFramebufferAttachment::Renderbuffer(att_rb)) => {
320 (Some(att_rb.internal_format()), att_rb.size())
321 },
322 Some(WebGLFramebufferAttachment::Texture {
323 texture: att_tex,
324 level,
325 }) => match att_tex.image_info_at_face(0, *level as u32) {
326 Some(info) => (
327 Some(info.internal_format().as_gl_constant()),
328 Some((info.width() as i32, info.height() as i32)),
329 ),
330 None => return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
331 },
332 None => (None, None),
333 };
334
335 if size.is_some() {
338 if fb_size.is_some() && size != *fb_size {
339 return Err(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
340 } else {
341 *fb_size = size;
342 }
343 }
344
345 if let Some(format) = format {
346 if constraints.all(|c| *c != format) {
347 return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
348 }
349 }
350
351 Ok(())
352 }
353
354 pub(crate) fn update_status(&self) {
355 let z = self.depth.borrow();
356 let s = self.stencil.borrow();
357 let zs = self.depthstencil.borrow();
358 let has_z = z.is_some();
359 let has_s = s.is_some();
360 let has_zs = zs.is_some();
361
362 let is_supported = match self.webgl_version {
363 WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s),
377
378 WebGLVersion::WebGL2 => {
386 use WebGLFramebufferAttachment::{Renderbuffer, Texture};
387 match (&*z, &*s) {
388 (Some(Renderbuffer(a)), Some(Renderbuffer(b))) => a.id() == b.id(),
389 (Some(Texture { texture: a, .. }), Some(Texture { texture: b, .. })) => {
390 a.id() == b.id()
391 },
392 _ => !has_z || !has_s,
393 }
394 },
395 };
396 if !is_supported {
397 return self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
398 }
399
400 let mut fb_size = None;
401
402 let attachments = [&*z, &*s, &*zs];
403 let webgl1_attachment_constraints = &[
404 &[
405 constants::DEPTH_COMPONENT16,
406 constants::DEPTH_COMPONENT24,
407 constants::DEPTH_COMPONENT32F,
408 constants::DEPTH24_STENCIL8,
409 constants::DEPTH32F_STENCIL8,
410 ][..],
411 &[
412 constants::STENCIL_INDEX8,
413 constants::DEPTH24_STENCIL8,
414 constants::DEPTH32F_STENCIL8,
415 ][..],
416 &[constants::DEPTH_STENCIL][..],
417 ];
418 let webgl2_attachment_constraints = &[
419 &[constants::DEPTH_STENCIL][..],
420 &[constants::DEPTH_STENCIL][..],
421 &[][..],
422 ];
423 let empty_attachment_constrains = &[&[][..], &[][..], &[][..]];
424 let extra_attachment_constraints = match self.webgl_version {
425 WebGLVersion::WebGL1 => empty_attachment_constrains,
426 WebGLVersion::WebGL2 => webgl2_attachment_constraints,
427 };
428 let attachment_constraints = webgl1_attachment_constraints
429 .iter()
430 .zip(extra_attachment_constraints.iter())
431 .map(|(a, b)| a.iter().chain(b.iter()));
432
433 for (attachment, constraints) in attachments.iter().zip(attachment_constraints) {
434 if let Err(errnum) =
435 self.check_attachment_constraints(attachment, constraints, &mut fb_size)
436 {
437 return self.status.set(errnum);
438 }
439 }
440
441 let webgl1_color_constraints = &[
442 constants::RGB,
443 constants::RGB565,
444 constants::RGB5_A1,
445 constants::RGBA,
446 constants::RGBA4,
447 ][..];
448 let webgl2_color_constraints = &[
449 constants::ALPHA,
450 constants::LUMINANCE,
451 constants::LUMINANCE_ALPHA,
452 constants::R11F_G11F_B10F,
453 constants::R16F,
454 constants::R16I,
455 constants::R16UI,
456 constants::R32F,
457 constants::R32I,
458 constants::R32UI,
459 constants::R8,
460 constants::R8_SNORM,
461 constants::R8I,
462 constants::R8UI,
463 constants::RG16F,
464 constants::RG16I,
465 constants::RG16UI,
466 constants::RG32F,
467 constants::RG32I,
468 constants::RG32UI,
469 constants::RG8,
470 constants::RG8_SNORM,
471 constants::RG8I,
472 constants::RG8UI,
473 constants::RGB10_A2,
474 constants::RGB10_A2UI,
475 constants::RGB16F,
476 constants::RGB16I,
477 constants::RGB16UI,
478 constants::RGB32F,
479 constants::RGB32I,
480 constants::RGB32UI,
481 constants::RGB8,
482 constants::RGB8_SNORM,
483 constants::RGB8I,
484 constants::RGB8UI,
485 constants::RGB9_E5,
486 constants::RGBA16F,
487 constants::RGBA16I,
488 constants::RGBA16UI,
489 constants::RGBA32F,
490 constants::RGBA32I,
491 constants::RGBA32UI,
492 constants::RGBA8,
493 constants::RGBA8_SNORM,
494 constants::RGBA8I,
495 constants::RGBA8UI,
496 constants::SRGB8,
497 constants::SRGB8_ALPHA8,
498 ][..];
499 let empty_color_constrains = &[][..];
500 let extra_color_constraints = match self.webgl_version {
501 WebGLVersion::WebGL1 => empty_color_constrains,
502 WebGLVersion::WebGL2 => webgl2_color_constraints,
503 };
504 let color_constraints = webgl1_color_constraints
505 .iter()
506 .chain(extra_color_constraints.iter());
507
508 let has_c = self.colors.iter().any(|att| att.borrow().is_some());
509 for attachment in self.colors.iter() {
510 let attachment = attachment.borrow();
511 let constraints = color_constraints.clone();
512 if let Err(errnum) =
513 self.check_attachment_constraints(&attachment, constraints, &mut fb_size)
514 {
515 return self.status.set(errnum);
516 }
517 }
518
519 self.size.set(fb_size);
520
521 if has_c || has_z || has_zs || has_s {
522 if self.size.get().is_some_and(|(w, h)| w != 0 && h != 0) {
523 self.status.set(constants::FRAMEBUFFER_COMPLETE);
524 } else {
525 self.status
526 .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
527 }
528 } else {
529 self.status
530 .set(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
531 }
532 }
533
534 pub(crate) fn check_status(&self) -> u32 {
535 #[cfg(feature = "webxr")]
538 if let Some(xr_session) = self.xr_session.get() {
539 return if xr_session.is_outside_raf() {
540 constants::FRAMEBUFFER_UNSUPPORTED
541 } else {
542 constants::FRAMEBUFFER_COMPLETE
543 };
544 }
545
546 self.status.get()
547 }
551
552 pub(crate) fn check_status_for_rendering(&self) -> CompleteForRendering {
553 let result = self.check_status();
554 if result != constants::FRAMEBUFFER_COMPLETE {
555 return CompleteForRendering::Incomplete;
556 }
557
558 #[cfg(feature = "webxr")]
561 if self.xr_session.get().is_some() {
562 return CompleteForRendering::Complete;
563 }
564
565 if self.colors.iter().all(|att| att.borrow().is_none()) {
566 return CompleteForRendering::MissingColorAttachment;
567 }
568
569 if !self.is_initialized.get() {
570 let attachments = [
571 (&self.depth, constants::DEPTH_BUFFER_BIT),
572 (&self.stencil, constants::STENCIL_BUFFER_BIT),
573 (
574 &self.depthstencil,
575 constants::DEPTH_BUFFER_BIT | constants::STENCIL_BUFFER_BIT,
576 ),
577 ];
578 let mut clear_bits = 0;
579 for &(attachment, bits) in &attachments {
580 if let Some(ref att) = *attachment.borrow() {
581 if att.needs_initialization() {
582 att.mark_initialized();
583 clear_bits |= bits;
584 }
585 }
586 }
587 for attachment in self.colors.iter() {
588 if let Some(ref att) = *attachment.borrow() {
589 if att.needs_initialization() {
590 att.mark_initialized();
591 clear_bits |= constants::COLOR_BUFFER_BIT;
592 }
593 }
594 }
595 self.upcast::<WebGLObject>()
596 .context()
597 .initialize_framebuffer(clear_bits);
598 self.is_initialized.set(true);
599 }
600
601 CompleteForRendering::Complete
606 }
607
608 pub(crate) fn renderbuffer(
609 &self,
610 attachment: u32,
611 rb: Option<&WebGLRenderbuffer>,
612 ) -> WebGLResult<()> {
613 self.validate_transparent()?;
616
617 let binding = self
618 .attachment_binding(attachment)
619 .ok_or(WebGLError::InvalidEnum)?;
620
621 let rb_id = match rb {
622 Some(rb) => {
623 if !rb.ever_bound() {
624 return Err(WebGLError::InvalidOperation);
625 }
626 *binding.borrow_mut() =
627 Some(WebGLFramebufferAttachment::Renderbuffer(Dom::from_ref(rb)));
628 rb.attach_to_framebuffer(self);
629 Some(rb.id())
630 },
631
632 _ => None,
633 };
634
635 self.upcast::<WebGLObject>()
636 .context()
637 .send_command(WebGLCommand::FramebufferRenderbuffer(
638 self.target.get().unwrap(),
639 attachment,
640 constants::RENDERBUFFER,
641 rb_id,
642 ));
643
644 if rb.is_none() {
645 self.detach_binding(binding, attachment)?;
646 }
647
648 self.update_status();
649 self.is_initialized.set(false);
650 Ok(())
651 }
652
653 fn detach_binding(
654 &self,
655 binding: &DomRefCell<Option<WebGLFramebufferAttachment>>,
656 attachment: u32,
657 ) -> WebGLResult<()> {
658 self.validate_transparent()?;
661
662 if let Some(att) = &*binding.borrow() {
663 att.detach();
664 }
665 *binding.borrow_mut() = None;
666 if INTERESTING_ATTACHMENT_POINTS.contains(&attachment) {
667 self.reattach_depth_stencil()?;
668 }
669 Ok(())
670 }
671
672 fn attachment_binding(
673 &self,
674 attachment: u32,
675 ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> {
676 match attachment {
677 constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => {
678 let idx = attachment - constants::COLOR_ATTACHMENT0;
679 self.colors.get(idx as usize)
680 },
681 constants::DEPTH_ATTACHMENT => Some(&self.depth),
682 constants::STENCIL_ATTACHMENT => Some(&self.stencil),
683 constants::DEPTH_STENCIL_ATTACHMENT => Some(&self.depthstencil),
684 _ => None,
685 }
686 }
687
688 fn reattach_depth_stencil(&self) -> WebGLResult<()> {
689 self.validate_transparent()?;
692
693 let reattach = |attachment: &WebGLFramebufferAttachment, attachment_point| {
694 let context = self.upcast::<WebGLObject>().context();
695 match *attachment {
696 WebGLFramebufferAttachment::Renderbuffer(ref rb) => {
697 rb.attach_to_framebuffer(self);
698 context.send_command(WebGLCommand::FramebufferRenderbuffer(
699 self.target.get().unwrap(),
700 attachment_point,
701 constants::RENDERBUFFER,
702 Some(rb.id()),
703 ));
704 },
705 WebGLFramebufferAttachment::Texture { ref texture, level } => {
706 texture.attach_to_framebuffer(self);
707 context.send_command(WebGLCommand::FramebufferTexture2D(
708 self.target.get().unwrap(),
709 attachment_point,
710 texture.target().expect("missing texture target"),
711 Some(texture.id()),
712 level,
713 ));
714 },
715 }
716 };
717
718 if let Some(ref depth) = *self.depth.borrow() {
723 reattach(depth, constants::DEPTH_ATTACHMENT);
724 }
725 if let Some(ref stencil) = *self.stencil.borrow() {
726 reattach(stencil, constants::STENCIL_ATTACHMENT);
727 }
728 if let Some(ref depth_stencil) = *self.depthstencil.borrow() {
729 reattach(depth_stencil, constants::DEPTH_STENCIL_ATTACHMENT);
730 }
731 Ok(())
732 }
733
734 pub(crate) fn attachment(&self, attachment: u32) -> Option<WebGLFramebufferAttachmentRoot> {
735 let binding = self.attachment_binding(attachment)?;
736 binding
737 .borrow()
738 .as_ref()
739 .map(WebGLFramebufferAttachment::root)
740 }
741
742 pub(crate) fn texture2d(
743 &self,
744 attachment: u32,
745 textarget: u32,
746 texture: Option<&WebGLTexture>,
747 level: i32,
748 ) -> WebGLResult<()> {
749 self.validate_transparent()?;
752 if let Some(texture) = texture {
753 let is_cube = match textarget {
765 constants::TEXTURE_2D => false,
766
767 constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
768 constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
769 constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
770 constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
771 constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
772 constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,
773
774 _ => return Err(WebGLError::InvalidEnum),
775 };
776
777 match texture.target() {
778 Some(constants::TEXTURE_CUBE_MAP) if is_cube => {},
779 Some(_) if !is_cube => {},
780 _ => return Err(WebGLError::InvalidOperation),
781 }
782
783 let context = self.upcast::<WebGLObject>().context();
784 let max_tex_size = if is_cube {
785 context.limits().max_cube_map_tex_size
786 } else {
787 context.limits().max_tex_size
788 };
789 if level < 0 || level as u32 > log2(max_tex_size) {
790 return Err(WebGLError::InvalidValue);
791 }
792 }
793 self.texture2d_even_if_opaque(attachment, textarget, texture, level)
794 }
795
796 pub(crate) fn texture2d_even_if_opaque(
797 &self,
798 attachment: u32,
799 textarget: u32,
800 texture: Option<&WebGLTexture>,
801 level: i32,
802 ) -> WebGLResult<()> {
803 let binding = self
804 .attachment_binding(attachment)
805 .ok_or(WebGLError::InvalidEnum)?;
806
807 let tex_id = match texture {
808 Some(texture) => {
811 *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
812 texture: Dom::from_ref(texture),
813 level,
814 });
815 texture.attach_to_framebuffer(self);
816
817 Some(texture.id())
818 },
819
820 _ => None,
821 };
822
823 self.upcast::<WebGLObject>()
824 .context()
825 .send_command(WebGLCommand::FramebufferTexture2D(
826 self.target.get().unwrap(),
827 attachment,
828 textarget,
829 tex_id,
830 level,
831 ));
832
833 if texture.is_none() {
834 self.detach_binding(binding, attachment)?;
835 }
836
837 self.update_status();
838 self.is_initialized.set(false);
839 Ok(())
840 }
841
842 pub(crate) fn texture_layer(
843 &self,
844 attachment: u32,
845 texture: Option<&WebGLTexture>,
846 level: i32,
847 layer: i32,
848 ) -> WebGLResult<()> {
849 let binding = self
850 .attachment_binding(attachment)
851 .ok_or(WebGLError::InvalidEnum)?;
852
853 let context = self.upcast::<WebGLObject>().context();
854
855 let tex_id = match texture {
856 Some(texture) => {
857 let (max_level, max_layer) = match texture.target() {
858 Some(constants::TEXTURE_3D) => (
859 log2(context.limits().max_3d_texture_size),
860 context.limits().max_3d_texture_size - 1,
861 ),
862 Some(constants::TEXTURE_2D) => (
863 log2(context.limits().max_tex_size),
864 context.limits().max_array_texture_layers - 1,
865 ),
866 _ => return Err(WebGLError::InvalidOperation),
867 };
868
869 if level < 0 || level as u32 >= max_level {
870 return Err(WebGLError::InvalidValue);
871 }
872 if layer < 0 || layer as u32 >= max_layer {
873 return Err(WebGLError::InvalidValue);
874 }
875
876 *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
877 texture: Dom::from_ref(texture),
878 level,
879 });
880 texture.attach_to_framebuffer(self);
881
882 Some(texture.id())
883 },
884 _ => None,
885 };
886
887 context.send_command(WebGLCommand::FramebufferTextureLayer(
888 self.target.get().unwrap(),
889 attachment,
890 tex_id,
891 level,
892 layer,
893 ));
894 Ok(())
895 }
896
897 fn with_matching_renderbuffers<F>(&self, rb: &WebGLRenderbuffer, mut closure: F)
898 where
899 F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
900 {
901 let rb_id = rb.id();
902 let attachments = [
903 (&self.depth, constants::DEPTH_ATTACHMENT),
904 (&self.stencil, constants::STENCIL_ATTACHMENT),
905 (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
906 ];
907
908 fn has_matching_id(
909 attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
910 target: &WebGLRenderbufferId,
911 ) -> bool {
912 match *attachment.borrow() {
913 Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => {
914 att_rb.id() == *target
915 },
916 _ => false,
917 }
918 }
919
920 for (attachment, name) in &attachments {
921 if has_matching_id(attachment, &rb_id) {
922 closure(attachment, *name);
923 }
924 }
925
926 for (idx, attachment) in self.colors.iter().enumerate() {
927 if has_matching_id(attachment, &rb_id) {
928 let name = constants::COLOR_ATTACHMENT0 + idx as u32;
929 closure(attachment, name);
930 }
931 }
932 }
933
934 fn with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F)
935 where
936 F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
937 {
938 let tex_id = texture.id();
939 let attachments = [
940 (&self.depth, constants::DEPTH_ATTACHMENT),
941 (&self.stencil, constants::STENCIL_ATTACHMENT),
942 (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
943 ];
944
945 fn has_matching_id(
946 attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
947 target: &WebGLTextureId,
948 ) -> bool {
949 matches!(*attachment.borrow(), Some(WebGLFramebufferAttachment::Texture {
950 texture: ref att_texture,
951 ..
952 }) if att_texture.id() == *target)
953 }
954
955 for (attachment, name) in &attachments {
956 if has_matching_id(attachment, &tex_id) {
957 closure(attachment, *name);
958 }
959 }
960
961 for (idx, attachment) in self.colors.iter().enumerate() {
962 if has_matching_id(attachment, &tex_id) {
963 let name = constants::COLOR_ATTACHMENT0 + idx as u32;
964 closure(attachment, name);
965 }
966 }
967 }
968
969 pub(crate) fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) -> WebGLResult<()> {
970 self.validate_transparent()?;
973
974 let mut depth_or_stencil_updated = false;
975 self.with_matching_renderbuffers(rb, |att, name| {
976 depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
977 if let Some(att) = &*att.borrow() {
978 att.detach();
979 }
980 *att.borrow_mut() = None;
981 self.update_status();
982 });
983
984 if depth_or_stencil_updated {
985 self.reattach_depth_stencil()?;
986 }
987 Ok(())
988 }
989
990 pub(crate) fn detach_texture(&self, texture: &WebGLTexture) -> WebGLResult<()> {
991 self.validate_transparent()?;
994
995 let mut depth_or_stencil_updated = false;
996 self.with_matching_textures(texture, |att, name| {
997 depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
998 if let Some(att) = &*att.borrow() {
999 att.detach();
1000 }
1001 *att.borrow_mut() = None;
1002 self.update_status();
1003 });
1004
1005 if depth_or_stencil_updated {
1006 self.reattach_depth_stencil()?;
1007 }
1008 Ok(())
1009 }
1010
1011 pub(crate) fn invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer) {
1012 self.with_matching_renderbuffers(rb, |_att, _| {
1013 self.is_initialized.set(false);
1014 self.update_status();
1015 });
1016 }
1017
1018 pub(crate) fn invalidate_texture(&self, texture: &WebGLTexture) {
1019 self.with_matching_textures(texture, |_att, _name| {
1020 self.update_status();
1021 });
1022 }
1023
1024 pub(crate) fn set_read_buffer(&self, buffer: u32) -> WebGLResult<()> {
1025 let context = self.upcast::<WebGLObject>().context();
1026
1027 match buffer {
1028 constants::NONE => {},
1029 _ if context.valid_color_attachment_enum(buffer) => {},
1030 _ => return Err(WebGLError::InvalidOperation),
1031 };
1032
1033 *self.color_read_buffer.borrow_mut() = buffer;
1034 context.send_command(WebGLCommand::ReadBuffer(buffer));
1035 Ok(())
1036 }
1037
1038 pub(crate) fn set_draw_buffers(&self, buffers: Vec<u32>) -> WebGLResult<()> {
1039 let context = self.upcast::<WebGLObject>().context();
1040
1041 if buffers.len() > context.limits().max_draw_buffers as usize {
1042 return Err(WebGLError::InvalidValue);
1043 }
1044
1045 let enums_valid = buffers
1046 .iter()
1047 .all(|&val| val == constants::NONE || context.valid_color_attachment_enum(val));
1048 if !enums_valid {
1049 return Err(WebGLError::InvalidEnum);
1050 }
1051
1052 let values_valid = buffers.iter().enumerate().all(|(i, &val)| {
1053 val == constants::NONE || val == (constants::COLOR_ATTACHMENT0 + i as u32)
1054 });
1055 if !values_valid {
1056 return Err(WebGLError::InvalidOperation);
1057 }
1058
1059 self.color_draw_buffers.borrow_mut().clone_from(&buffers);
1060 context.send_command(WebGLCommand::DrawBuffers(buffers));
1061 Ok(())
1062 }
1063
1064 pub(crate) fn read_buffer(&self) -> u32 {
1065 *self.color_read_buffer.borrow()
1066 }
1067
1068 pub(crate) fn draw_buffer_i(&self, index: usize) -> u32 {
1069 let buffers = &*self.color_draw_buffers.borrow();
1070 *buffers.get(index).unwrap_or(&constants::NONE)
1071 }
1072
1073 pub(crate) fn target(&self) -> Option<u32> {
1074 self.target.get()
1075 }
1076}
1077
1078static INTERESTING_ATTACHMENT_POINTS: &[u32] = &[
1079 constants::DEPTH_ATTACHMENT,
1080 constants::STENCIL_ATTACHMENT,
1081 constants::DEPTH_STENCIL_ATTACHMENT,
1082];