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::<WebGLObject>()
264            .context()
265            .send_command(WebGLCommand::BindFramebuffer(
266                target,
267                WebGLFramebufferBindingRequest::Explicit(self.id()),
268            ));
269    }
270
271    pub(crate) fn delete(&self, operation_fallibility: Operation) {
272        self.droppable.delete(operation_fallibility);
273    }
274
275    pub(crate) fn is_deleted(&self) -> bool {
276        // TODO: if a framebuffer has an attachment which is invalid due to
277        // being outside a webxr rAF, should this make the framebuffer invalid?
278        // https://github.com/immersive-web/layers/issues/196
279        self.droppable.is_deleted()
280    }
281
282    pub(crate) fn size(&self) -> Option<(i32, i32)> {
283        self.size.get()
284    }
285
286    pub(crate) fn get_attachment_formats(
287        &self,
288    ) -> WebGLResult<(Option<u32>, Option<u32>, Option<u32>)> {
289        if self.check_status() != constants::FRAMEBUFFER_COMPLETE {
290            return Err(WebGLError::InvalidFramebufferOperation);
291        }
292        let color = match self.attachment(constants::COLOR_ATTACHMENT0) {
293            Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
294            _ => None,
295        };
296        let depth = match self.attachment(constants::DEPTH_ATTACHMENT) {
297            Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
298            _ => None,
299        };
300        let stencil = match self.attachment(constants::STENCIL_ATTACHMENT) {
301            Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => Some(rb.internal_format()),
302            _ => None,
303        };
304        Ok((color, depth, stencil))
305    }
306
307    fn check_attachment_constraints<'a>(
308        &self,
309        attachment: &Option<WebGLFramebufferAttachment>,
310        mut constraints: impl Iterator<Item = &'a u32>,
311        fb_size: &mut Option<(i32, i32)>,
312    ) -> Result<(), u32> {
313        // Get the size of this attachment.
314        let (format, size) = match attachment {
315            Some(WebGLFramebufferAttachment::Renderbuffer(att_rb)) => {
316                (Some(att_rb.internal_format()), att_rb.size())
317            },
318            Some(WebGLFramebufferAttachment::Texture {
319                texture: att_tex,
320                level,
321            }) => match att_tex.image_info_at_face(0, *level as u32) {
322                Some(info) => (
323                    Some(info.internal_format().as_gl_constant()),
324                    Some((info.width() as i32, info.height() as i32)),
325                ),
326                None => return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
327            },
328            None => (None, None),
329        };
330
331        // Make sure that, if we've found any other attachment,
332        // that the size matches.
333        if size.is_some() {
334            if fb_size.is_some() && size != *fb_size {
335                return Err(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
336            } else {
337                *fb_size = size;
338            }
339        }
340
341        if let Some(format) = format {
342            if constraints.all(|c| *c != format) {
343                return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
344            }
345        }
346
347        Ok(())
348    }
349
350    pub(crate) fn update_status(&self) {
351        let z = self.depth.borrow();
352        let s = self.stencil.borrow();
353        let zs = self.depthstencil.borrow();
354        let has_z = z.is_some();
355        let has_s = s.is_some();
356        let has_zs = zs.is_some();
357
358        let is_supported = match self.webgl_version {
359            // From the WebGL 1.0 spec, 6.6 ("Framebuffer Object Attachments"):
360            //
361            //    "In the WebGL API, it is an error to concurrently attach
362            //     renderbuffers to the following combinations of
363            //     attachment points:
364            //
365            //     DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
366            //     STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
367            //     DEPTH_ATTACHMENT + STENCIL_ATTACHMENT
368            //
369            //     If any of the constraints above are violated, then:
370            //
371            //     checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED."
372            WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s),
373
374            // In WebGL 2.0, DEPTH_STENCIL_ATTACHMENT is considered an alias for
375            // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT, i.e., the same image is attached to both DEPTH_ATTACHMENT
376            // and STENCIL_ATTACHMENT, overwriting the original images attached to the two attachment points.
377            // If different images are bound to the depth and stencil attachment points, checkFramebufferStatus
378            // returns FRAMEBUFFER_UNSUPPORTED, and getFramebufferAttachmentParameter with attachment of
379            // DEPTH_STENCIL_ATTACHMENT generates an INVALID_OPERATION error.
380            // -- WebGL 2.0 spec, 4.1.5 Framebuffer Object Attachments
381            WebGLVersion::WebGL2 => {
382                use WebGLFramebufferAttachment::{Renderbuffer, Texture};
383                match (&*z, &*s) {
384                    (Some(Renderbuffer(a)), Some(Renderbuffer(b))) => a.id() == b.id(),
385                    (Some(Texture { texture: a, .. }), Some(Texture { texture: b, .. })) => {
386                        a.id() == b.id()
387                    },
388                    _ => !has_z || !has_s,
389                }
390            },
391        };
392        if !is_supported {
393            return self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
394        }
395
396        let mut fb_size = None;
397
398        let attachments = [&*z, &*s, &*zs];
399        let webgl1_attachment_constraints = &[
400            &[
401                constants::DEPTH_COMPONENT16,
402                constants::DEPTH_COMPONENT24,
403                constants::DEPTH_COMPONENT32F,
404                constants::DEPTH24_STENCIL8,
405                constants::DEPTH32F_STENCIL8,
406            ][..],
407            &[
408                constants::STENCIL_INDEX8,
409                constants::DEPTH24_STENCIL8,
410                constants::DEPTH32F_STENCIL8,
411            ][..],
412            &[constants::DEPTH_STENCIL][..],
413        ];
414        let webgl2_attachment_constraints = &[
415            &[constants::DEPTH_STENCIL][..],
416            &[constants::DEPTH_STENCIL][..],
417            &[][..],
418        ];
419        let empty_attachment_constrains = &[&[][..], &[][..], &[][..]];
420        let extra_attachment_constraints = match self.webgl_version {
421            WebGLVersion::WebGL1 => empty_attachment_constrains,
422            WebGLVersion::WebGL2 => webgl2_attachment_constraints,
423        };
424        let attachment_constraints = webgl1_attachment_constraints
425            .iter()
426            .zip(extra_attachment_constraints.iter())
427            .map(|(a, b)| a.iter().chain(b.iter()));
428
429        for (attachment, constraints) in attachments.iter().zip(attachment_constraints) {
430            if let Err(errnum) =
431                self.check_attachment_constraints(attachment, constraints, &mut fb_size)
432            {
433                return self.status.set(errnum);
434            }
435        }
436
437        let webgl1_color_constraints = &[
438            constants::RGB,
439            constants::RGB565,
440            constants::RGB5_A1,
441            constants::RGBA,
442            constants::RGBA4,
443        ][..];
444        let webgl2_color_constraints = &[
445            constants::ALPHA,
446            constants::LUMINANCE,
447            constants::LUMINANCE_ALPHA,
448            constants::R11F_G11F_B10F,
449            constants::R16F,
450            constants::R16I,
451            constants::R16UI,
452            constants::R32F,
453            constants::R32I,
454            constants::R32UI,
455            constants::R8,
456            constants::R8_SNORM,
457            constants::R8I,
458            constants::R8UI,
459            constants::RG16F,
460            constants::RG16I,
461            constants::RG16UI,
462            constants::RG32F,
463            constants::RG32I,
464            constants::RG32UI,
465            constants::RG8,
466            constants::RG8_SNORM,
467            constants::RG8I,
468            constants::RG8UI,
469            constants::RGB10_A2,
470            constants::RGB10_A2UI,
471            constants::RGB16F,
472            constants::RGB16I,
473            constants::RGB16UI,
474            constants::RGB32F,
475            constants::RGB32I,
476            constants::RGB32UI,
477            constants::RGB8,
478            constants::RGB8_SNORM,
479            constants::RGB8I,
480            constants::RGB8UI,
481            constants::RGB9_E5,
482            constants::RGBA16F,
483            constants::RGBA16I,
484            constants::RGBA16UI,
485            constants::RGBA32F,
486            constants::RGBA32I,
487            constants::RGBA32UI,
488            constants::RGBA8,
489            constants::RGBA8_SNORM,
490            constants::RGBA8I,
491            constants::RGBA8UI,
492            constants::SRGB8,
493            constants::SRGB8_ALPHA8,
494        ][..];
495        let empty_color_constrains = &[][..];
496        let extra_color_constraints = match self.webgl_version {
497            WebGLVersion::WebGL1 => empty_color_constrains,
498            WebGLVersion::WebGL2 => webgl2_color_constraints,
499        };
500        let color_constraints = webgl1_color_constraints
501            .iter()
502            .chain(extra_color_constraints.iter());
503
504        let has_c = self.colors.iter().any(|att| att.borrow().is_some());
505        for attachment in self.colors.iter() {
506            let attachment = attachment.borrow();
507            let constraints = color_constraints.clone();
508            if let Err(errnum) =
509                self.check_attachment_constraints(&attachment, constraints, &mut fb_size)
510            {
511                return self.status.set(errnum);
512            }
513        }
514
515        self.size.set(fb_size);
516
517        if has_c || has_z || has_zs || has_s {
518            if self.size.get().is_some_and(|(w, h)| w != 0 && h != 0) {
519                self.status.set(constants::FRAMEBUFFER_COMPLETE);
520            } else {
521                self.status
522                    .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
523            }
524        } else {
525            self.status
526                .set(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
527        }
528    }
529
530    pub(crate) fn check_status(&self) -> u32 {
531        // For opaque framebuffers, check to see if the XR session is currently processing an rAF
532        // https://immersive-web.github.io/webxr/#opaque-framebuffer
533        #[cfg(feature = "webxr")]
534        if let Some(xr_session) = self.xr_session.get() {
535            return if xr_session.is_outside_raf() {
536                constants::FRAMEBUFFER_UNSUPPORTED
537            } else {
538                constants::FRAMEBUFFER_COMPLETE
539            };
540        }
541
542        self.status.get()
543        // TODO: if a framebuffer has an attachment which is invalid due to
544        // being outside a webxr rAF, should this make the framebuffer incomplete?
545        // https://github.com/immersive-web/layers/issues/196
546    }
547
548    pub(crate) fn check_status_for_rendering(&self) -> CompleteForRendering {
549        let result = self.check_status();
550        if result != constants::FRAMEBUFFER_COMPLETE {
551            return CompleteForRendering::Incomplete;
552        }
553
554        // XR framebuffers are complete inside an rAF
555        // https://github.com/immersive-web/webxr/issues/854
556        #[cfg(feature = "webxr")]
557        if self.xr_session.get().is_some() {
558            return CompleteForRendering::Complete;
559        }
560
561        if self.colors.iter().all(|att| att.borrow().is_none()) {
562            return CompleteForRendering::MissingColorAttachment;
563        }
564
565        if !self.is_initialized.get() {
566            let attachments = [
567                (&self.depth, constants::DEPTH_BUFFER_BIT),
568                (&self.stencil, constants::STENCIL_BUFFER_BIT),
569                (
570                    &self.depthstencil,
571                    constants::DEPTH_BUFFER_BIT | constants::STENCIL_BUFFER_BIT,
572                ),
573            ];
574            let mut clear_bits = 0;
575            for &(attachment, bits) in &attachments {
576                if let Some(ref att) = *attachment.borrow() {
577                    if att.needs_initialization() {
578                        att.mark_initialized();
579                        clear_bits |= bits;
580                    }
581                }
582            }
583            for attachment in self.colors.iter() {
584                if let Some(ref att) = *attachment.borrow() {
585                    if att.needs_initialization() {
586                        att.mark_initialized();
587                        clear_bits |= constants::COLOR_BUFFER_BIT;
588                    }
589                }
590            }
591            self.upcast::<WebGLObject>()
592                .context()
593                .initialize_framebuffer(clear_bits);
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::<WebGLObject>()
632            .context()
633            .send_command(WebGLCommand::FramebufferRenderbuffer(
634                self.target.get().unwrap(),
635                attachment,
636                constants::RENDERBUFFER,
637                rb_id,
638            ));
639
640        if rb.is_none() {
641            self.detach_binding(binding, attachment)?;
642        }
643
644        self.update_status();
645        self.is_initialized.set(false);
646        Ok(())
647    }
648
649    fn detach_binding(
650        &self,
651        binding: &DomRefCell<Option<WebGLFramebufferAttachment>>,
652        attachment: u32,
653    ) -> WebGLResult<()> {
654        // Opaque framebuffers cannot have their attachments changed
655        // https://immersive-web.github.io/webxr/#opaque-framebuffer
656        self.validate_transparent()?;
657
658        if let Some(att) = &*binding.borrow() {
659            att.detach();
660        }
661        *binding.borrow_mut() = None;
662        if INTERESTING_ATTACHMENT_POINTS.contains(&attachment) {
663            self.reattach_depth_stencil()?;
664        }
665        Ok(())
666    }
667
668    fn attachment_binding(
669        &self,
670        attachment: u32,
671    ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> {
672        match attachment {
673            constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => {
674                let idx = attachment - constants::COLOR_ATTACHMENT0;
675                self.colors.get(idx as usize)
676            },
677            constants::DEPTH_ATTACHMENT => Some(&self.depth),
678            constants::STENCIL_ATTACHMENT => Some(&self.stencil),
679            constants::DEPTH_STENCIL_ATTACHMENT => Some(&self.depthstencil),
680            _ => None,
681        }
682    }
683
684    fn reattach_depth_stencil(&self) -> WebGLResult<()> {
685        // Opaque framebuffers cannot have their attachments changed
686        // https://immersive-web.github.io/webxr/#opaque-framebuffer
687        self.validate_transparent()?;
688
689        let reattach = |attachment: &WebGLFramebufferAttachment, attachment_point| {
690            let context = self.upcast::<WebGLObject>().context();
691            match *attachment {
692                WebGLFramebufferAttachment::Renderbuffer(ref rb) => {
693                    rb.attach_to_framebuffer(self);
694                    context.send_command(WebGLCommand::FramebufferRenderbuffer(
695                        self.target.get().unwrap(),
696                        attachment_point,
697                        constants::RENDERBUFFER,
698                        Some(rb.id()),
699                    ));
700                },
701                WebGLFramebufferAttachment::Texture { ref texture, level } => {
702                    texture.attach_to_framebuffer(self);
703                    context.send_command(WebGLCommand::FramebufferTexture2D(
704                        self.target.get().unwrap(),
705                        attachment_point,
706                        texture.target().expect("missing texture target"),
707                        Some(texture.id()),
708                        level,
709                    ));
710                },
711            }
712        };
713
714        // Since the DEPTH_STENCIL attachment causes both the DEPTH and STENCIL
715        // attachments to be overwritten, we need to ensure that we reattach
716        // the DEPTH and STENCIL attachments when any of those attachments
717        // is cleared.
718        if let Some(ref depth) = *self.depth.borrow() {
719            reattach(depth, constants::DEPTH_ATTACHMENT);
720        }
721        if let Some(ref stencil) = *self.stencil.borrow() {
722            reattach(stencil, constants::STENCIL_ATTACHMENT);
723        }
724        if let Some(ref depth_stencil) = *self.depthstencil.borrow() {
725            reattach(depth_stencil, constants::DEPTH_STENCIL_ATTACHMENT);
726        }
727        Ok(())
728    }
729
730    pub(crate) fn attachment(&self, attachment: u32) -> Option<WebGLFramebufferAttachmentRoot> {
731        let binding = self.attachment_binding(attachment)?;
732        binding
733            .borrow()
734            .as_ref()
735            .map(WebGLFramebufferAttachment::root)
736    }
737
738    pub(crate) fn texture2d(
739        &self,
740        attachment: u32,
741        textarget: u32,
742        texture: Option<&WebGLTexture>,
743        level: i32,
744    ) -> WebGLResult<()> {
745        // Opaque framebuffers cannot have their attachments changed
746        // https://immersive-web.github.io/webxr/#opaque-framebuffer
747        self.validate_transparent()?;
748        if let Some(texture) = texture {
749            //     "If texture is not zero, then texture must either
750            //      name an existing texture object with an target of
751            //      textarget, or texture must name an existing cube
752            //      map texture and textarget must be one of:
753            //      TEXTURE_CUBE_MAP_POSITIVE_X,
754            //      TEXTURE_CUBE_MAP_POSITIVE_Y,
755            //      TEXTURE_CUBE_MAP_POSITIVE_Z,
756            //      TEXTURE_CUBE_MAP_NEGATIVE_X,
757            //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
758            //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
759            //      INVALID_OPERATION is generated."
760            let is_cube = match textarget {
761                constants::TEXTURE_2D => false,
762
763                constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
764                constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
765                constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
766                constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
767                constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
768                constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,
769
770                _ => return Err(WebGLError::InvalidEnum),
771            };
772
773            match texture.target() {
774                Some(constants::TEXTURE_CUBE_MAP) if is_cube => {},
775                Some(_) if !is_cube => {},
776                _ => return Err(WebGLError::InvalidOperation),
777            }
778
779            let context = self.upcast::<WebGLObject>().context();
780            let max_tex_size = if is_cube {
781                context.limits().max_cube_map_tex_size
782            } else {
783                context.limits().max_tex_size
784            };
785            if level < 0 || level as u32 > max_tex_size.ilog2() {
786                return Err(WebGLError::InvalidValue);
787            }
788        }
789        self.texture2d_even_if_opaque(attachment, textarget, texture, level)
790    }
791
792    pub(crate) fn texture2d_even_if_opaque(
793        &self,
794        attachment: u32,
795        textarget: u32,
796        texture: Option<&WebGLTexture>,
797        level: i32,
798    ) -> WebGLResult<()> {
799        let binding = self
800            .attachment_binding(attachment)
801            .ok_or(WebGLError::InvalidEnum)?;
802
803        let tex_id = match texture {
804            // Note, from the GLES 2.0.25 spec, page 113:
805            //      "If texture is zero, then textarget and level are ignored."
806            Some(texture) => {
807                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
808                    texture: Dom::from_ref(texture),
809                    level,
810                });
811                texture.attach_to_framebuffer(self);
812
813                Some(texture.id())
814            },
815
816            _ => None,
817        };
818
819        self.upcast::<WebGLObject>()
820            .context()
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 context = self.upcast::<WebGLObject>().context();
850
851        let tex_id = match texture {
852            Some(texture) => {
853                let (max_level, max_layer) = match texture.target() {
854                    Some(constants::TEXTURE_3D) => (
855                        context.limits().max_3d_texture_size.ilog2(),
856                        context.limits().max_3d_texture_size - 1,
857                    ),
858                    Some(constants::TEXTURE_2D) => (
859                        context.limits().max_tex_size.ilog2(),
860                        context.limits().max_array_texture_layers - 1,
861                    ),
862                    _ => return Err(WebGLError::InvalidOperation),
863                };
864
865                if level < 0 || level as u32 >= max_level {
866                    return Err(WebGLError::InvalidValue);
867                }
868                if layer < 0 || layer as u32 >= max_layer {
869                    return Err(WebGLError::InvalidValue);
870                }
871
872                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
873                    texture: Dom::from_ref(texture),
874                    level,
875                });
876                texture.attach_to_framebuffer(self);
877
878                Some(texture.id())
879            },
880            _ => None,
881        };
882
883        context.send_command(WebGLCommand::FramebufferTextureLayer(
884            self.target.get().unwrap(),
885            attachment,
886            tex_id,
887            level,
888            layer,
889        ));
890        Ok(())
891    }
892
893    fn with_matching_renderbuffers<F>(&self, rb: &WebGLRenderbuffer, mut closure: F)
894    where
895        F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
896    {
897        let rb_id = rb.id();
898        let attachments = [
899            (&self.depth, constants::DEPTH_ATTACHMENT),
900            (&self.stencil, constants::STENCIL_ATTACHMENT),
901            (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
902        ];
903
904        fn has_matching_id(
905            attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
906            target: &WebGLRenderbufferId,
907        ) -> bool {
908            match *attachment.borrow() {
909                Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => {
910                    att_rb.id() == *target
911                },
912                _ => false,
913            }
914        }
915
916        for (attachment, name) in &attachments {
917            if has_matching_id(attachment, &rb_id) {
918                closure(attachment, *name);
919            }
920        }
921
922        for (idx, attachment) in self.colors.iter().enumerate() {
923            if has_matching_id(attachment, &rb_id) {
924                let name = constants::COLOR_ATTACHMENT0 + idx as u32;
925                closure(attachment, name);
926            }
927        }
928    }
929
930    fn with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F)
931    where
932        F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32),
933    {
934        let tex_id = texture.id();
935        let attachments = [
936            (&self.depth, constants::DEPTH_ATTACHMENT),
937            (&self.stencil, constants::STENCIL_ATTACHMENT),
938            (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT),
939        ];
940
941        fn has_matching_id(
942            attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>,
943            target: &WebGLTextureId,
944        ) -> bool {
945            matches!(*attachment.borrow(), Some(WebGLFramebufferAttachment::Texture {
946                                     texture: ref att_texture,
947                                     ..
948                                }) if att_texture.id() == *target)
949        }
950
951        for (attachment, name) in &attachments {
952            if has_matching_id(attachment, &tex_id) {
953                closure(attachment, *name);
954            }
955        }
956
957        for (idx, attachment) in self.colors.iter().enumerate() {
958            if has_matching_id(attachment, &tex_id) {
959                let name = constants::COLOR_ATTACHMENT0 + idx as u32;
960                closure(attachment, name);
961            }
962        }
963    }
964
965    pub(crate) fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) -> WebGLResult<()> {
966        // Opaque framebuffers cannot have their attachments changed
967        // https://immersive-web.github.io/webxr/#opaque-framebuffer
968        self.validate_transparent()?;
969
970        let mut depth_or_stencil_updated = false;
971        self.with_matching_renderbuffers(rb, |att, name| {
972            depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
973            if let Some(att) = &*att.borrow() {
974                att.detach();
975            }
976            *att.borrow_mut() = None;
977            self.update_status();
978        });
979
980        if depth_or_stencil_updated {
981            self.reattach_depth_stencil()?;
982        }
983        Ok(())
984    }
985
986    pub(crate) fn detach_texture(&self, texture: &WebGLTexture) -> WebGLResult<()> {
987        // Opaque framebuffers cannot have their attachments changed
988        // https://immersive-web.github.io/webxr/#opaque-framebuffer
989        self.validate_transparent()?;
990
991        let mut depth_or_stencil_updated = false;
992        self.with_matching_textures(texture, |att, name| {
993            depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name);
994            if let Some(att) = &*att.borrow() {
995                att.detach();
996            }
997            *att.borrow_mut() = None;
998            self.update_status();
999        });
1000
1001        if depth_or_stencil_updated {
1002            self.reattach_depth_stencil()?;
1003        }
1004        Ok(())
1005    }
1006
1007    pub(crate) fn invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer) {
1008        self.with_matching_renderbuffers(rb, |_att, _| {
1009            self.is_initialized.set(false);
1010            self.update_status();
1011        });
1012    }
1013
1014    pub(crate) fn invalidate_texture(&self, texture: &WebGLTexture) {
1015        self.with_matching_textures(texture, |_att, _name| {
1016            self.update_status();
1017        });
1018    }
1019
1020    pub(crate) fn set_read_buffer(&self, buffer: u32) -> WebGLResult<()> {
1021        let context = self.upcast::<WebGLObject>().context();
1022
1023        match buffer {
1024            constants::NONE => {},
1025            _ if context.valid_color_attachment_enum(buffer) => {},
1026            _ => return Err(WebGLError::InvalidOperation),
1027        };
1028
1029        *self.color_read_buffer.borrow_mut() = buffer;
1030        context.send_command(WebGLCommand::ReadBuffer(buffer));
1031        Ok(())
1032    }
1033
1034    pub(crate) fn set_draw_buffers(&self, buffers: Vec<u32>) -> WebGLResult<()> {
1035        let context = self.upcast::<WebGLObject>().context();
1036
1037        if buffers.len() > context.limits().max_draw_buffers as usize {
1038            return Err(WebGLError::InvalidValue);
1039        }
1040
1041        let enums_valid = buffers
1042            .iter()
1043            .all(|&val| val == constants::NONE || context.valid_color_attachment_enum(val));
1044        if !enums_valid {
1045            return Err(WebGLError::InvalidEnum);
1046        }
1047
1048        let values_valid = buffers.iter().enumerate().all(|(i, &val)| {
1049            val == constants::NONE || val == (constants::COLOR_ATTACHMENT0 + i as u32)
1050        });
1051        if !values_valid {
1052            return Err(WebGLError::InvalidOperation);
1053        }
1054
1055        self.color_draw_buffers.borrow_mut().clone_from(&buffers);
1056        context.send_command(WebGLCommand::DrawBuffers(buffers));
1057        Ok(())
1058    }
1059
1060    pub(crate) fn read_buffer(&self) -> u32 {
1061        *self.color_read_buffer.borrow()
1062    }
1063
1064    pub(crate) fn draw_buffer_i(&self, index: usize) -> u32 {
1065        let buffers = &*self.color_draw_buffers.borrow();
1066        *buffers.get(index).unwrap_or(&constants::NONE)
1067    }
1068
1069    pub(crate) fn target(&self) -> Option<u32> {
1070        self.target.get()
1071    }
1072}
1073
1074static INTERESTING_ATTACHMENT_POINTS: &[u32] = &[
1075    constants::DEPTH_ATTACHMENT,
1076    constants::STENCIL_ATTACHMENT,
1077    constants::DEPTH_STENCIL_ATTACHMENT,
1078];