Skip to main content

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