1 """Pipeline class unifying vertex array, shader and framebuffer.
2
3 @author: Stephan Wenger
4 @date: 2012-03-05
5 """
6
7 from glitter.framebuffers import Framebuffer
8 from glitter.arrays import VertexArray
9 from glitter.utils import ItemProxy, InstanceDescriptorMixin, State, StateMixin, add_proxies, PropertyProxy
10 from glitter.shaders.uniform import BaseUniform
11
12 -class Pipeline(InstanceDescriptorMixin, StateMixin):
13 """Convenience class for rendering pipelines.
14
15 C{Pipeline}s contain a vertex array, a shader, and an optional framebuffer.
16 Property access and method calls are appropriately redirected to these
17 objects. Vertex attributes, shader uniforms, and fragment outputs can be
18 set by their name either in the constructor or by accessing the
19 corresponding properties on the pipeline.
20
21 Vertex attributes can be set to buffer objects or anything that can be
22 converted to an C{ArrayBuffer}.
23
24 Shader uniforms behave as usual.
25
26 Fragment outputs can be set to texture objects.
27
28 @attention: In order to guarantee correct functioning of vertex attribute,
29 uniform and fragment output variables, their names must be unique, must not
30 begin with an underscore, and must not collide with the names of any vertex
31 array or framebuffer methods.
32
33 Usage examples:
34
35 >>> shader = ShaderProgram(...)
36 >>> vertices = [...]
37 >>> colors = [...]
38 >>> elements = [...]
39 >>> texture = Texture2D(...)
40
41 >>> pipeline = Pipeline(shader, in_position=vertices, in_color=colors, elements=elements, out_color=texture)
42 >>> pipeline.clear()
43 >>> pipeline.draw()
44
45 >>> pipeline = Pipeline(shader, use_framebuffer=False)
46 >>> with pipeline(in_position=vertices, in_color=colors, elements=elements):
47 ... pipeline.draw()
48 >>> with pipeline(in_position=vertices, in_color=colors, elements=elements) as p:
49 ... p.draw()
50 >>> pipeline.draw_with(in_position=vertices, in_color=colors, elements=elements)
51
52 >>> pipeline = Pipeline(shader, use_framebuffer=False)
53 >>> vertex_array = VertexArray(...)
54 >>> with pipeline:
55 ... vertex_array.draw()
56 """
57
58 _frozen = False
59 """Whether setting of unknown attributes should be interpreted literally or as accessing vertex array and framebuffer properties."""
60
61 - def __init__(self, shader, use_framebuffer=True, **kwargs):
62 """Create a new C{Pipeline}.
63
64 @param shader: The compiled and linked shader program object to use.
65 @type shader: L{ShaderProgram}
66 @param use_framebuffer: If C{True}, render to textures instead of the currently bound framebuffer.
67 @type use_framebuffer: C{bool}
68 @param kwargs: Named arguments are translated to setting of attributes.
69
70 @todo: C{_vao} and C{_fbo} should be created dynamically when (and if)
71 attributes and attachments are accessed, respectively;
72 C{use_framebuffer} should not be necessary then.
73 """
74
75 self._shader = shader
76 self._context = self._shader._context
77 self._lazy_context_properties = {}
78 self._lazy_context_property_stack = []
79
80
81 for name, proxy in self._shader.__dict__.items():
82 if isinstance(proxy, BaseUniform):
83 setattr(self, name, PropertyProxy(self._shader, name))
84
85
86 self._vao = VertexArray(context=self._context)
87 add_proxies(self, self._vao)
88
89
90 if use_framebuffer:
91 self._fbo = Framebuffer(context=self._context)
92 add_proxies(self, self._fbo)
93 else:
94 self._fbo = None
95
96 self._frozen = True
97
98
99 for key, value in kwargs.items():
100 setattr(self, key, value)
101
105
112
114 """Determine whether the shader as a fragment output named C{name}."""
115 return self._fbo is not None and self._shader.has_frag_data_location(name)
116
123
125 """Set an attribute.
126
127 When C{name} is a known attribute or starts with an underscore, or when
128 C{self} is not in L{_frozen} state, C{__setattr__} works as usual.
129
130 When C{self} is in L{_frozen} state (default), setting of unknown
131 attributes will check for vertex attributes, fragment outputs and
132 context properties called C{name} and create appropriate proxies or
133 raise an error if no such attribute or output exists.
134
135 Context properties set here will be set on binding and reset on
136 unbinding the pipeline.
137 """
138
139 if hasattr(self, name) or name.startswith("_") or not self._frozen:
140 super(Pipeline, self).__setattr__(name, value)
141 elif self._has_input(name):
142 self._add_input(name, value)
143 elif self._has_output(name):
144 self._add_output(name, value)
145 elif hasattr(self._context, name):
146 self._lazy_context_properties[name] = value
147 else:
148 raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
149
151 """Delete an attribute.
152
153 When C{name} is a known attribute or starts with an underscore, or when
154 C{self} is not in L{_frozen} state, C{__delattr__} works as usual.
155
156 Otherwise, if the attribute is a previously set context property, it
157 will be removed from the pipeline.
158 """
159
160 if hasattr(self, name) or name.startswith("_") or not self._frozen:
161 super(Pipeline, self).__delattr__(name, value)
162 elif name in self._lazy_context_properties:
163 del self._lazy_context_properties[name]
164 else:
165 raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
166
168 """Bind framebuffer and shader."""
169
170 stack_frame = []
171 for key, value in self._lazy_context_properties.items():
172 stack_frame.append((key, getattr(self._context, key)))
173 setattr(self._context, key, value)
174 self._lazy_context_property_stack.append(stack_frame)
175
176 if self._fbo is not None:
177 self._fbo.__enter__()
178 self._shader.__enter__()
179 return self
180
181 - def __exit__(self, type, value, traceback):
182 """Unbind framebuffer and shader."""
183 self._shader.__exit__(type, value, traceback)
184 if self._fbo is not None:
185 self._fbo.__exit__(type, value, traceback)
186
187 for key, value in self._lazy_context_property_stack.pop():
188 setattr(self._context, key, value)
189
191 """Call L{draw<VertexArray.draw>} on C{self} with attributes from C{kwargs} set.
192
193 Keyword arguments that are known attributes or shader input our output
194 variable names will be set as properties on C{self}. Context properties
195 are set before drawing and reset afterwards. Any other arguments will
196 be passed to L{draw<VertexArray.draw>}.
197
198 @todo: C{kwargs} context properties should override C{_lazy_context_properties}.
199 """
200
201 def is_valid_property(name):
202 return hasattr(self, name) or self._has_input(name) or self._has_output(name)
203 with self(**{key: kwargs.pop(key) for key, value in kwargs.items() if is_valid_property(key)}):
204 with State(context=self._context, **{key: kwargs.pop(key) for key, value in kwargs.items() if hasattr(self._context, key)}):
205 self.draw(*args, **kwargs)
206
207 __all__ = ["Pipeline"]
208