1use 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 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 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 constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
225 constants::RGB5_A1 => {
226 if is_gles {
228 constants::RGB5_A1
229 } else {
230 constants::RGBA8
231 }
232 },
233 constants::RGB565 => {
234 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}