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__))
19 texturePath = os.path.join(BASEDIR, "test-patterns")
20 for name in os.listdir(texturePath):
21 if not name.endswith(".ppm"):
24 with open(os.path.join(texturePath, name), "r") as handle:
28 width, height, pixels = texreader.parse_ppm(data)
30 print >> sys.stderr, "Could not read texture from %r: %s" % (
34 res.append((os.path.basename(name), width, height, pixels))
39 def scale_integer(windowW, windowH, inputW, inputH):
41 Calculates the image as the largest integer multiple of raw frame size.
43 scaleX = windowW / inputW
44 scaleY = windowH / inputH
46 if scaleX > 0 and scaleY > 0:
47 scale = min(scaleX, scaleY)
48 finalW = scale * inputW
49 finalH = scale * inputH
64 def scale_aspect(windowW, windowH, inputW, inputH):
66 Calculates the image as the largest multiple of the raw frame size.
69 float(windowW) / float(inputW),
70 float(windowH) / float(inputH),
73 return inputW * scale, inputH * scale
76 def scale_max(windowW, windowH, inputW, inputH):
78 Stretch the image to the largest possible size, regardless of aspect.
80 return windowW, windowH
84 ("Integer", scale_integer),
85 ("Aspect-correct", scale_aspect),
86 ("Maximized", scale_max),
90 class Texture(object):
92 def __init__(self, width, height, pixels=None):
93 self.textureID = glGenTextures(1)
97 glBindTexture(GL_TEXTURE_2D, self.textureID)
99 # We assume the GL implementation can handle non-power-of-two textures,
100 # so we use the image dimensions directly as texture dimensions.
101 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
102 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels)
104 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
105 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
108 glDeleteTextures([self.textureID])
111 class GLUTDemo(object):
113 def __init__(self, shaderFile, textures):
114 self.start = time.clock()
117 glutInitDisplayMode(GLUT_RGBA)
119 glutInitWindowSize(640, 480)
120 self.window = glutCreateWindow("XML Shader Demo - Esc to exit")
124 glutDisplayFunc(self._draw_scene)
125 glutIdleFunc(self._handle_idle)
126 glutReshapeFunc(self._handle_resize)
127 glutKeyboardFunc(self._handle_key)
129 glClearColor(0.0, 0.0, 0.0, 0.0)
130 glEnable(GL_TEXTURE_2D)
132 with open(shaderFile, "r") as handle:
133 self.shaderPasses = shaderreader.parse_shader(handle.read())
135 self.textures = textures
136 textureMenu = glutCreateMenu(self._set_texture)
137 for index, (filename, _, _, _) in enumerate(self.textures):
138 glutAddMenuEntry(filename, index)
140 self.inputTexture = None
143 self.framebufferID = framebufferobjects.glGenFramebuffers(1)
144 self.framebufferTexture1, self.framebufferTexture2 = \
147 scaleMenu = glutCreateMenu(self._set_scale_method)
148 for index, (desc, _) in enumerate(SCALE_METHODS):
149 glutAddMenuEntry(desc, index)
151 self._set_scale_method(0)
153 self.masterMenu = glutCreateMenu(lambda _: 0)
154 glutAddSubMenu("Test pattern", textureMenu)
155 glutAddSubMenu("Scale method", scaleMenu)
156 glutAttachMenu(GLUT_RIGHT_BUTTON)
158 def _set_texture(self, textureIndex):
160 _, inputW, inputH, pixels = self.textures[textureIndex]
162 self.inputTexture = Texture(inputW, inputH, pixels)
164 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
165 # to return anything. PyOpenGL seems to think differently.
168 def _set_scale_method(self, index):
169 self.scaleMethod = SCALE_METHODS[index][1]
171 # According to the OpenGL docs, a glutCreateMenu callback doesn't need
172 # to return anything. PyOpenGL seems to think differently.
175 def _setUniform(self, program, name, x, y):
176 loc = glGetUniformLocation(program, name)
181 glUniform2f(loc, x, y)
183 def _render_pass(self, shaderPass, fromTexture, finalW, finalH):
185 # How big should the output of this pass be?
186 outputW, outputH = shaderPass.calculateFramebufferSize(
187 fromTexture.width, fromTexture.height, finalW, finalH,
190 # If this pass doesn't specify a size, default to the input.
191 if outputW is None: outputW = fromTexture.width
192 if outputH is None: outputH = fromTexture.height
194 # Make a texture exactly that big.
195 toTexture = Texture(outputW, outputH)
197 # Configure OpenGL to render to the texture.
198 framebufferobjects.glBindFramebuffer(
199 framebufferobjects.GL_FRAMEBUFFER,
202 framebufferobjects.glFramebufferTexture2D(
203 framebufferobjects.GL_FRAMEBUFFER,
204 framebufferobjects.GL_COLOR_ATTACHMENT0,
211 framebufferobjects.glCheckFramebufferStatus(
212 framebufferobjects.GL_FRAMEBUFFER,
215 # Let's do our rendering pass.
216 glUseProgram(shaderPass.programID)
218 self._setUniform(shaderPass.programID, 'rubyInputSize',
219 fromTexture.width, fromTexture.height)
220 self._setUniform(shaderPass.programID, 'rubyOutputSize',
221 toTexture.width, toTexture.height)
222 self._setUniform(shaderPass.programID, 'rubyTextureSize',
223 fromTexture.width, fromTexture.height)
224 loc = glGetUniformLocation(shaderPass.programID, 'rubyFrameCount')
226 glUniform1i(loc, self.framecount)
228 glBindTexture(GL_TEXTURE_2D, fromTexture.textureID)
230 if shaderPass.filterMethod == shaderreader.ATTR_FILTER_LINEAR:
231 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
232 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
234 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
235 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
237 glViewport(0, 0, int(outputW), int(outputH))
238 glMatrixMode(GL_PROJECTION)
240 # A framebufferobject will always have the underlying texture's (0,0)
241 # at the lower-left, so align our coordinate system to match.
242 glOrtho(0, outputW, 0, outputH, -1, 1)
244 glClear(GL_COLOR_BUFFER_BIT)
246 glBegin(GL_QUADS) # Start drawing a 4 sided polygon
248 glTexCoord2f(0, 0 ); glVertex(0, 0, )
249 glTexCoord2f(1.0, 0 ); glVertex(outputW, 0, )
250 glTexCoord2f(1.0, 1.0); glVertex(outputW, outputH)
251 glTexCoord2f(0, 1.0); glVertex(0, outputH)
253 glEnd() # We are done with the polygon
255 # Detach all the things we set up.
257 framebufferobjects.glBindFramebuffer(
258 framebufferobjects.GL_FRAMEBUFFER,
264 def _draw_scene(self):
265 if None in (self.inputTexture, self.windowW, self.windowH):
268 finalW, finalH = self.scaleMethod(
269 self.windowW, self.windowH,
270 self.inputTexture.width, self.inputTexture.height,
273 finalX = (self.windowW - finalW) / 2
274 finalY = (self.windowH - finalH) / 2
276 fromTexture = self.inputTexture
277 for shaderPass in self.shaderPasses:
278 toTexture = self._render_pass(shaderPass, fromTexture,
280 fromTexture = toTexture
283 glViewport(0, 0, self.windowW, self.windowH)
284 glMatrixMode(GL_PROJECTION)
286 # After all the shader passes, the (0,0) corner of the resulting
287 # texture is supposed to appear at the top-left, so let's set our
288 # coordinate system appropriately.
289 glOrtho(0, self.windowW, self.windowH, 0, -1, 1)
291 glClear(GL_COLOR_BUFFER_BIT)
293 glBindTexture(GL_TEXTURE_2D, toTexture.textureID)
294 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
295 glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
297 glBegin(GL_QUADS) # Start drawing a 4 sided polygon
299 glTexCoord(0, 0 ); glVertex(finalX, finalY, )
300 glTexCoord(1.0, 0 ); glVertex(finalX+finalW, finalY, )
301 glTexCoord(1.0, 1.0); glVertex(finalX+finalW, finalY+finalH)
302 glTexCoord(0, 1.0); glVertex(finalX, finalY+finalH)
304 glEnd() # We are done with the polygon
308 def _handle_key(self, key, x, y):
309 if key == '\x1b': # Escape
312 def _handle_resize(self, width, height):
313 self.windowW = max(width, 1)
314 self.windowH = max(height, 1)
316 def _handle_idle(self):
318 if now > self.start + 1:
319 fps = self.framecount / (now - self.start)
320 sys.stdout.write("FPS: %0.1f\r" % fps)
325 glutPostWindowRedisplay(self.window)
328 if __name__ == "__main__":
329 argv = glutInit(sys.argv)
332 print >> sys.stderr, "Usage: %s /path/to/shaderfile" % (argv[0],)
336 textures = load_textures()
339 print >> sys.stderr, "Can't find any textures to display."
342 demo = GLUTDemo(shaderFile, textures)