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))
33 def scale_integer(windowW, windowH, inputW, inputH):
35 Calculates the image as the largest integer multiple of raw frame size.
37 scaleX = windowW / inputW
38 scaleY = windowH / inputH
40 if scaleX > 0 and scaleY > 0:
41 scale = min(scaleX, scaleY)
42 finalW = scale * inputW
43 finalH = scale * inputH
58 def scale_aspect(windowW, windowH, inputW, inputH):
60 Calculates the image as the largest multiple of the raw frame size.
63 float(windowW) / float(inputW),
64 float(windowH) / float(inputH),
67 return inputW * scale, inputH * scale
70 def scale_max(windowW, windowH, inputW, inputH):
72 Stretch the image to the largest possible size, regardless of aspect.
74 return windowW, windowH
78 ("Integer", scale_integer),
79 ("Aspect-correct", scale_aspect),
80 ("Maximized", scale_max),
84 class GLUTDemo(object):
86 def __init__(self, shaderFile, texturePath):
87 self.start = time.clock()
90 glutInitDisplayMode(GLUT_RGBA)
92 glutInitWindowSize(640, 480)
93 self.window = glutCreateWindow("XML Shader Demo - Esc to exit")
97 glutDisplayFunc(self._draw_scene)
98 glutIdleFunc(self._handle_idle)
99 glutReshapeFunc(self._handle_resize)
100 glutKeyboardFunc(self._handle_key)
102 glClearColor(0.0, 0.0, 0.0, 0.0)
103 glEnable(GL_TEXTURE_2D)
105 with open(shaderFile, "r") as handle:
106 self.shaderPasses = shaderreader.parse_shader(handle.read())
108 self.textures = load_textures(texturePath)
109 textureMenu = glutCreateMenu(self._set_texture)
110 for index, (filename, _) in enumerate(self.textures):
111 glutAddMenuEntry(filename, index)
113 self.inputTexture = None
116 self.framebufferID = framebufferobjects.glGenFramebuffers(1)
117 self.framebufferTexture1, self.framebufferTexture2 = \
120 scaleMenu = glutCreateMenu(self._set_scale_method)
121 for index, (desc, _) in enumerate(SCALE_METHODS):
122 glutAddMenuEntry(desc, index)
124 self._set_scale_method(0)
126 self.masterMenu = glutCreateMenu(lambda _: 0)
127 glutAddSubMenu("Test pattern", textureMenu)
128 glutAddSubMenu("Scale method", scaleMenu)
129 glutAttachMenu(GLUT_RIGHT_BUTTON)
131 def _set_texture(self, textureIndex):
133 _, self.inputTexture = self.textures[textureIndex]
135 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
136 # to return anything. PyOpenGL seems to think differently.
139 def _set_scale_method(self, index):
140 self.scaleMethod = SCALE_METHODS[index][1]
142 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
143 # to return anything. PyOpenGL seems to think differently.
146 def _setUniform(self, setter, program, name, *values):
147 loc = glGetUniformLocation(program, name)
153 def _draw_texture(self, shaderPass, texture, x, y, width, height):
154 if shaderPass is not None:
155 glUseProgram(shaderPass.programID)
157 self._setUniform(glUniform2f, shaderPass.programID,
158 "rubyInputSize", texture.imageWidth, texture.imageHeight)
159 self._setUniform(glUniform2f, shaderPass.programID,
160 "rubyOutputSize", width, height)
161 self._setUniform(glUniform2f, shaderPass.programID,
163 texture.textureWidth, texture.textureHeight)
164 self._setUniform(glUniform1i, shaderPass.programID,
165 "rubyFrameCount", self.framecount)
167 if shaderPass.filterMethod == shaderreader.ATTR_FILTER_LINEAR:
170 filterID = GL_NEAREST
173 # There's no shader pass to tell us what to do, so go with a safe
175 if shaderreader.ATTR_FILTER_DEFAULT == shaderreader.ATTR_FILTER_LINEAR:
178 filterID = GL_NEAREST
180 glBindTexture(GL_TEXTURE_2D, texture.textureID)
182 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterID)
183 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterID)
185 glClear(GL_COLOR_BUFFER_BIT)
187 glBegin(GL_QUADS) # Start drawing a 4 sided polygon
189 texW = float(texture.imageWidth) / texture.textureWidth
190 texH = float(texture.imageHeight) / texture.textureHeight
192 glTexCoord2f(0, 0 ); glVertex(x, y, )
193 glTexCoord2f(texW, 0 ); glVertex(x+width, y, )
194 glTexCoord2f(texW, texH); glVertex(x+width, y+height)
195 glTexCoord2f(0, texH); glVertex(x, y+height)
197 glEnd() # We are done with the polygon
199 if shaderPass is not None:
202 def _draw_texture_to_fbo(self, shaderPass, fromTexture, finalW, finalH):
204 # How big should the output of this pass be?
205 outputW, outputH = shaderPass.calculateFramebufferSize(
206 fromTexture.imageWidth, fromTexture.imageHeight,
210 # If this pass doesn't specify a size, default to the input.
211 if outputW is None: outputW = fromTexture.imageWidth
212 if outputH is None: outputH = fromTexture.imageWidth
214 # Make a texture exactly that big.
215 toTexture = texreader.Texture(outputW, outputH)
217 # Configure OpenGL to render to the texture.
218 framebufferobjects.glBindFramebuffer(
219 framebufferobjects.GL_FRAMEBUFFER,
222 framebufferobjects.glFramebufferTexture2D(
223 framebufferobjects.GL_FRAMEBUFFER,
224 framebufferobjects.GL_COLOR_ATTACHMENT0,
231 framebufferobjects.checkFramebufferStatus()
233 glViewport(0, 0, int(outputW), int(outputH))
234 glMatrixMode(GL_PROJECTION)
236 # A framebufferobject will always have the underlying texture's (0,0)
237 # at the lower-left, so align our coordinate system to match.
238 glOrtho(0, outputW, 0, outputH, -1, 1)
240 self._draw_texture(shaderPass, fromTexture, 0, 0, outputW, outputH)
242 # Detach all the things we set up.
243 framebufferobjects.glBindFramebuffer(
244 framebufferobjects.GL_FRAMEBUFFER,
250 def _draw_scene(self):
251 if None in (self.inputTexture, self.windowW, self.windowH):
254 finalW, finalH = self.scaleMethod(
255 self.windowW, self.windowH,
256 self.inputTexture.imageWidth, self.inputTexture.imageHeight,
259 finalX = (self.windowW - finalW) / 2
260 finalY = (self.windowH - finalH) / 2
262 # Will we need an implicit pass at the end of this?
263 requiresImplicitPass = self.shaderPasses[-1].requiresImplicitPass()
265 # Render all but the last pass
266 fromTexture = self.inputTexture
267 for shaderPass in self.shaderPasses[:-1]:
268 fromTexture = self._draw_texture_to_fbo(
269 shaderPass, fromTexture, finalW, finalH)
271 # If the last pass expects to be rendered at some specific scale, we'd
272 # better respect its wishes.
273 if requiresImplicitPass:
274 fromTexture = self._draw_texture_to_fbo(
275 self.shaderPasses[-1], fromTexture, finalW, finalH)
278 lastPass = self.shaderPasses[-1]
280 glViewport(0, 0, self.windowW, self.windowH)
281 glMatrixMode(GL_PROJECTION)
283 # After all the shader passes, the (0,0) corner of the resulting
284 # texture is supposed to appear at the top-left, so let's set our
285 # coordinate system appropriately.
286 glOrtho(0, self.windowW, self.windowH, 0, -1, 1)
288 self._draw_texture(lastPass, fromTexture,
289 finalX, finalY, finalW, finalH,
294 def _handle_key(self, key, x, y):
295 if key == '\x1b': # Escape
298 def _handle_resize(self, width, height):
299 self.windowW = max(width, 1)
300 self.windowH = max(height, 1)
302 def _handle_idle(self):
304 if now > self.start + 1:
305 fps = self.framecount / (now - self.start)
306 sys.stdout.write("FPS: %0.1f\r" % fps)
311 glutPostWindowRedisplay(self.window)
315 # clean up textures we've allocated.
316 for _, texture in self.textures:
319 # There's no clean way to kill GLUT.
322 if __name__ == "__main__":
323 argv = glutInit(sys.argv)
326 print >> sys.stderr, "Usage: %s /path/to/shaderfile" % (argv[0],)
330 texturePath = os.path.join(BASEDIR, "test-patterns")
331 demo = GLUTDemo(shaderFile, texturePath)