1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from __future__ import division
24
25 import Log
26 import Config
27 import Image
28 import pygame
29 import StringIO
30 import PngImagePlugin
31 from OpenGL.GL import *
32 from OpenGL.GLU import *
33 from Queue import Queue, Empty
34
35 Config.define("opengl", "supportfbo", bool, False)
36
37 try:
38 from glew import *
39 except ImportError:
40
41 pass
42
43 -class TextureException(Exception):
45
46
47
48 cleanupQueue = Queue()
49
51 fboSupported = None
52
53 - def __init__(self, texture, width, height, generateMipmap = False):
54 self.emulated = not self._fboSupported()
55 self.size = (width, height)
56 self.colorbuf = texture
57 self.generateMipmap = generateMipmap
58 self.fb = 0
59 self.depthbuf = 0
60 self.stencilbuf = 0
61
62 if self.emulated:
63 if (width & (width - 1)) or (height & (height - 1)):
64 raise TextureException("Only power of two render target textures are supported when frame buffer objects support is missing.")
65 else:
66 self.fb = glGenFramebuffersEXT(1)[0]
67 self.depthbuf = glGenRenderbuffersEXT(1)[0]
68 self.stencilbuf = glGenRenderbuffersEXT(1)[0]
69 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.fb)
70 self._checkError()
71
72 glBindTexture(GL_TEXTURE_2D, self.colorbuf)
73 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
74
75 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
76 GL_RGBA, GL_UNSIGNED_BYTE, "\x00" * (width * height * 4))
77 self._checkError()
78
79 if self.emulated:
80 return
81
82 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.fb)
83
84 try:
85 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
86 GL_COLOR_ATTACHMENT0_EXT,
87 GL_TEXTURE_2D, self.colorbuf, 0)
88 self._checkError()
89
90
91
92
93 if glewGetExtension("GL_NV_packed_depth_stencil"):
94 GL_DEPTH_STENCIL_EXT = 0x84F9
95
96 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, self.depthbuf)
97 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
98 GL_DEPTH_STENCIL_EXT, width, height)
99 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
100 GL_DEPTH_ATTACHMENT_EXT,
101 GL_RENDERBUFFER_EXT, self.depthbuf)
102 self._checkError()
103 else:
104 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, self.depthbuf)
105 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
106 GL_DEPTH_COMPONENT24, width, height)
107 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
108 GL_DEPTH_ATTACHMENT_EXT,
109 GL_RENDERBUFFER_EXT, self.depthbuf)
110 self._checkError()
111 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, self.stencilbuf)
112 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
113 GL_STENCIL_INDEX_EXT, width, height)
114 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
115 GL_STENCIL_ATTACHMENT_EXT,
116 GL_RENDERBUFFER_EXT, self.stencilbuf)
117 self._checkError()
118 finally:
119 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
120
122
123 try:
124 cleanupQueue.put((glDeleteBuffers, [3, [self.depthbuf, self.stencilbuf, self.fb]]))
125 except NameError:
126 pass
127
129 if Framebuffer.fboSupported is not None:
130 return Framebuffer.fboSupported
131 Framebuffer.fboSupported = False
132
133 if not Config.get("opengl", "supportfbo"):
134 Log.warn("Frame buffer object support disabled in configuration.")
135 return False
136
137 if not "glewGetExtension" in globals():
138 Log.warn("GLEWpy not found, so render to texture functionality disabled.")
139 return False
140
141 glewInit()
142
143 if not glewGetExtension("GL_EXT_framebuffer_object"):
144 Log.warn("No support for framebuffer objects, so render to texture functionality disabled.")
145 return False
146
147 if glGetString(GL_VENDOR) == "ATI Technologies Inc.":
148 Log.warn("Frame buffer object support disabled until ATI learns to make proper OpenGL drivers (no stencil support).")
149 return False
150
151 Framebuffer.fboSupported = True
152 return True
153
156
157
158
159
160
162 if not self.emulated:
163 glBindTexture(GL_TEXTURE_2D, 0)
164 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.fb)
165 self._checkError()
166
168 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
169 if not self.emulated:
170 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
171 glBindTexture(GL_TEXTURE_2D, self.colorbuf)
172 if self.generateMipmap:
173 glGenerateMipmapEXT(GL_TEXTURE_2D)
174 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
175 else:
176 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
177 else:
178 glBindTexture(GL_TEXTURE_2D, self.colorbuf)
179 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, self.size[0], self.size[1])
180 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
181
183 """Represents an OpenGL texture, optionally loaded from disk in any format supported by PIL"""
184
185 - def __init__(self, name = None, target = GL_TEXTURE_2D):
186
187 try:
188 func, args = cleanupQueue.get_nowait()
189 func(*args)
190 except Empty:
191 pass
192
193 self.texture = glGenTextures(1)
194 self.texEnv = GL_MODULATE
195 self.glTarget = target
196 self.framebuffer = None
197
198 self.setDefaults()
199 self.name = name
200
201 if name:
202 self.loadFile(name)
203
204 - def loadFile(self, name):
205 """Load the texture from disk, using PIL to open the file"""
206 self.loadImage(Image.open(name))
207 self.name = name
208
209 - def loadImage(self, image):
210 """Load the texture from a PIL image"""
211 image = image.transpose(Image.FLIP_TOP_BOTTOM)
212 if image.mode == "RGBA":
213 string = image.tostring('raw', 'RGBA', 0, -1)
214 self.loadRaw(image.size, string, GL_RGBA, 4)
215 elif image.mode == "RGB":
216 string = image.tostring('raw', 'RGB', 0, -1)
217 self.loadRaw(image.size, string, GL_RGB, 3)
218 elif image.mode == "L":
219 string = image.tostring('raw', 'L', 0, -1)
220 self.loadRaw(image.size, string, GL_LUMINANCE, 1)
221 else:
222 raise TextureException("Unsupported image mode '%s'" % image.mode)
223
224 - def prepareRenderTarget(self, width, height, generateMipmap = True):
225 self.framebuffer = Framebuffer(self.texture, width, height, generateMipmap)
226 self.pixelSize = (width, height)
227 self.size = (1.0, 1.0)
228
230 assert self.framebuffer
231 self.framebuffer.setAsRenderTarget()
232
234 assert self.framebuffer
235 self.framebuffer.resetDefaultRenderTarget()
236
237 - def nextPowerOfTwo(self, n):
238 m = 1
239 while m < n:
240 m <<= 1
241 return m
242
243 - def loadSurface(self, surface, monochrome = False, alphaChannel = False):
244 """Load the texture from a pygame surface"""
245
246
247 self.pixelSize = w, h = surface.get_size()
248 w2, h2 = [self.nextPowerOfTwo(x) for x in [w, h]]
249 if w != w2 or h != h2:
250 s = pygame.Surface((w2, h2), pygame.SRCALPHA, 32)
251 s.blit(surface, (0, h2 - h))
252 surface = s
253
254 if monochrome:
255
256
257 string = pygame.image.tostring(surface, "RGB")
258 image = Image.fromstring("RGB", surface.get_size(), string).convert("L")
259 string = image.tostring('raw', 'L', 0, -1)
260 self.loadRaw(surface.get_size(), string, GL_LUMINANCE, GL_INTENSITY8)
261 else:
262 if alphaChannel:
263 string = pygame.image.tostring(surface, "RGBA", True)
264 self.loadRaw(surface.get_size(), string, GL_RGBA, 4)
265 else:
266 string = pygame.image.tostring(surface, "RGB", True)
267 self.loadRaw(surface.get_size(), string, GL_RGB, 3)
268 self.size = (w / w2, h / h2)
269
270 - def loadSubsurface(self, surface, position = (0, 0), monochrome = False, alphaChannel = False):
271 """Load the texture from a pygame surface"""
272
273 if monochrome:
274
275
276 string = pygame.image.tostring(surface, "RGB")
277 image = Image.fromstring("RGB", surface.get_size(), string).convert("L")
278 string = image.tostring('raw', 'L', 0, -1)
279 self.loadSubRaw(surface.get_size(), position, string, GL_INTENSITY8)
280 else:
281 if alphaChannel:
282 string = pygame.image.tostring(surface, "RGBA", True)
283 self.loadSubRaw(surface.get_size(), position, string, GL_RGBA)
284 else:
285 string = pygame.image.tostring(surface, "RGB", True)
286 self.loadSubRaw(surface.get_size(), position, string, GL_RGB)
287
288 - def loadRaw(self, size, string, format, components):
289 """Load a raw image from the given string. 'format' is a constant such as
290 GL_RGB or GL_RGBA that can be passed to gluBuild2DMipmaps.
291 """
292 self.pixelSize = size
293 self.size = (1.0, 1.0)
294 self.format = format
295 self.components = components
296 (w, h) = size
297 Texture.bind(self)
298 glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
299 gluBuild2DMipmaps(self.glTarget, components, w, h, format, GL_UNSIGNED_BYTE, string)
300
301 - def loadSubRaw(self, size, position, string, format):
302 Texture.bind(self)
303 glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
304 glTexSubImage2D(self.glTarget, 0, position[0], position[1], size[0], size[1], format, GL_UNSIGNED_BYTE, string)
305
306 - def loadEmpty(self, size, format):
307 self.pixelSize = size
308 self.size = (1.0, 1.0)
309 self.format = format
310 Texture.bind(self)
311 glTexImage2D(GL_TEXTURE_2D, 0, format, size[0], size[1], 0,
312 format, GL_UNSIGNED_BYTE, "\x00" * (size[0] * size[1] * 4))
313
314 - def setDefaults(self):
315 """Set the default OpenGL options for this texture"""
316 self.setRepeat()
317 self.setFilter()
318 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
319
320 - def setRepeat(self, u=GL_REPEAT, v=GL_REPEAT):
321 Texture.bind(self)
322 glTexParameteri(self.glTarget, GL_TEXTURE_WRAP_S, u)
323 glTexParameteri(self.glTarget, GL_TEXTURE_WRAP_T, v)
324
325 - def setFilter(self, min=GL_LINEAR_MIPMAP_LINEAR, mag=GL_LINEAR):
326 Texture.bind(self)
327 glTexParameteri(self.glTarget, GL_TEXTURE_MIN_FILTER, min)
328 glTexParameteri(self.glTarget, GL_TEXTURE_MAG_FILTER, mag)
329
331
332 try:
333 cleanupQueue.put((glDeleteTextures, [self.texture]))
334 except NameError:
335 pass
336
337 - def bind(self, glTarget = None):
338 """Bind this texture to self.glTarget in the current OpenGL context"""
339 if not glTarget:
340 glTarget = self.glTarget
341 glBindTexture(glTarget, self.texture)
342 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, self.texEnv)
343
344
345
346
347 TEXTURE_ATLAS_SIZE = 1024
348
351
352 -class TextureAtlas(object):
354 self.texture = Texture()
355 self.cursor = (0, 0)
356 self.rowHeight = 0
357 self.surfaceCount = 0
358 self.texture.loadEmpty((size, size), GL_RGBA)
359
360 - def add(self, surface, margin = 0):
361 w, h = surface.get_size()
362 x, y = self.cursor
363
364 w += margin
365 h += margin
366
367 if w > self.texture.pixelSize[0] or h > self.texture.pixelSize[1]:
368 raise ValueError("Surface is too big to fit into atlas.")
369
370 if x + w >= self.texture.pixelSize[0]:
371 x = 0
372 y += self.rowHeight
373 self.rowHeight = 0
374
375 if y + h >= self.texture.pixelSize[1]:
376 Log.debug("Texture atlas %s full after %d surfaces." % (self.texture.pixelSize, self.surfaceCount))
377 raise TextureAtlasFullException()
378
379 self.texture.loadSubsurface(surface, position = (x, y), alphaChannel = True)
380
381 self.surfaceCount += 1
382 self.rowHeight = max(self.rowHeight, h)
383 self.cursor = (x + w, y + h)
384
385
386 w -= margin
387 h -= margin
388 return x / float(self.texture.pixelSize[0]), y / float(self.texture.pixelSize[1]), \
389 (x + w) / float(self.texture.pixelSize[0]), (y + h) / float(self.texture.pixelSize[1])
390
393