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 js::context::JSContext;
10use script_bindings::reflector::reflect_dom_object_with_cx;
11use script_bindings::weakref::WeakRef;
12use servo_canvas_traits::webgl::{
13    GlType, InternalFormatIntVec, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult,
14    WebGLVersion, webgl_channel,
15};
16
17use crate::dom::bindings::codegen::Bindings::EXTColorBufferHalfFloatBinding::EXTColorBufferHalfFloatConstants;
18use crate::dom::bindings::codegen::Bindings::WEBGLColorBufferFloatBinding::WEBGLColorBufferFloatConstants;
19use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::{DomRoot, MutNullableDom};
23use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
24use crate::dom::webgl::webglobject::WebGLObject;
25use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
26use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
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(
111        cx: &mut JSContext,
112        context: &WebGLRenderingContext,
113    ) -> Option<DomRoot<Self>> {
114        let (sender, receiver) = webgl_channel().unwrap();
115        context.send_command(WebGLCommand::CreateRenderbuffer(sender));
116        receiver
117            .recv()
118            .unwrap()
119            .map(|id| WebGLRenderbuffer::new(cx, context, id))
120    }
121
122    pub(crate) fn new(
123        cx: &mut JSContext,
124        context: &WebGLRenderingContext,
125        id: WebGLRenderbufferId,
126    ) -> DomRoot<Self> {
127        reflect_dom_object_with_cx(
128            Box::new(WebGLRenderbuffer::new_inherited(context, id)),
129            &*context.global(),
130            cx,
131        )
132    }
133}
134
135impl WebGLRenderbuffer {
136    pub(crate) fn id(&self) -> WebGLRenderbufferId {
137        self.droppable.id
138    }
139
140    pub(crate) fn size(&self) -> Option<(i32, i32)> {
141        self.size.get()
142    }
143
144    pub(crate) fn internal_format(&self) -> u32 {
145        self.internal_format.get().unwrap_or(constants::RGBA4)
146    }
147
148    pub(crate) fn mark_initialized(&self) {
149        self.is_initialized.set(true);
150    }
151
152    pub(crate) fn is_initialized(&self) -> bool {
153        self.is_initialized.get()
154    }
155
156    pub(crate) fn bind(&self, target: u32) {
157        self.ever_bound.set(true);
158        self.upcast()
159            .send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id())));
160    }
161
162    pub(crate) fn delete(&self, operation_fallibility: Operation) {
163        self.droppable.delete(operation_fallibility);
164    }
165
166    pub(crate) fn is_deleted(&self) -> bool {
167        self.droppable.is_deleted.get()
168    }
169
170    pub(crate) fn ever_bound(&self) -> bool {
171        self.ever_bound.get()
172    }
173
174    pub(crate) fn storage(
175        &self,
176        api_type: GlType,
177        sample_count: i32,
178        internal_format: u32,
179        width: i32,
180        height: i32,
181    ) -> WebGLResult<()> {
182        let Some(context) = self.upcast().context() else {
183            return Err(WebGLError::ContextLost);
184        };
185
186        let webgl_version = context.webgl_version();
187        let is_gles = api_type == GlType::Gles;
188
189        // Validate the internal_format, and save it for completeness
190        // validation.
191        let actual_format = match internal_format {
192            constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => {
193                internal_format
194            },
195            constants::R8 |
196            constants::R8UI |
197            constants::R8I |
198            constants::R16UI |
199            constants::R16I |
200            constants::R32UI |
201            constants::R32I |
202            constants::RG8 |
203            constants::RG8UI |
204            constants::RG8I |
205            constants::RG16UI |
206            constants::RG16I |
207            constants::RG32UI |
208            constants::RG32I |
209            constants::RGB8 |
210            constants::RGBA8 |
211            constants::SRGB8_ALPHA8 |
212            constants::RGB10_A2 |
213            constants::RGBA8UI |
214            constants::RGBA8I |
215            constants::RGB10_A2UI |
216            constants::RGBA16UI |
217            constants::RGBA16I |
218            constants::RGBA32I |
219            constants::RGBA32UI |
220            constants::DEPTH_COMPONENT24 |
221            constants::DEPTH_COMPONENT32F |
222            constants::DEPTH24_STENCIL8 |
223            constants::DEPTH32F_STENCIL8 => match webgl_version {
224                WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum),
225                _ => internal_format,
226            },
227            // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8
228            constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
229            constants::RGB5_A1 => {
230                // 16-bit RGBA formats are not supported on desktop GL.
231                if is_gles {
232                    constants::RGB5_A1
233                } else {
234                    constants::RGBA8
235                }
236            },
237            constants::RGB565 => {
238                // RGB565 is not supported on desktop GL.
239                if is_gles {
240                    constants::RGB565
241                } else {
242                    constants::RGB8
243                }
244            },
245            EXTColorBufferHalfFloatConstants::RGBA16F_EXT |
246            EXTColorBufferHalfFloatConstants::RGB16F_EXT => {
247                if !context
248                    .extension_manager()
249                    .is_half_float_buffer_renderable()
250                {
251                    return Err(WebGLError::InvalidEnum);
252                }
253                internal_format
254            },
255            WEBGLColorBufferFloatConstants::RGBA32F_EXT => {
256                if !context.extension_manager().is_float_buffer_renderable() {
257                    return Err(WebGLError::InvalidEnum);
258                }
259                internal_format
260            },
261            _ => return Err(WebGLError::InvalidEnum),
262        };
263
264        if webgl_version != WebGLVersion::WebGL1 {
265            let (sender, receiver) = webgl_channel().unwrap();
266            self.upcast()
267                .send_command(WebGLCommand::GetInternalFormatIntVec(
268                    constants::RENDERBUFFER,
269                    internal_format,
270                    InternalFormatIntVec::Samples,
271                    sender,
272                ));
273            let samples = receiver.recv().unwrap();
274            if sample_count < 0 || sample_count > samples.first().cloned().unwrap_or(0) {
275                return Err(WebGLError::InvalidOperation);
276            }
277        }
278
279        self.internal_format.set(Some(internal_format));
280        self.is_initialized.set(false);
281
282        if let Some(fb) = self.attached_framebuffer.get() {
283            fb.update_status();
284        }
285
286        let command = match sample_count {
287            0 => WebGLCommand::RenderbufferStorage(
288                constants::RENDERBUFFER,
289                actual_format,
290                width,
291                height,
292            ),
293            _ => WebGLCommand::RenderbufferStorageMultisample(
294                constants::RENDERBUFFER,
295                sample_count,
296                actual_format,
297                width,
298                height,
299            ),
300        };
301        self.upcast().send_command(command);
302
303        self.size.set(Some((width, height)));
304        Ok(())
305    }
306
307    pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
308        self.attached_framebuffer.set(Some(fb));
309    }
310
311    pub(crate) fn detach_from_framebuffer(&self) {
312        self.attached_framebuffer.set(None);
313    }
314}