script/dom/webgl/
webglframebuffer.rs

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