1 """Framebuffer object class.
2
3 @warn: Framebuffers are currently bound for drawing only (C{GL_DRAW_FRAMEBUFFER}, not C{GL_READ_FRAMEBUFFER}).
4 @todo: Implement binding for reading; wrap C{glReadPixels}, C{glBlitFramebuffer}, C{glCopyTexSubImage} and related framebuffer copy functions.
5 @todo: Add properties for C{viewport}, C{color_writemask}, C{depth_writemask},
6 C{blend_func}, C{blend_equation}, and C{depth_range} (using
7 C{glDepthRangeArray} and C{glDepthRangeIndexed}).
8
9 @author: Stephan Wenger
10 @date: 2012-02-29
11 """
12
13 import glitter.raw as _gl
14 from glitter.utils import ManagedObject, BindableObject, framebuffer_status
17 _generate_id = _gl.glGenFramebuffers
18 _delete_id = _gl.glDeleteBuffers
19 _db = "framebuffers"
20 _binding = "draw_framebuffer_binding"
21 _target = _gl.GL_DRAW_FRAMEBUFFER
22
23 framebuffer_status = framebuffer_status
24 _initialized = False
25
26 - def __init__(self, *attachments, **kwargs):
27 """Create a new framebuffer.
28
29 @param attachments: Textures to bind to color attachments.
30 @type attachments: C{list} of L{Texture}s
31 @param kwargs: Named arguments.
32 @type kwargs: C{dict}
33 @keyword context: The context in which to create the framebuffer.
34 @type context: L{Context}
35 @keyword depth: An optional depth buffer attachment.
36 @type depth: L{Texture}
37 @keyword stencil: An optional stencil buffer attachment.
38 @type stencil: L{Texture}
39 """
40
41 super(Framebuffer, self).__init__(context=kwargs.pop("context", None))
42 self._attachments = {}
43
44 if isinstance(attachments, dict):
45 attachments = dict(attachments)
46 else:
47 attachments = dict(enumerate(attachments))
48 for i in range(self._context.max_color_attachments):
49 self[i] = attachments.pop(i, None)
50 if attachments:
51 raise ValueError("framebuffer has no attachment(s) %s" % ", ".join("'%s'" % x for x in attachments.keys()))
52
53 self.depth = kwargs.pop("depth", None)
54 self.stencil = kwargs.pop("stencil", None)
55
56 if kwargs:
57 raise TypeError("__init__() got an unexpected keyword argument '%s'" % kwargs.keys()[0])
58
59 self._initialized = True
60
62 return self._attachments[index]
63
66
69
71 """Setup global framebuffer related state.
72
73 @todo: Setup C{color_writemasks}, C{depth_writemasks}, C{blend_funcs}, C{blend_equations}, and C{depth_ranges} after C{viewport}.
74 """
75
76 if self._initialized:
77 self._stack.append(self._context.draw_buffers)
78 self._context.draw_buffers = [i if self[i] is not None else None for i in range(self._context.max_color_attachments)]
79
80 self._stack.append(self._context.viewport)
81 if self.shape is not None:
82 self._context.viewport = (0, 0) + self.shape
83
85 """Restore global framebuffer related state.
86
87 @todo: Restore C{depth_ranges}, C{blend_equations}, C{blend_funcs}, C{depth_writemasks}, and C{color_writemasks} before C{viewport}.
88 """
89
90 if self._initialized:
91 self._context.viewport = self._stack.pop()
92 self._context.draw_buffers = self._stack.pop()
93
94 - def _attach(self, attachment, texture=None, layer=None, level=0):
95 """Attach a texture to an attachment.
96
97 C{texture} may be a (texture, layer) tuple generated by
98 L{Texture}C{.__getitem__()}.
99 """
100
101 if type(texture) is tuple:
102 if layer is not None:
103 raise ValueError("cannot provide layer as both keyword and texture tuple")
104 texture, layer = texture
105 return self._attach(attachment, texture, layer, level)
106
107 with self:
108 if texture is None:
109 _gl.glFramebufferTextureLayer(self._target, attachment, 0, level, 0)
110 elif layer is None:
111 _gl.glFramebufferTexture(self._target, attachment, texture._id, level)
112 else:
113 _gl.glFramebufferTextureLayer(self._target, attachment, texture._id, level, layer)
114
115 - def attach(self, index, texture=None, layer=None, level=0):
116 """Attach a texture to color attachment C{index}.
117
118 C{texture} may be a (texture, layer) tuple generated by
119 L{Texture}C{.__getitem__()}. Otherwise, C{layer} specifies which
120 texture layer is to be bound. All layers are bound when C{layer} is
121 C{None}.
122
123 C{level} specifies which resolution level of the texture is to be
124 bound.
125
126 If C{texture} is C{None}, the attachment will be unbound.
127 """
128
129 self._attach(_gl.GL_COLOR_ATTACHMENT0 + index, texture, layer, level)
130 self._attachments[index] = texture
131
132 @property
134 if not hasattr(self, "_depth"):
135 return None
136 return self._depth
137
138 @depth.setter
141
142 @depth.deleter
145
147 """Attach a texture to the depth attachment.
148
149 C{texture} may be a (texture, layer) tuple generated by
150 L{Texture}C{.__getitem__()}. Otherwise, C{layer} specifies which
151 texture layer is to be bound. All layers are bound when C{layer} is
152 C{None}.
153
154 C{level} specifies which resolution level of the texture is to be
155 bound.
156
157 If C{texture} is C{None}, the attachment will be unbound.
158 """
159
160 self._attach(_gl.GL_DEPTH_ATTACHMENT, texture, layer, level)
161 self._depth = texture
162
163 @property
165 if not hasattr(self, "_stencil"):
166 return None
167 return self._stencil
168
169 @stencil.setter
172
173 @stencil.deleter
176
178 """Attach a texture to the stencil attachment.
179
180 C{texture} may be a (texture, layer) tuple generated by
181 L{Texture}C{.__getitem__()}. Otherwise, C{layer} specifies which
182 texture layer is to be bound. All layers are bound when C{layer} is
183 C{None}.
184
185 C{level} specifies which resolution level of the texture is to be
186 bound.
187
188 If C{texture} is C{None}, the attachment will be unbound.
189 """
190
191 self._attach(_gl.GL_STENCIL_ATTACHMENT, texture, layer, level)
192 self._stencil = texture
193
194 @property
203
204 @property
206 shape = None
207 for i, attachment in sorted(self._attachments.items()):
208 if attachment is not None:
209 if type(attachment) is tuple:
210 texture, layer = attachment
211 shape = (min(shape[0], texture.shape[1]), min(shape[1], texture.shape[2])) if shape else texture.shape[1:3]
212 else:
213 shape = (min(shape[0], attachment.shape[0]), min(shape[1], attachment.shape[1])) if shape else attachment.shape[:2]
214 if self.depth is not None:
215 shape = (min(shape[0], self.depth.shape[0]), min(shape[1], self.depth.shape[1])) if shape else self.depth.shape[:2]
216 if self.stencil is not None:
217 shape = (min(shape[0], self.stencil.shape[0]), min(shape[1], self.stencil.shape[1])) if shape else self.stencil.shape[:2]
218 return shape
219
220 - def clear(self, color=None, depth=None, stencil=None):
221 """Clear the framebuffer.
222
223 @param color: Whether to clear the color buffer, and optionally, to which value.
224 @type color: C{bool} or C{numpy.ndarray}.
225 @param depth: Whether to clear the depth buffer, and optionally, to which value.
226 @type depth: C{bool} or C{numpy.ndarray}.
227 @param stencil: Whether to clear the stencil buffer, and optionally, to which value.
228 @type stencil: C{bool} or C{numpy.ndarray}.
229
230 If no parameters are given, color, depth and stencil are cleared with
231 the current clear values.
232
233 @todo: Use C{glClearBuffer} to clear selected attachments only.
234 """
235
236 with self:
237 self._context._perform_gl_clear(color, depth, stencil)
238
239 __all__ = ["Framebuffer"]
240