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
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    // The attachment points for textures and renderbuffers on this
159    // FBO.
160    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    // Framebuffers for XR keep a reference to the XR session.
168    // https://github.com/immersive-web/webxr/issues/856
169    #[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    // TODO: depth, stencil and alpha
207    // https://github.com/servo/servo/issues/24498
208    #[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            // Update the framebuffer status on binding.  It may have
261            // changed if its attachments were resized or deleted while
262            // we've been unbound.
263            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        // TODO: if a framebuffer has an attachment which is invalid due to
281        // being outside a webxr rAF, should this make the framebuffer invalid?
282        // https://github.com/immersive-web/layers/issues/196
283        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        // Get the size of this attachment.
318        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        // Make sure that, if we've found any other attachment,
336        // that the size matches.
337        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            // From the WebGL 1.0 spec, 6.6 ("Framebuffer Object Attachments"):
364            //
365            //    "In the WebGL API, it is an error to concurrently attach
366            //     renderbuffers to the following combinations of
367            //     attachment points:
368            //
369            //     DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
370            //     STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
371            //     DEPTH_ATTACHMENT + STENCIL_ATTACHMENT
372            //
373            //     If any of the constraints above are violated, then:
374            //
375            //     checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED."
376            WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s),
377
378            // In WebGL 2.0, DEPTH_STENCIL_ATTACHMENT is considered an alias for
379            // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT, i.e., the same image is attached to both DEPTH_ATTACHMENT
380            // and STENCIL_ATTACHMENT, overwriting the original images attached to the two attachment points.
381            // If different images are bound to the depth and stencil attachment points, checkFramebufferStatus
382            // returns FRAMEBUFFER_UNSUPPORTED, and getFramebufferAttachmentParameter with attachment of
383            // DEPTH_STENCIL_ATTACHMENT generates an INVALID_OPERATION error.
384            // -- WebGL 2.0 spec, 4.1.5 Framebuffer Object Attachments
385            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        // For opaque framebuffers, check to see if the XR session is currently processing an rAF
536        // https://immersive-web.github.io/webxr/#opaque-framebuffer
537        #[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        // TODO: if a framebuffer has an attachment which is invalid due to
548        // being outside a webxr rAF, should this make the framebuffer incomplete?
549        // https://github.com/immersive-web/layers/issues/196
550    }
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        // XR framebuffers are complete inside an rAF
559        // https://github.com/immersive-web/webxr/issues/854
560        #[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        // TODO: if a framebuffer has an attachment which is invalid due to
602        // being outside a webxr rAF, should this make the framebuffer incomplete?
603        // https://github.com/immersive-web/layers/issues/196
604
605        CompleteForRendering::Complete
606    }
607
608    pub(crate) fn renderbuffer(
609        &self,
610        attachment: u32,
611        rb: Option<&WebGLRenderbuffer>,
612    ) -> WebGLResult<()> {
613        // Opaque framebuffers cannot have their attachments changed
614        // https://immersive-web.github.io/webxr/#opaque-framebuffer
615        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        // Opaque framebuffers cannot have their attachments changed
659        // https://immersive-web.github.io/webxr/#opaque-framebuffer
660        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        // Opaque framebuffers cannot have their attachments changed
690        // https://immersive-web.github.io/webxr/#opaque-framebuffer
691        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        // Since the DEPTH_STENCIL attachment causes both the DEPTH and STENCIL
719        // attachments to be overwritten, we need to ensure that we reattach
720        // the DEPTH and STENCIL attachments when any of those attachments
721        // is cleared.
722        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        // Opaque framebuffers cannot have their attachments changed
750        // https://immersive-web.github.io/webxr/#opaque-framebuffer
751        self.validate_transparent()?;
752        if let Some(texture) = texture {
753            //     "If texture is not zero, then texture must either
754            //      name an existing texture object with an target of
755            //      textarget, or texture must name an existing cube
756            //      map texture and textarget must be one of:
757            //      TEXTURE_CUBE_MAP_POSITIVE_X,
758            //      TEXTURE_CUBE_MAP_POSITIVE_Y,
759            //      TEXTURE_CUBE_MAP_POSITIVE_Z,
760            //      TEXTURE_CUBE_MAP_NEGATIVE_X,
761            //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
762            //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
763            //      INVALID_OPERATION is generated."
764            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            // Note, from the GLES 2.0.25 spec, page 113:
809            //      "If texture is zero, then textarget and level are ignored."
810            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        // Opaque framebuffers cannot have their attachments changed
971        // https://immersive-web.github.io/webxr/#opaque-framebuffer
972        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        // Opaque framebuffers cannot have their attachments changed
992        // https://immersive-web.github.io/webxr/#opaque-framebuffer
993        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];