6 from OpenGL.GL import *
7 from OpenGL.GLUT import *
8 from OpenGL.GL import framebufferobjects
13 BASEDIR = os.path.abspath(os.path.dirname(__file__))
16 def load_textures(texturePath):
19 for name in os.listdir(texturePath):
20 if not name.endswith(".ppm"):
23 fullpath = os.path.join(texturePath, name)
25 (name, texreader.textureFromFile(fullpath))
31 def scale_integer(windowW, windowH, inputW, inputH):
33 Calculates the image as the largest integer multiple of raw frame size.
35 scaleX = windowW / inputW
36 scaleY = windowH / inputH
38 if scaleX > 0 and scaleY > 0:
39 scale = min(scaleX, scaleY)
40 finalW = scale * inputW
41 finalH = scale * inputH
56 def scale_aspect(windowW, windowH, inputW, inputH):
58 Calculates the image as the largest multiple of the raw frame size.
61 float(windowW) / float(inputW),
62 float(windowH) / float(inputH),
65 return inputW * scale, inputH * scale
68 def scale_max(windowW, windowH, inputW, inputH):
70 Stretch the image to the largest possible size, regardless of aspect.
72 return windowW, windowH
76 ("Integer", scale_integer),
77 ("Aspect-correct", scale_aspect),
78 ("Maximized", scale_max),
82 class GLUTDemo(object):
84 def __init__(self, shaderFile, texturePath):
85 self.start = time.clock()
88 glutInitDisplayMode(GLUT_RGBA)
90 glutInitWindowSize(640, 480)
91 self.window = glutCreateWindow("XML Shader Demo - Esc to exit")
95 glutDisplayFunc(self._draw_scene)
96 glutIdleFunc(self._handle_idle)
97 glutReshapeFunc(self._handle_resize)
98 glutKeyboardFunc(self._handle_key)
100 glClearColor(0.0, 0.0, 0.0, 0.0)
101 glEnable(GL_TEXTURE_2D)
103 with open(shaderFile, "r") as handle:
104 self.shaderPasses = shaderreader.parse_shader(handle.read())
106 self.textures = load_textures(texturePath)
107 textureMenu = glutCreateMenu(self._set_texture)
108 for index, (filename, _) in enumerate(self.textures):
109 glutAddMenuEntry(filename, index)
111 self.inputTexture = None
114 self.framebufferID = framebufferobjects.glGenFramebuffers(1)
115 self.framebufferTexture1, self.framebufferTexture2 = \
118 scaleMenu = glutCreateMenu(self._set_scale_method)
119 for index, (desc, _) in enumerate(SCALE_METHODS):
120 glutAddMenuEntry(desc, index)
122 self._set_scale_method(0)
124 self.masterMenu = glutCreateMenu(lambda _: 0)
125 glutAddSubMenu("Test pattern", textureMenu)
126 glutAddSubMenu("Scale method", scaleMenu)
127 glutAttachMenu(GLUT_RIGHT_BUTTON)
129 def _set_texture(self, textureIndex):
131 _, self.inputTexture = self.textures[textureIndex]
133 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
134 # to return anything. PyOpenGL seems to think differently.
137 def _set_scale_method(self, index):
138 self.scaleMethod = SCALE_METHODS[index][1]
140 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
141 # to return anything. PyOpenGL seems to think differently.
144 def _setUniform(self, program, name, x, y):
145 loc = glGetUniformLocation(program, name)
150 glUniform2f(loc, x, y)
152 def _render_pass(self, shaderPass, fromTexture, finalW, finalH):
154 # How big should the output of this pass be?
155 outputW, outputH = shaderPass.calculateFramebufferSize(
156 fromTexture.width, fromTexture.height, finalW, finalH,
159 # If this pass doesn't specify a size, default to the input.
160 if outputW is None: outputW = fromTexture.width
161 if outputH is None: outputH = fromTexture.height
163 # Make a texture exactly that big.
164 toTexture = texreader.Texture(outputW, outputH)
166 # Configure OpenGL to render to the texture.
167 framebufferobjects.glBindFramebuffer(
168 framebufferobjects.GL_FRAMEBUFFER,
171 framebufferobjects.glFramebufferTexture2D(
172 framebufferobjects.GL_FRAMEBUFFER,
173 framebufferobjects.GL_COLOR_ATTACHMENT0,
180 framebufferobjects.glCheckFramebufferStatus(
181 framebufferobjects.GL_FRAMEBUFFER,
184 # Let's do our rendering pass.
185 glUseProgram(shaderPass.programID)
187 self._setUniform(shaderPass.programID, 'rubyInputSize',
188 fromTexture.width, fromTexture.height)
189 self._setUniform(shaderPass.programID, 'rubyOutputSize',
190 toTexture.width, toTexture.height)
191 self._setUniform(shaderPass.programID, 'rubyTextureSize',
192 fromTexture.width, fromTexture.height)
193 loc = glGetUniformLocation(shaderPass.programID, 'rubyFrameCount')
195 glUniform1i(loc, self.framecount)
197 glBindTexture(GL_TEXTURE_2D, fromTexture.textureID)
199 if shaderPass.filterMethod == shaderreader.ATTR_FILTER_LINEAR:
200 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
201 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
203 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
204 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
206 glViewport(0, 0, int(outputW), int(outputH))
207 glMatrixMode(GL_PROJECTION)
209 # A framebufferobject will always have the underlying texture's (0,0)
210 # at the lower-left, so align our coordinate system to match.
211 glOrtho(0, outputW, 0, outputH, -1, 1)
213 glClear(GL_COLOR_BUFFER_BIT)
215 glBegin(GL_QUADS) # Start drawing a 4 sided polygon
217 glTexCoord2f(0, 0 ); glVertex(0, 0, )
218 glTexCoord2f(1.0, 0 ); glVertex(outputW, 0, )
219 glTexCoord2f(1.0, 1.0); glVertex(outputW, outputH)
220 glTexCoord2f(0, 1.0); glVertex(0, outputH)
222 glEnd() # We are done with the polygon
224 # Detach all the things we set up.
226 framebufferobjects.glBindFramebuffer(
227 framebufferobjects.GL_FRAMEBUFFER,
233 def _draw_scene(self):
234 if None in (self.inputTexture, self.windowW, self.windowH):
237 finalW, finalH = self.scaleMethod(
238 self.windowW, self.windowH,
239 self.inputTexture.width, self.inputTexture.height,
242 finalX = (self.windowW - finalW) / 2
243 finalY = (self.windowH - finalH) / 2
245 fromTexture = self.inputTexture
246 for shaderPass in self.shaderPasses:
247 toTexture = self._render_pass(shaderPass, fromTexture,
249 fromTexture = toTexture
252 glViewport(0, 0, self.windowW, self.windowH)
253 glMatrixMode(GL_PROJECTION)
255 # After all the shader passes, the (0,0) corner of the resulting
256 # texture is supposed to appear at the top-left, so let's set our
257 # coordinate system appropriately.
258 glOrtho(0, self.windowW, self.windowH, 0, -1, 1)
260 glClear(GL_COLOR_BUFFER_BIT)
262 glBindTexture(GL_TEXTURE_2D, toTexture.textureID)
263 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
264 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
266 glBegin(GL_QUADS) # Start drawing a 4 sided polygon
268 glTexCoord(0, 0 ); glVertex(finalX, finalY, )
269 glTexCoord(1.0, 0 ); glVertex(finalX+finalW, finalY, )
270 glTexCoord(1.0, 1.0); glVertex(finalX+finalW, finalY+finalH)
271 glTexCoord(0, 1.0); glVertex(finalX, finalY+finalH)
273 glEnd() # We are done with the polygon
277 def _handle_key(self, key, x, y):
278 if key == '\x1b': # Escape
281 def _handle_resize(self, width, height):
282 self.windowW = max(width, 1)
283 self.windowH = max(height, 1)
285 def _handle_idle(self):
287 if now > self.start + 1:
288 fps = self.framecount / (now - self.start)
289 sys.stdout.write("FPS: %0.1f\r" % fps)
294 glutPostWindowRedisplay(self.window)
297 if __name__ == "__main__":
298 argv = glutInit(sys.argv)
301 print >> sys.stderr, "Usage: %s /path/to/shaderfile" % (argv[0],)
305 texturePath = os.path.join(BASEDIR, "test-patterns")
306 demo = GLUTDemo(shaderFile, texturePath)