Skip to main content

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 dom_struct::dom_struct;
9#[cfg(feature = "webxr")]
10use euclid::Size2D;
11use js::context::JSContext;
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::reflect_dom_object;
14use script_bindings::weakref::WeakRef;
15use servo_canvas_traits::webgl::{
16    TexFormat, WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLFramebufferId,
17    WebGLRenderbufferId, WebGLResult, WebGLTextureId, WebGLVersion, webgl_channel,
18};
19#[cfg(feature = "webxr")]
20use webxr_api::Viewport;
21
22use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::reflector::DomGlobal;
25#[cfg(feature = "webxr")]
26use crate::dom::bindings::root::MutNullableDom;
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::webgl::webglobject::WebGLObject;
29use crate::dom::webgl::webglrenderbuffer::WebGLRenderbuffer;
30use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
31use crate::dom::webgl::webgltexture::WebGLTexture;
32#[cfg(feature = "webxr")]
33use crate::dom::xrsession::XRSession;
34
35pub(crate) enum CompleteForRendering {
36    Complete,
37    Incomplete,
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: Cell<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: Cell::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        cx: &mut JSContext,
193        context: &WebGLRenderingContext,
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(cx, context, id);
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        cx: &mut JSContext,
207        session: &XRSession,
208        context: &WebGLRenderingContext,
209        size: Size2D<i32, Viewport>,
210    ) -> Option<DomRoot<Self>> {
211        let framebuffer = Self::maybe_new(cx, context)?;
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        cx: &mut JSContext,
220        context: &WebGLRenderingContext,
221        id: WebGLFramebufferId,
222    ) -> DomRoot<Self> {
223        reflect_dom_object(
224            cx,
225            Box::new(WebGLFramebuffer::new_inherited(context, id)),
226            &*context.global(),
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            constraints.all(|c| *c != format)
341        {
342            return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
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        // The color-renderable formats of OpenGL ES 3.0.6 table 3.13, plus those of
443        // EXT_color_buffer_float.
444        let webgl2_color_constraints = &[
445            constants::R8,
446            constants::R8I,
447            constants::R8UI,
448            constants::R16I,
449            constants::R16UI,
450            constants::R32I,
451            constants::R32UI,
452            constants::RG8,
453            constants::RG8I,
454            constants::RG8UI,
455            constants::RG16I,
456            constants::RG16UI,
457            constants::RG32I,
458            constants::RG32UI,
459            constants::RGB8,
460            constants::RGBA8,
461            constants::SRGB8_ALPHA8,
462            constants::RGB10_A2,
463            constants::RGB10_A2UI,
464            constants::RGBA8I,
465            constants::RGBA8UI,
466            constants::RGBA16I,
467            constants::RGBA16UI,
468            constants::RGBA32I,
469            constants::RGBA32UI,
470            // TODO: gate these behind EXT_color_buffer_float once Servo exposes it.
471            constants::R16F,
472            constants::RG16F,
473            constants::RGBA16F,
474            constants::R32F,
475            constants::RG32F,
476            constants::RGBA32F,
477            constants::R11F_G11F_B10F,
478        ][..];
479        let empty_color_constrains = &[][..];
480        let extra_color_constraints = match self.webgl_version {
481            WebGLVersion::WebGL1 => empty_color_constrains,
482            WebGLVersion::WebGL2 => webgl2_color_constraints,
483        };
484        let color_constraints = webgl1_color_constraints
485            .iter()
486            .chain(extra_color_constraints.iter());
487
488        let has_c = self.colors.iter().any(|att| att.borrow().is_some());
489        for attachment in self.colors.iter() {
490            let attachment = attachment.borrow();
491            let constraints = color_constraints.clone();
492            if let Err(errnum) =
493                self.check_attachment_constraints(&attachment, constraints, &mut fb_size)
494            {
495                return self.status.set(errnum);
496            }
497        }
498
499        self.size.set(fb_size);
500
501        if has_c || has_z || has_zs || has_s {
502            if self.size.get().is_some_and(|(w, h)| w != 0 && h != 0) {
503                self.status.set(constants::FRAMEBUFFER_COMPLETE);
504            } else {
505                self.status
506                    .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
507            }
508        } else {
509            self.status
510                .set(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
511        }
512    }
513
514    pub(crate) fn check_status(&self) -> u32 {
515        // For opaque framebuffers, check to see if the XR session is currently processing an rAF
516        // https://immersive-web.github.io/webxr/#opaque-framebuffer
517        #[cfg(feature = "webxr")]
518        if let Some(xr_session) = self.xr_session.get() {
519            return if xr_session.is_outside_raf() {
520                constants::FRAMEBUFFER_UNSUPPORTED
521            } else {
522                constants::FRAMEBUFFER_COMPLETE
523            };
524        }
525
526        self.status.get()
527        // TODO: if a framebuffer has an attachment which is invalid due to
528        // being outside a webxr rAF, should this make the framebuffer incomplete?
529        // https://github.com/immersive-web/layers/issues/196
530    }
531
532    pub(crate) fn has_color_attachment(&self) -> bool {
533        self.colors
534            .iter()
535            .any(|attachment| attachment.borrow().is_some())
536    }
537
538    pub(crate) fn check_status_for_rendering(&self) -> CompleteForRendering {
539        let result = self.check_status();
540        if result != constants::FRAMEBUFFER_COMPLETE {
541            return CompleteForRendering::Incomplete;
542        }
543
544        // XR framebuffers are complete inside an rAF
545        // https://github.com/immersive-web/webxr/issues/854
546        #[cfg(feature = "webxr")]
547        if self.xr_session.get().is_some() {
548            return CompleteForRendering::Complete;
549        }
550
551        if !self.is_initialized.get() {
552            let attachments = [
553                (&self.depth, constants::DEPTH_BUFFER_BIT),
554                (&self.stencil, constants::STENCIL_BUFFER_BIT),
555                (
556                    &self.depthstencil,
557                    constants::DEPTH_BUFFER_BIT | constants::STENCIL_BUFFER_BIT,
558                ),
559            ];
560            let colors = self
561                .colors
562                .iter()
563                .map(|attachment| (attachment, constants::COLOR_BUFFER_BIT));
564            // Collect every bit before marking: in WebGL 2 one image can fill two slots.
565            let mut clear_bits = 0;
566            for (attachment, bits) in attachments.into_iter().chain(colors.clone()) {
567                if let Some(ref att) = *attachment.borrow() &&
568                    att.needs_initialization()
569                {
570                    clear_bits |= bits;
571                }
572            }
573            for (attachment, _) in attachments.into_iter().chain(colors) {
574                if let Some(ref att) = *attachment.borrow() {
575                    att.mark_initialized();
576                }
577            }
578
579            if let Some(context) = self.upcast().context() {
580                context.initialize_framebuffer(clear_bits);
581            }
582
583            self.is_initialized.set(true);
584        }
585
586        // TODO: if a framebuffer has an attachment which is invalid due to
587        // being outside a webxr rAF, should this make the framebuffer incomplete?
588        // https://github.com/immersive-web/layers/issues/196
589
590        CompleteForRendering::Complete
591    }
592
593    /// Clears the uninitialized attachments of this framebuffer, which is bound for reading only.
594    /// Clearing requires a draw binding, so the `draw_framebuffer` binding is restored afterwards.
595    pub(crate) fn initialize_for_reading(&self, draw_framebuffer: Option<&WebGLFramebuffer>) {
596        if self.is_initialized.get() {
597            return;
598        }
599        let bind_draw = |request| {
600            self.upcast().send_command(WebGLCommand::BindFramebuffer(
601                constants::DRAW_FRAMEBUFFER,
602                request,
603            ))
604        };
605        bind_draw(WebGLFramebufferBindingRequest::Explicit(self.id()));
606        self.check_status_for_rendering();
607        bind_draw(
608            draw_framebuffer.map_or(WebGLFramebufferBindingRequest::Default, |fb| {
609                WebGLFramebufferBindingRequest::Explicit(fb.id())
610            }),
611        );
612    }
613
614    pub(crate) fn renderbuffer(
615        &self,
616        attachment: u32,
617        rb: Option<&WebGLRenderbuffer>,
618    ) -> WebGLResult<()> {
619        // Opaque framebuffers cannot have their attachments changed
620        // https://immersive-web.github.io/webxr/#opaque-framebuffer
621        self.validate_transparent()?;
622
623        let binding = self
624            .attachment_binding(attachment)
625            .ok_or(WebGLError::InvalidEnum)?;
626
627        let rb_id = match rb {
628            Some(rb) => {
629                if !rb.ever_bound() {
630                    return Err(WebGLError::InvalidOperation);
631                }
632                *binding.borrow_mut() =
633                    Some(WebGLFramebufferAttachment::Renderbuffer(Dom::from_ref(rb)));
634                self.mirror_alias(binding, attachment);
635                rb.attach_to_framebuffer(self);
636                Some(rb.id())
637            },
638
639            _ => None,
640        };
641
642        self.upcast()
643            .send_command(WebGLCommand::FramebufferRenderbuffer(
644                self.target.get().unwrap(),
645                attachment,
646                constants::RENDERBUFFER,
647                rb_id,
648            ));
649
650        if rb.is_none() {
651            self.detach_binding(binding, attachment)?;
652        }
653
654        self.update_status();
655        self.is_initialized.set(false);
656        Ok(())
657    }
658
659    fn detach_binding(
660        &self,
661        binding: &DomRefCell<Option<WebGLFramebufferAttachment>>,
662        attachment: u32,
663    ) -> WebGLResult<()> {
664        // Opaque framebuffers cannot have their attachments changed
665        // https://immersive-web.github.io/webxr/#opaque-framebuffer
666        self.validate_transparent()?;
667
668        for slot in std::iter::once(binding).chain(self.aliased_binding(attachment)) {
669            if let Some(att) = &*slot.borrow() {
670                att.detach();
671            }
672            *slot.borrow_mut() = None;
673        }
674        if INTERESTING_ATTACHMENT_POINTS.contains(&attachment) {
675            self.reattach_depth_stencil()?;
676        }
677        Ok(())
678    }
679
680    fn attachment_binding(
681        &self,
682        attachment: u32,
683    ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> {
684        match attachment {
685            constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => {
686                let idx = attachment - constants::COLOR_ATTACHMENT0;
687                self.colors.get(idx as usize)
688            },
689            constants::DEPTH_ATTACHMENT => Some(&self.depth),
690            constants::STENCIL_ATTACHMENT => Some(&self.stencil),
691            constants::DEPTH_STENCIL_ATTACHMENT => match self.webgl_version {
692                WebGLVersion::WebGL1 => Some(&self.depthstencil),
693                WebGLVersion::WebGL2 => Some(&self.depth),
694            },
695            _ => None,
696        }
697    }
698
699    /// Returns the stencil slot for WebGL 2's `DEPTH_STENCIL_ATTACHMENT`, which aliases
700    /// `DEPTH_ATTACHMENT` + `STENCIL_ATTACHMENT`, and `None` for any other attachment.
701    fn aliased_binding(
702        &self,
703        attachment: u32,
704    ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> {
705        (self.webgl_version == WebGLVersion::WebGL2 &&
706            attachment == constants::DEPTH_STENCIL_ATTACHMENT)
707            .then_some(&self.stencil)
708    }
709
710    fn mirror_alias(
711        &self,
712        binding: &DomRefCell<Option<WebGLFramebufferAttachment>>,
713        attachment: u32,
714    ) {
715        if let Some(alias) = self.aliased_binding(attachment) {
716            *alias.borrow_mut() = binding.borrow().clone();
717        }
718    }
719
720    fn reattach_depth_stencil(&self) -> WebGLResult<()> {
721        // Opaque framebuffers cannot have their attachments changed
722        // https://immersive-web.github.io/webxr/#opaque-framebuffer
723        self.validate_transparent()?;
724
725        // WebGL 2 attachment points match GL's, so there is nothing to restore.
726        if self.webgl_version == WebGLVersion::WebGL2 {
727            return Ok(());
728        }
729
730        let reattach = |attachment: &WebGLFramebufferAttachment, attachment_point| {
731            let webgl_object = self.upcast();
732            match *attachment {
733                WebGLFramebufferAttachment::Renderbuffer(ref rb) => {
734                    rb.attach_to_framebuffer(self);
735                    webgl_object.send_command(WebGLCommand::FramebufferRenderbuffer(
736                        self.target.get().unwrap(),
737                        attachment_point,
738                        constants::RENDERBUFFER,
739                        Some(rb.id()),
740                    ));
741                },
742                WebGLFramebufferAttachment::Texture { ref texture, level } => {
743                    texture.attach_to_framebuffer(self);
744                    webgl_object.send_command(WebGLCommand::FramebufferTexture2D(
745                        self.target.get().unwrap(),
746                        attachment_point,
747                        texture.target().expect("missing texture target"),
748                        Some(texture.id()),
749                        level,
750                    ));
751                },
752            }
753        };
754
755        // Since the DEPTH_STENCIL attachment causes both the DEPTH and STENCIL
756        // attachments to be overwritten, we need to ensure that we reattach
757        // the DEPTH and STENCIL attachments when any of those attachments
758        // is cleared.
759        if let Some(ref depth) = *self.depth.borrow() {
760            reattach(depth, constants::DEPTH_ATTACHMENT);
761        }
762        if let Some(ref stencil) = *self.stencil.borrow() {
763            reattach(stencil, constants::STENCIL_ATTACHMENT);
764        }
765        if let Some(ref depth_stencil) = *self.depthstencil.borrow() {
766            reattach(depth_stencil, constants::DEPTH_STENCIL_ATTACHMENT);
767        }
768        Ok(())
769    }
770
771    pub(crate) fn attachment(&self, attachment: u32) -> Option<WebGLFramebufferAttachmentRoot> {
772        let binding = self.attachment_binding(attachment)?;
773        binding
774            .borrow()
775            .as_ref()
776            .map(WebGLFramebufferAttachment::root)
777    }
778
779    /// Returns the effective internal format of the image bound to `attachment`: the sized
780    /// format it was created with, or the sized format corresponding to its unsized format
781    /// and type; `None` if no image is bound or script does not track its format.
782    pub(crate) fn attachment_effective_format(&self, attachment: u32) -> Option<TexFormat> {
783        match self.attachment_binding(attachment)?.borrow().as_ref()? {
784            WebGLFramebufferAttachment::Renderbuffer(rb) => {
785                TexFormat::from_gl_constant(rb.internal_format())
786            },
787            WebGLFramebufferAttachment::Texture { texture, level } => {
788                let info = texture.image_info_at_face(0, *level as u32)?;
789                Some(match info.data_type() {
790                    Some(data_type) => info.internal_format().effective_internal_format(data_type),
791                    None => info.internal_format(),
792                })
793            },
794        }
795    }
796
797    pub(crate) fn texture2d(
798        &self,
799        attachment: u32,
800        textarget: u32,
801        texture: Option<&WebGLTexture>,
802        level: i32,
803    ) -> WebGLResult<()> {
804        // Opaque framebuffers cannot have their attachments changed
805        // https://immersive-web.github.io/webxr/#opaque-framebuffer
806        self.validate_transparent()?;
807        if let Some(texture) = texture {
808            //     "If texture is not zero, then texture must either
809            //      name an existing texture object with an target of
810            //      textarget, or texture must name an existing cube
811            //      map texture and textarget must be one of:
812            //      TEXTURE_CUBE_MAP_POSITIVE_X,
813            //      TEXTURE_CUBE_MAP_POSITIVE_Y,
814            //      TEXTURE_CUBE_MAP_POSITIVE_Z,
815            //      TEXTURE_CUBE_MAP_NEGATIVE_X,
816            //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
817            //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
818            //      INVALID_OPERATION is generated."
819            let is_cube = match textarget {
820                constants::TEXTURE_2D => false,
821
822                constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
823                constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
824                constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
825                constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
826                constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
827                constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,
828
829                _ => return Err(WebGLError::InvalidEnum),
830            };
831
832            match texture.target() {
833                Some(constants::TEXTURE_CUBE_MAP) if is_cube => {},
834                Some(_) if !is_cube => {},
835                _ => return Err(WebGLError::InvalidOperation),
836            }
837
838            let Some(context) = self.upcast().context() else {
839                return Err(WebGLError::ContextLost);
840            };
841            let max_tex_size = if is_cube {
842                context.limits().max_cube_map_tex_size
843            } else {
844                context.limits().max_tex_size
845            };
846            if level < 0 || level as u32 > max_tex_size.ilog2() {
847                return Err(WebGLError::InvalidValue);
848            }
849        }
850        self.texture2d_even_if_opaque(attachment, textarget, texture, level)
851    }
852
853    pub(crate) fn texture2d_even_if_opaque(
854        &self,
855        attachment: u32,
856        textarget: u32,
857        texture: Option<&WebGLTexture>,
858        level: i32,
859    ) -> WebGLResult<()> {
860        let binding = self
861            .attachment_binding(attachment)
862            .ok_or(WebGLError::InvalidEnum)?;
863
864        let tex_id = match texture {
865            // Note, from the GLES 2.0.25 spec, page 113:
866            //      "If texture is zero, then textarget and level are ignored."
867            Some(texture) => {
868                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
869                    texture: Dom::from_ref(texture),
870                    level,
871                });
872                self.mirror_alias(binding, attachment);
873                texture.attach_to_framebuffer(self);
874
875                Some(texture.id())
876            },
877
878            _ => None,
879        };
880
881        self.upcast()
882            .send_command(WebGLCommand::FramebufferTexture2D(
883                self.target.get().unwrap(),
884                attachment,
885                textarget,
886                tex_id,
887                level,
888            ));
889
890        if texture.is_none() {
891            self.detach_binding(binding, attachment)?;
892        }
893
894        self.update_status();
895        self.is_initialized.set(false);
896        Ok(())
897    }
898
899    pub(crate) fn texture_layer(
900        &self,
901        attachment: u32,
902        texture: Option<&WebGLTexture>,
903        level: i32,
904        layer: i32,
905    ) -> WebGLResult<()> {
906        let binding = self
907            .attachment_binding(attachment)
908            .ok_or(WebGLError::InvalidEnum)?;
909
910        let Some(context) = self.upcast().context() else {
911            return Err(WebGLError::ContextLost);
912        };
913
914        let tex_id = match texture {
915            Some(texture) => {
916                let (max_level, max_layer) = match texture.target() {
917                    Some(constants::TEXTURE_3D) => (
918                        context.limits().max_3d_texture_size.ilog2(),
919                        context.limits().max_3d_texture_size - 1,
920                    ),
921                    Some(constants::TEXTURE_2D) => (
922                        context.limits().max_tex_size.ilog2(),
923                        context.limits().max_array_texture_layers - 1,
924                    ),
925                    _ => return Err(WebGLError::InvalidOperation),
926                };
927
928                if level < 0 || level as u32 >= max_level {
929                    return Err(WebGLError::InvalidValue);
930                }
931                if layer < 0 || layer as u32 >= max_layer {
932                    return Err(WebGLError::InvalidValue);
933                }
934
935                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
936                    texture: Dom::from_ref(texture),
937                    level,
938                });
939                self.mirror_alias(binding, attachment);
940                texture.attach_to_framebuffer(self);
941
942                Some(texture.id())
943            },
944            _ => None,
945        };
946
947        context.send_command(WebGLCommand::FramebufferTextureLayer(
948            self.target.get().unwrap(),
949            attachment,
950            tex_id,
951            level,
952            layer,
953        ));
954        Ok(())
955    }
956
957    fn with_matching_renderbuffers_id<F>(&self, rb_id: &WebGLRenderbufferId, mut closure: F)
958    where
959        F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
960    {
961        let attachments = [
962            (&self.depth, constants::DEPTH_ATTACHMENT),
963            (&self.stencil, constants::STENCIL_ATTACHMENT),
964            (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
965        ];
966
967        fn has_matching_id(
968            attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
969            target: &WebGLRenderbufferId,
970        ) -> bool {
971            match *attachment.borrow() {
972                Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => {
973                    att_rb.id() == *target
974                },
975                _ => false,
976            }
977        }
978
979        for (attachment, name) in &attachments {
980            if has_matching_id(attachment, rb_id) {
981                closure(attachment, *name);
982            }
983        }
984
985        for (idx, attachment) in self.colors.iter().enumerate() {
986            if has_matching_id(attachment, rb_id) {
987                let name = constants::COLOR_ATTACHMENT0 + idx as u32;
988                closure(attachment, name);
989            }
990        }
991    }
992
993    fn with_matching_textures_id<F>(&self, tex_id: WebGLTextureId, mut closure: F)
994    where
995        F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
996    {
997        let attachments = [
998            (&self.depth, constants::DEPTH_ATTACHMENT),
999            (&self.stencil, constants::STENCIL_ATTACHMENT),
1000            (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
1001        ];
1002
1003        fn has_matching_id(
1004            attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
1005            target: &WebGLTextureId,
1006        ) -> bool {
1007            matches!(*attachment.borrow(), Some(WebGLFramebufferAttachment::Texture {
1008                                     texture: ref att_texture,
1009                                     ..
1010                                }) if att_texture.id() == *target)
1011        }
1012
1013        for (attachment, name) in &attachments {
1014            if has_matching_id(attachment, &tex_id) {
1015                closure(attachment, *name);
1016            }
1017        }
1018
1019        for (idx, attachment) in self.colors.iter().enumerate() {
1020            if has_matching_id(attachment, &tex_id) {
1021                let name = constants::COLOR_ATTACHMENT0 + idx as u32;
1022                closure(attachment, name);
1023            }
1024        }
1025    }
1026
1027    pub(crate) fn detach_renderbuffer_by_id(&self, rb_id: &WebGLRenderbufferId) -> WebGLResult<()> {
1028        // Opaque framebuffers cannot have their attachments changed
1029        // https://immersive-web.github.io/webxr/#opaque-framebuffer
1030        self.validate_transparent()?;
1031
1032        let mut depth_or_stencil_updated = false;
1033        self.with_matching_renderbuffers_id(rb_id, |att, name| {
1034            depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
1035            if let Some(att) = &*att.borrow() {
1036                att.detach();
1037            }
1038            *att.borrow_mut() = None;
1039            self.update_status();
1040        });
1041
1042        if depth_or_stencil_updated {
1043            self.reattach_depth_stencil()?;
1044        }
1045        Ok(())
1046    }
1047
1048    pub(crate) fn detach_texture(&self, tex_id: WebGLTextureId) -> WebGLResult<()> {
1049        // Opaque framebuffers cannot have their attachments changed
1050        // https://immersive-web.github.io/webxr/#opaque-framebuffer
1051        self.validate_transparent()?;
1052
1053        let mut depth_or_stencil_updated = false;
1054        self.with_matching_textures_id(tex_id, |att, name| {
1055            depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
1056            if let Some(att) = &*att.borrow() {
1057                att.detach();
1058            }
1059            *att.borrow_mut() = None;
1060            self.update_status();
1061        });
1062
1063        if depth_or_stencil_updated {
1064            self.reattach_depth_stencil()?;
1065        }
1066        Ok(())
1067    }
1068
1069    pub(crate) fn invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer) {
1070        self.with_matching_renderbuffers_id(&rb.id(), |_att, _| {
1071            self.is_initialized.set(false);
1072            self.update_status();
1073        });
1074    }
1075
1076    pub(crate) fn invalidate_texture(&self, texture: &WebGLTexture) {
1077        self.with_matching_textures_id(texture.id(), |_att, _name| {
1078            self.update_status();
1079        });
1080    }
1081
1082    pub(crate) fn set_read_buffer(&self, buffer: u32) -> WebGLResult<()> {
1083        let Some(context) = self.upcast().context() else {
1084            return Err(WebGLError::ContextLost);
1085        };
1086
1087        match buffer {
1088            constants::NONE => {},
1089            _ if context.valid_color_attachment_enum(buffer) => {},
1090            _ => return Err(WebGLError::InvalidOperation),
1091        };
1092
1093        self.color_read_buffer.set(buffer);
1094        context.send_command(WebGLCommand::ReadBuffer(buffer));
1095        Ok(())
1096    }
1097
1098    pub(crate) fn set_draw_buffers(&self, buffers: Vec<u32>) -> WebGLResult<()> {
1099        let Some(context) = self.upcast().context() else {
1100            return Err(WebGLError::ContextLost);
1101        };
1102
1103        if buffers.len() > context.limits().max_draw_buffers as usize {
1104            return Err(WebGLError::InvalidValue);
1105        }
1106
1107        let enums_valid = buffers
1108            .iter()
1109            .all(|&val| val == constants::NONE || context.valid_color_attachment_enum(val));
1110        if !enums_valid {
1111            return Err(WebGLError::InvalidEnum);
1112        }
1113
1114        let values_valid = buffers.iter().enumerate().all(|(i, &val)| {
1115            val == constants::NONE || val == (constants::COLOR_ATTACHMENT0 + i as u32)
1116        });
1117        if !values_valid {
1118            return Err(WebGLError::InvalidOperation);
1119        }
1120
1121        self.color_draw_buffers.borrow_mut().clone_from(&buffers);
1122        context.send_command(WebGLCommand::DrawBuffers(buffers));
1123        Ok(())
1124    }
1125
1126    pub(crate) fn read_buffer(&self) -> u32 {
1127        self.color_read_buffer.get()
1128    }
1129
1130    pub(crate) fn draw_buffer_i(&self, index: usize) -> u32 {
1131        let buffers = &*self.color_draw_buffers.borrow();
1132        *buffers.get(index).unwrap_or(&constants::NONE)
1133    }
1134
1135    pub(crate) fn target(&self) -> Option<u32> {
1136        self.target.get()
1137    }
1138}
1139
1140static INTERESTING_ATTACHMENT_POINTS: &[u32] = &[
1141    constants::DEPTH_ATTACHMENT,
1142    constants::STENCIL_ATTACHMENT,
1143    constants::DEPTH_STENCIL_ATTACHMENT,
1144];