script/dom/webgl/
webglrenderbuffer.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;
9use script_bindings::weakref::WeakRef;
10use servo_canvas_traits::webgl::{
11    GlType, InternalFormatIntVec, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult,
12    WebGLVersion, webgl_channel,
13};
14
15use crate::dom::bindings::codegen::Bindings::EXTColorBufferHalfFloatBinding::EXTColorBufferHalfFloatConstants;
16use crate::dom::bindings::codegen::Bindings::WEBGLColorBufferFloatBinding::WEBGLColorBufferFloatConstants;
17use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
20use crate::dom::bindings::root::{DomRoot, MutNullableDom};
21use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
22use crate::dom::webgl::webglobject::WebGLObject;
23use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
24use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
25use crate::script_runtime::CanGc;
26
27#[derive(JSTraceable, MallocSizeOf)]
28struct DroppableWebGLRenderbuffer {
29    context: WeakRef<WebGLRenderingContext>,
30    #[no_trace]
31    id: WebGLRenderbufferId,
32    is_deleted: Cell<bool>,
33}
34
35impl DroppableWebGLRenderbuffer {
36    fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
37        if let Some(root) = self.context.root() {
38            let result = root.sender().send(command, capture_webgl_backtrace());
39            if matches!(fallibility, Operation::Infallible) {
40                result.expect("Operation failed");
41            }
42        }
43    }
44
45    fn delete(&self, operation_fallibility: Operation) {
46        if !self.is_deleted.get() {
47            self.is_deleted.set(true);
48
49            /*
50            If a renderbuffer object is deleted while its image is attached to one or more
51            attachment points in a currently bound framebuffer object, then it is as if
52            FramebufferRenderbuffer had been called, with a renderbuffer of zero, for each
53            attachment point to which this image was attached in that framebuffer object.
54            In other words,the renderbuffer image is first detached from all attachment points
55            in that frame-buffer object.
56            - GLES 3.0, 4.4.2.3, "Attaching Renderbuffer Images to a Framebuffer"
57            */
58            if let Some(context) = self.context.root() {
59                if let Some(fb) = context.get_draw_framebuffer_slot().get() {
60                    let _ = fb.detach_renderbuffer_by_id(&self.id);
61                }
62                if let Some(fb) = context.get_read_framebuffer_slot().get() {
63                    let _ = fb.detach_renderbuffer_by_id(&self.id);
64                }
65            }
66
67            self.send_with_fallibility(
68                WebGLCommand::DeleteRenderbuffer(self.id),
69                operation_fallibility,
70            );
71        }
72    }
73}
74
75impl Drop for DroppableWebGLRenderbuffer {
76    fn drop(&mut self) {
77        self.delete(Operation::Fallible);
78    }
79}
80
81#[dom_struct(associated_memory)]
82pub(crate) struct WebGLRenderbuffer {
83    webgl_object: WebGLObject,
84    ever_bound: Cell<bool>,
85    size: Cell<Option<(i32, i32)>>,
86    internal_format: Cell<Option<u32>>,
87    is_initialized: Cell<bool>,
88    attached_framebuffer: MutNullableDom<WebGLFramebuffer>,
89    droppable: DroppableWebGLRenderbuffer,
90}
91
92impl WebGLRenderbuffer {
93    fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self {
94        Self {
95            webgl_object: WebGLObject::new_inherited(context),
96            ever_bound: Cell::new(false),
97            internal_format: Cell::new(None),
98            size: Cell::new(None),
99            is_initialized: Cell::new(false),
100            attached_framebuffer: Default::default(),
101            droppable: DroppableWebGLRenderbuffer {
102                context: WeakRef::new(context),
103                id,
104                is_deleted: Cell::new(false),
105            },
106        }
107    }
108
109    pub(crate) fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
110        let (sender, receiver) = webgl_channel().unwrap();
111        context.send_command(WebGLCommand::CreateRenderbuffer(sender));
112        receiver
113            .recv()
114            .unwrap()
115            .map(|id| WebGLRenderbuffer::new(context, id, CanGc::note()))
116    }
117
118    pub(crate) fn new(
119        context: &WebGLRenderingContext,
120        id: WebGLRenderbufferId,
121        can_gc: CanGc,
122    ) -> DomRoot<Self> {
123        reflect_dom_object(
124            Box::new(WebGLRenderbuffer::new_inherited(context, id)),
125            &*context.global(),
126            can_gc,
127        )
128    }
129}
130
131impl WebGLRenderbuffer {
132    pub(crate) fn id(&self) -> WebGLRenderbufferId {
133        self.droppable.id
134    }
135
136    pub(crate) fn size(&self) -> Option<(i32, i32)> {
137        self.size.get()
138    }
139
140    pub(crate) fn internal_format(&self) -> u32 {
141        self.internal_format.get().unwrap_or(constants::RGBA4)
142    }
143
144    pub(crate) fn mark_initialized(&self) {
145        self.is_initialized.set(true);
146    }
147
148    pub(crate) fn is_initialized(&self) -> bool {
149        self.is_initialized.get()
150    }
151
152    pub(crate) fn bind(&self, target: u32) {
153        self.ever_bound.set(true);
154        self.upcast()
155            .send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id())));
156    }
157
158    pub(crate) fn delete(&self, operation_fallibility: Operation) {
159        self.droppable.delete(operation_fallibility);
160    }
161
162    pub(crate) fn is_deleted(&self) -> bool {
163        self.droppable.is_deleted.get()
164    }
165
166    pub(crate) fn ever_bound(&self) -> bool {
167        self.ever_bound.get()
168    }
169
170    pub(crate) fn storage(
171        &self,
172        api_type: GlType,
173        sample_count: i32,
174        internal_format: u32,
175        width: i32,
176        height: i32,
177    ) -> WebGLResult<()> {
178        let Some(context) = self.upcast().context() else {
179            return Err(WebGLError::ContextLost);
180        };
181
182        let webgl_version = context.webgl_version();
183        let is_gles = api_type == GlType::Gles;
184
185        // Validate the internal_format, and save it for completeness
186        // validation.
187        let actual_format = match internal_format {
188            constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => {
189                internal_format
190            },
191            constants::R8 |
192            constants::R8UI |
193            constants::R8I |
194            constants::R16UI |
195            constants::R16I |
196            constants::R32UI |
197            constants::R32I |
198            constants::RG8 |
199            constants::RG8UI |
200            constants::RG8I |
201            constants::RG16UI |
202            constants::RG16I |
203            constants::RG32UI |
204            constants::RG32I |
205            constants::RGB8 |
206            constants::RGBA8 |
207            constants::SRGB8_ALPHA8 |
208            constants::RGB10_A2 |
209            constants::RGBA8UI |
210            constants::RGBA8I |
211            constants::RGB10_A2UI |
212            constants::RGBA16UI |
213            constants::RGBA16I |
214            constants::RGBA32I |
215            constants::RGBA32UI |
216            constants::DEPTH_COMPONENT24 |
217            constants::DEPTH_COMPONENT32F |
218            constants::DEPTH24_STENCIL8 |
219            constants::DEPTH32F_STENCIL8 => match webgl_version {
220                WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum),
221                _ => internal_format,
222            },
223            // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8
224            constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
225            constants::RGB5_A1 => {
226                // 16-bit RGBA formats are not supported on desktop GL.
227                if is_gles {
228                    constants::RGB5_A1
229                } else {
230                    constants::RGBA8
231                }
232            },
233            constants::RGB565 => {
234                // RGB565 is not supported on desktop GL.
235                if is_gles {
236                    constants::RGB565
237                } else {
238                    constants::RGB8
239                }
240            },
241            EXTColorBufferHalfFloatConstants::RGBA16F_EXT |
242            EXTColorBufferHalfFloatConstants::RGB16F_EXT => {
243                if !context
244                    .extension_manager()
245                    .is_half_float_buffer_renderable()
246                {
247                    return Err(WebGLError::InvalidEnum);
248                }
249                internal_format
250            },
251            WEBGLColorBufferFloatConstants::RGBA32F_EXT => {
252                if !context.extension_manager().is_float_buffer_renderable() {
253                    return Err(WebGLError::InvalidEnum);
254                }
255                internal_format
256            },
257            _ => return Err(WebGLError::InvalidEnum),
258        };
259
260        if webgl_version != WebGLVersion::WebGL1 {
261            let (sender, receiver) = webgl_channel().unwrap();
262            self.upcast()
263                .send_command(WebGLCommand::GetInternalFormatIntVec(
264                    constants::RENDERBUFFER,
265                    internal_format,
266                    InternalFormatIntVec::Samples,
267                    sender,
268                ));
269            let samples = receiver.recv().unwrap();
270            if sample_count < 0 || sample_count > samples.first().cloned().unwrap_or(0) {
271                return Err(WebGLError::InvalidOperation);
272            }
273        }
274
275        self.internal_format.set(Some(internal_format));
276        self.is_initialized.set(false);
277
278        if let Some(fb) = self.attached_framebuffer.get() {
279            fb.update_status();
280        }
281
282        let command = match sample_count {
283            0 => WebGLCommand::RenderbufferStorage(
284                constants::RENDERBUFFER,
285                actual_format,
286                width,
287                height,
288            ),
289            _ => WebGLCommand::RenderbufferStorageMultisample(
290                constants::RENDERBUFFER,
291                sample_count,
292                actual_format,
293                width,
294                height,
295            ),
296        };
297        self.upcast().send_command(command);
298
299        self.size.set(Some((width, height)));
300        Ok(())
301    }
302
303    pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
304        self.attached_framebuffer.set(Some(fb));
305    }
306
307    pub(crate) fn detach_from_framebuffer(&self) {
308        self.attached_framebuffer.set(None);
309    }
310}