1use 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 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 #[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 #[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 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 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 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 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 WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s),
371
372 WebGLVersion::WebGL2 => {
380 use WebGLFramebufferAttachment::{Renderbuffer, Texture};
381 match (&*z, &*s) {
382 (Some(Renderbuffer(a)), Some(Renderbuffer(b))) => a.id() == b.id(),
383 (Some(Texture { texture: a, .. }), Some(Texture { texture: b, .. })) => {
384 a.id() == b.id()
385 },
386 _ => !has_z || !has_s,
387 }
388 },
389 };
390 if !is_supported {
391 return self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
392 }
393
394 let mut fb_size = None;
395
396 let attachments = [&*z, &*s, &*zs];
397 let webgl1_attachment_constraints = &[
398 &[
399 constants::DEPTH_COMPONENT16,
400 constants::DEPTH_COMPONENT24,
401 constants::DEPTH_COMPONENT32F,
402 constants::DEPTH24_STENCIL8,
403 constants::DEPTH32F_STENCIL8,
404 ][..],
405 &[
406 constants::STENCIL_INDEX8,
407 constants::DEPTH24_STENCIL8,
408 constants::DEPTH32F_STENCIL8,
409 ][..],
410 &[constants::DEPTH_STENCIL][..],
411 ];
412 let webgl2_attachment_constraints = &[
413 &[constants::DEPTH_STENCIL][..],
414 &[constants::DEPTH_STENCIL][..],
415 &[][..],
416 ];
417 let empty_attachment_constrains = &[&[][..], &[][..], &[][..]];
418 let extra_attachment_constraints = match self.webgl_version {
419 WebGLVersion::WebGL1 => empty_attachment_constrains,
420 WebGLVersion::WebGL2 => webgl2_attachment_constraints,
421 };
422 let attachment_constraints = webgl1_attachment_constraints
423 .iter()
424 .zip(extra_attachment_constraints.iter())
425 .map(|(a, b)| a.iter().chain(b.iter()));
426
427 for (attachment, constraints) in attachments.iter().zip(attachment_constraints) {
428 if let Err(errnum) =
429 self.check_attachment_constraints(attachment, constraints, &mut fb_size)
430 {
431 return self.status.set(errnum);
432 }
433 }
434
435 let webgl1_color_constraints = &[
436 constants::RGB,
437 constants::RGB565,
438 constants::RGB5_A1,
439 constants::RGBA,
440 constants::RGBA4,
441 ][..];
442 let webgl2_color_constraints = &[
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 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 #[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 }
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 #[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 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 CompleteForRendering::Complete
591 }
592
593 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 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 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 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 self.validate_transparent()?;
724
725 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 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 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 self.validate_transparent()?;
807 if let Some(texture) = texture {
808 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 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 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 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];