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;
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]
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(
128            cx,
129            Box::new(WebGLRenderbuffer::new_inherited(context, id)),
130            &*context.global(),
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        sample_count: i32,
177        internal_format: u32,
178        width: i32,
179        height: i32,
180    ) -> WebGLResult<()> {
181        let Some(context) = self.upcast().context() else {
182            return Err(WebGLError::ContextLost);
183        };
184
185        let webgl_version = context.webgl_version();
186
187        // Validate the internal_format, and save it for completeness
188        // validation.
189        let actual_format = renderbuffer_format(&context, internal_format)?;
190
191        if webgl_version != WebGLVersion::WebGL1 {
192            let (sender, receiver) = webgl_channel().unwrap();
193            self.upcast()
194                .send_command(WebGLCommand::GetInternalFormatIntVec(
195                    constants::RENDERBUFFER,
196                    internal_format,
197                    InternalFormatIntVec::Samples,
198                    sender,
199                ));
200            let samples = receiver.recv().unwrap();
201            if sample_count < 0 || sample_count > samples.first().cloned().unwrap_or(0) {
202                return Err(WebGLError::InvalidOperation);
203            }
204        }
205
206        self.internal_format.set(Some(internal_format));
207        self.is_initialized.set(false);
208
209        if let Some(fb) = self.attached_framebuffer.get() {
210            fb.update_status();
211        }
212
213        let command = match sample_count {
214            0 => WebGLCommand::RenderbufferStorage(
215                constants::RENDERBUFFER,
216                actual_format,
217                width,
218                height,
219            ),
220            _ => WebGLCommand::RenderbufferStorageMultisample(
221                constants::RENDERBUFFER,
222                sample_count,
223                actual_format,
224                width,
225                height,
226            ),
227        };
228        self.upcast().send_command(command);
229
230        self.size.set(Some((width, height)));
231        Ok(())
232    }
233
234    pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
235        self.attached_framebuffer.set(Some(fb));
236    }
237
238    pub(crate) fn detach_from_framebuffer(&self) {
239        self.attached_framebuffer.set(None);
240    }
241}
242
243/// The format GL allocates for a renderbuffer of `internal_format`, or `InvalidEnum` if
244/// `context` cannot render to that format.
245pub(crate) fn renderbuffer_format(
246    context: &WebGLRenderingContext,
247    internal_format: u32,
248) -> WebGLResult<u32> {
249    let webgl_version = context.webgl_version();
250    let is_gles = context.api_type() == GlType::Gles;
251    Ok(match internal_format {
252        constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => {
253            internal_format
254        },
255        constants::R8 |
256        constants::R8UI |
257        constants::R8I |
258        constants::R16UI |
259        constants::R16I |
260        constants::R32UI |
261        constants::R32I |
262        constants::RG8 |
263        constants::RG8UI |
264        constants::RG8I |
265        constants::RG16UI |
266        constants::RG16I |
267        constants::RG32UI |
268        constants::RG32I |
269        constants::RGB8 |
270        constants::RGBA8 |
271        constants::SRGB8_ALPHA8 |
272        constants::RGB10_A2 |
273        constants::RGBA8UI |
274        constants::RGBA8I |
275        constants::RGB10_A2UI |
276        constants::RGBA16UI |
277        constants::RGBA16I |
278        constants::RGBA32I |
279        constants::RGBA32UI |
280        constants::DEPTH_COMPONENT24 |
281        constants::DEPTH_COMPONENT32F |
282        constants::DEPTH24_STENCIL8 |
283        constants::DEPTH32F_STENCIL8 => match webgl_version {
284            WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum),
285            _ => internal_format,
286        },
287        // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8
288        constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
289        constants::RGB5_A1 => {
290            // 16-bit RGBA formats are not supported on desktop GL.
291            if is_gles {
292                constants::RGB5_A1
293            } else {
294                constants::RGBA8
295            }
296        },
297        constants::RGB565 => {
298            // RGB565 is not supported on desktop GL.
299            if is_gles {
300                constants::RGB565
301            } else {
302                constants::RGB8
303            }
304        },
305        EXTColorBufferHalfFloatConstants::RGBA16F_EXT |
306        EXTColorBufferHalfFloatConstants::RGB16F_EXT => {
307            if !context
308                .extension_manager()
309                .is_half_float_buffer_renderable()
310            {
311                return Err(WebGLError::InvalidEnum);
312            }
313            internal_format
314        },
315        WEBGLColorBufferFloatConstants::RGBA32F_EXT => {
316            if !context.extension_manager().is_float_buffer_renderable() {
317                return Err(WebGLError::InvalidEnum);
318            }
319            internal_format
320        },
321        _ => return Err(WebGLError::InvalidEnum),
322    })
323}