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 http://mozilla.org/MPL/2.0/. */
4 
5 // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
6 use canvas_traits::webgl::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId};
7 use canvas_traits::webgl::{WebGLMsgSender, WebGLResult, WebGLError};
8 use canvas_traits::webgl::webgl_channel;
9 use dom::bindings::cell::DomRefCell;
10 use dom::bindings::codegen::Bindings::WebGLFramebufferBinding;
11 use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
12 use dom::bindings::reflector::reflect_dom_object;
13 use dom::bindings::root::{Dom, DomRoot};
14 use dom::webglobject::WebGLObject;
15 use dom::webglrenderbuffer::WebGLRenderbuffer;
16 use dom::webgltexture::WebGLTexture;
17 use dom::window::Window;
18 use dom_struct::dom_struct;
19 use std::cell::Cell;
20 
21 #[must_root]
22 #[derive(Clone, JSTraceable, MallocSizeOf)]
23 enum WebGLFramebufferAttachment {
24     Renderbuffer(Dom<WebGLRenderbuffer>),
25     Texture { texture: Dom<WebGLTexture>, level: i32 },
26 }
27 
28 #[dom_struct]
29 pub struct WebGLFramebuffer {
30     webgl_object: WebGLObject,
31     id: WebGLFramebufferId,
32     /// target can only be gl::FRAMEBUFFER at the moment
33     target: Cell<Option<u32>>,
34     is_deleted: Cell<bool>,
35     size: Cell<Option<(i32, i32)>>,
36     status: Cell<u32>,
37     #[ignore_malloc_size_of = "Defined in ipc-channel"]
38     renderer: WebGLMsgSender,
39 
40     // The attachment points for textures and renderbuffers on this
41     // FBO.
42     color: DomRefCell<Option<WebGLFramebufferAttachment>>,
43     depth: DomRefCell<Option<WebGLFramebufferAttachment>>,
44     stencil: DomRefCell<Option<WebGLFramebufferAttachment>>,
45     depthstencil: DomRefCell<Option<WebGLFramebufferAttachment>>,
46 }
47 
48 impl WebGLFramebuffer {
new_inherited(renderer: WebGLMsgSender, id: WebGLFramebufferId) -> WebGLFramebuffer49     fn new_inherited(renderer: WebGLMsgSender,
50                      id: WebGLFramebufferId)
51                      -> WebGLFramebuffer {
52         WebGLFramebuffer {
53             webgl_object: WebGLObject::new_inherited(),
54             id: id,
55             target: Cell::new(None),
56             is_deleted: Cell::new(false),
57             renderer: renderer,
58             size: Cell::new(None),
59             status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED),
60             color: DomRefCell::new(None),
61             depth: DomRefCell::new(None),
62             stencil: DomRefCell::new(None),
63             depthstencil: DomRefCell::new(None),
64         }
65     }
66 
maybe_new(window: &Window, renderer: WebGLMsgSender) -> Option<DomRoot<WebGLFramebuffer>>67     pub fn maybe_new(window: &Window, renderer: WebGLMsgSender)
68                      -> Option<DomRoot<WebGLFramebuffer>> {
69         let (sender, receiver) = webgl_channel().unwrap();
70         renderer.send(WebGLCommand::CreateFramebuffer(sender)).unwrap();
71 
72         let result = receiver.recv().unwrap();
73         result.map(|fb_id| WebGLFramebuffer::new(window, renderer, fb_id))
74     }
75 
new(window: &Window, renderer: WebGLMsgSender, id: WebGLFramebufferId) -> DomRoot<WebGLFramebuffer>76     pub fn new(window: &Window,
77                renderer: WebGLMsgSender,
78                id: WebGLFramebufferId)
79                -> DomRoot<WebGLFramebuffer> {
80         reflect_dom_object(Box::new(WebGLFramebuffer::new_inherited(renderer, id)),
81                            window,
82                            WebGLFramebufferBinding::Wrap)
83     }
84 }
85 
86 
87 impl WebGLFramebuffer {
id(&self) -> WebGLFramebufferId88     pub fn id(&self) -> WebGLFramebufferId {
89         self.id
90     }
91 
bind(&self, target: u32)92     pub fn bind(&self, target: u32) {
93         // Update the framebuffer status on binding.  It may have
94         // changed if its attachments were resized or deleted while
95         // we've been unbound.
96         self.update_status();
97 
98         self.target.set(Some(target));
99         let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id));
100         self.renderer.send(cmd).unwrap();
101     }
102 
delete(&self)103     pub fn delete(&self) {
104         if !self.is_deleted.get() {
105             self.is_deleted.set(true);
106             let _ = self.renderer.send(WebGLCommand::DeleteFramebuffer(self.id));
107         }
108     }
109 
is_deleted(&self) -> bool110     pub fn is_deleted(&self) -> bool {
111         self.is_deleted.get()
112     }
113 
size(&self) -> Option<(i32, i32)>114     pub fn size(&self) -> Option<(i32, i32)> {
115         self.size.get()
116     }
117 
update_status(&self)118     fn update_status(&self) {
119         let c = self.color.borrow();
120         let z = self.depth.borrow();
121         let s = self.stencil.borrow();
122         let zs = self.depthstencil.borrow();
123         let has_c = c.is_some();
124         let has_z = z.is_some();
125         let has_s = s.is_some();
126         let has_zs = zs.is_some();
127         let attachments = [&*c, &*z, &*s, &*zs];
128 
129         // From the WebGL spec, 6.6 ("Framebuffer Object Attachments"):
130         //
131         //    "In the WebGL API, it is an error to concurrently attach
132         //     renderbuffers to the following combinations of
133         //     attachment points:
134         //
135         //     DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
136         //     STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
137         //     DEPTH_ATTACHMENT + STENCIL_ATTACHMENT
138         //
139         //     If any of the constraints above are violated, then:
140         //
141         //     checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED."
142         if (has_zs && (has_z || has_s)) ||
143             (has_z && has_s) {
144             self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
145             return;
146         }
147 
148         let mut fb_size = None;
149         for attachment in &attachments {
150             // Get the size of this attachment.
151             let size = match **attachment {
152                 Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => {
153                     att_rb.size()
154                 }
155                 Some(WebGLFramebufferAttachment::Texture { texture: ref att_tex, level } ) => {
156                     let info = att_tex.image_info_at_face(0, level as u32);
157                     Some((info.width() as i32, info.height() as i32))
158                 }
159                 None => None,
160             };
161 
162             // Make sure that, if we've found any other attachment,
163             // that the size matches.
164             if size.is_some() {
165                 if fb_size.is_some() && size != fb_size {
166                     self.status.set(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
167                     return;
168                 } else {
169                     fb_size = size;
170                 }
171             }
172         }
173         self.size.set(fb_size);
174 
175         if has_c || has_z || has_zs || has_s {
176             self.status.set(constants::FRAMEBUFFER_COMPLETE);
177         } else {
178             self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
179         }
180     }
181 
check_status(&self) -> u32182     pub fn check_status(&self) -> u32 {
183         return self.status.get();
184     }
185 
renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()>186     pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> {
187         let binding = match attachment {
188             constants::COLOR_ATTACHMENT0 => &self.color,
189             constants::DEPTH_ATTACHMENT => &self.depth,
190             constants::STENCIL_ATTACHMENT => &self.stencil,
191             constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
192             _ => return Err(WebGLError::InvalidEnum),
193         };
194 
195         let rb_id = match rb {
196             Some(rb) => {
197                 *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Renderbuffer(Dom::from_ref(rb)));
198                 Some(rb.id())
199             }
200 
201             _ => {
202                 *binding.borrow_mut() = None;
203                 None
204             }
205         };
206 
207         self.renderer.send(WebGLCommand::FramebufferRenderbuffer(constants::FRAMEBUFFER,
208                                                                  attachment,
209                                                                  constants::RENDERBUFFER,
210                                                                  rb_id)).unwrap();
211 
212         self.update_status();
213         Ok(())
214     }
215 
texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>, level: i32) -> WebGLResult<()>216     pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>,
217                      level: i32) -> WebGLResult<()> {
218         let binding = match attachment {
219             constants::COLOR_ATTACHMENT0 => &self.color,
220             constants::DEPTH_ATTACHMENT => &self.depth,
221             constants::STENCIL_ATTACHMENT => &self.stencil,
222             constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
223             _ => return Err(WebGLError::InvalidEnum),
224         };
225 
226         let tex_id = match texture {
227             // Note, from the GLES 2.0.25 spec, page 113:
228             //      "If texture is zero, then textarget and level are ignored."
229             Some(texture) => {
230                 // From the GLES 2.0.25 spec, page 113:
231                 //
232                 //     "level specifies the mipmap level of the texture image
233                 //      to be attached to the framebuffer and must be
234                 //      0. Otherwise, INVALID_VALUE is generated."
235                 if level != 0 {
236                     return Err(WebGLError::InvalidValue);
237                 }
238 
239                 //     "If texture is not zero, then texture must either
240                 //      name an existing texture object with an target of
241                 //      textarget, or texture must name an existing cube
242                 //      map texture and textarget must be one of:
243                 //      TEXTURE_CUBE_MAP_POSITIVE_X,
244                 //      TEXTURE_CUBE_MAP_POSITIVE_Y,
245                 //      TEXTURE_CUBE_MAP_POSITIVE_Z,
246                 //      TEXTURE_CUBE_MAP_NEGATIVE_X,
247                 //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
248                 //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
249                 //      INVALID_OPERATION is generated."
250                 let is_cube = match textarget {
251                     constants::TEXTURE_2D => false,
252 
253                     constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
254                     constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
255                     constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
256                     constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
257                     constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
258                     constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,
259 
260                     _ => return Err(WebGLError::InvalidEnum),
261                 };
262 
263                 match texture.target() {
264                     Some(constants::TEXTURE_CUBE_MAP) if is_cube => {}
265                     Some(_) if !is_cube => {}
266                     _ => return Err(WebGLError::InvalidOperation),
267                 }
268 
269                 *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
270                     texture: Dom::from_ref(texture),
271                     level: level }
272                 );
273 
274                 Some(texture.id())
275             }
276 
277             _ => {
278                 *binding.borrow_mut() = None;
279                 None
280             }
281         };
282 
283         self.renderer.send(WebGLCommand::FramebufferTexture2D(constants::FRAMEBUFFER,
284                                                               attachment,
285                                                               textarget,
286                                                               tex_id,
287                                                               level)).unwrap();
288 
289         self.update_status();
290         Ok(())
291     }
292 
with_matching_renderbuffers<F>(&self, rb: &WebGLRenderbuffer, mut closure: F) where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>)293     fn with_matching_renderbuffers<F>(&self, rb: &WebGLRenderbuffer, mut closure: F)
294         where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>)
295     {
296         let attachments = [&self.color,
297                            &self.depth,
298                            &self.stencil,
299                            &self.depthstencil];
300 
301         for attachment in &attachments {
302             let matched = {
303                 match *attachment.borrow() {
304                     Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb))
305                         if rb.id() == att_rb.id() => true,
306                     _ => false,
307                 }
308             };
309 
310             if matched {
311                 closure(attachment);
312             }
313         }
314     }
315 
with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F) where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>)316     fn with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F)
317         where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>)
318     {
319         let attachments = [&self.color,
320                            &self.depth,
321                            &self.stencil,
322                            &self.depthstencil];
323 
324         for attachment in &attachments {
325             let matched = {
326                 match *attachment.borrow() {
327                     Some(WebGLFramebufferAttachment::Texture { texture: ref att_texture, .. })
328                         if texture.id() == att_texture.id() => true,
329                     _ => false,
330                 }
331             };
332 
333             if matched {
334                 closure(attachment);
335             }
336         }
337     }
338 
detach_renderbuffer(&self, rb: &WebGLRenderbuffer)339     pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) {
340         self.with_matching_renderbuffers(rb, |att| {
341             *att.borrow_mut() = None;
342             self.update_status();
343         });
344     }
345 
detach_texture(&self, texture: &WebGLTexture)346     pub fn detach_texture(&self, texture: &WebGLTexture) {
347         self.with_matching_textures(texture, |att| {
348             *att.borrow_mut() = None;
349             self.update_status();
350         });
351     }
352 
invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer)353     pub fn invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer) {
354         self.with_matching_renderbuffers(rb, |_att| {
355             self.update_status();
356         });
357     }
358 
invalidate_texture(&self, texture: &WebGLTexture)359     pub fn invalidate_texture(&self, texture: &WebGLTexture) {
360         self.with_matching_textures(texture, |_att| {
361             self.update_status();
362         });
363     }
364 
target(&self) -> Option<u32>365     pub fn target(&self) -> Option<u32> {
366         self.target.get()
367     }
368 }
369 
370 impl Drop for WebGLFramebuffer {
drop(&mut self)371     fn drop(&mut self) {
372         self.delete();
373     }
374 }
375