#@PydevCodeAnalysisIgnore #cython: profile=False # Copyright 2004-2019 Tom Rothamel # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function DEF ANGLE = False from gl cimport * from gldraw cimport * from sdl2 cimport * from pygame_sdl2 cimport * import_pygame_sdl2() from cpython.string cimport PyString_FromStringAndSize from libc.stdlib cimport calloc, free import sys import time import collections import renpy # The maximum size of a texture. MAX_SIZE = 2048 # Possible sizes for a texture, ordered from largest to smallest. # (Now set in test_texture_sizes.) SIZES = [ 64 ] # A list of texture number allocated. texture_numbers = set() # The texture generation. generation = 1 # A map from texture number to generation texture_generation = { } cdef GLenum tex_format = GL_RGBA cdef GLenum tex_internalformat = GL_RGBA cdef GLenum tex_type = GL_UNSIGNED_BYTE cdef GLenum rtt_format = GL_RGBA cdef GLenum rtt_internalformat = GL_RGBA cdef GLenum rtt_type = GL_UNSIGNED_BYTE def use_gles(): global tex_format global tex_internalformat global tex_type global rtt_format global rtt_internalformat global rtt_type tex_format = GL_RGBA tex_internalformat = GL_RGBA tex_type = GL_UNSIGNED_BYTE rtt_format = GL_RGBA rtt_internalformat = GL_RGBA rtt_type = GL_UNSIGNED_BYTE def use_gl(): global tex_format global tex_internalformat global tex_type global rtt_format global rtt_internalformat global rtt_type # Optimize for the case of little-endian systems that use ARGB. if sys.byteorder == 'little': tex_format = GL_BGRA tex_internalformat = GL_RGBA tex_type = GL_UNSIGNED_INT_8_8_8_8_REV else: tex_format = GL_RGBA tex_internalformat = GL_RGBA tex_type = GL_UNSIGNED_BYTE rtt_format = GL_RGBA rtt_internalformat = GL_RGBA rtt_type = GL_UNSIGNED_BYTE def test_texture_sizes(Environ environ, draw): """ Tests each possible texture size to see if it can be used. We test the texture by creating a texture of the appropriate size, drawing it to the screen, and then checking to see if the texture was drawn properly. """ cdef int i cdef int size cdef GLuint tex cdef unsigned char *bitmap cdef GLfloat coords[6] cdef GLfloat texcoords[6] cdef unsigned char pixel[4] cdef int hw_max_size global MAX_SIZE global SIZES renpy.display.log.write("Texture testing:") # There could be an error queued up from an ANGLE reset. Purge it before we do the # texture testing. error = realGlGetError() if error != GL_NO_ERROR: renpy.display.log.write("- Ignored error at start of testing: {0:x}".format(error)) glGetIntegerv(GL_MAX_TEXTURE_SIZE, &hw_max_size) renpy.display.log.write("- Hardware max texture size: %d", hw_max_size) hw_max_size = min(2048, hw_max_size) SIZES = [ ] size = 64 while size <= hw_max_size: # Create an all-red bitmap of the given size. bitmap = calloc(size * size, 4) if not bitmap: renpy.display.log.write("- Could not allocate {0}px bitmap.".format(size)) break with nogil: if tex_format == GL_RGBA: for i from 0 <= i < size * size: bitmap[i * 4 + 0] = 0xff # r bitmap[i * 4 + 1] = 0x00 # g bitmap[i * 4 + 2] = 0x00 # b bitmap[i * 4 + 3] = 0xff # a else: for i from 0 <= i < size * size: bitmap[i * 4 + 0] = 0x00 # b bitmap[i * 4 + 1] = 0x00 # g bitmap[i * 4 + 2] = 0xff # r bitmap[i * 4 + 3] = 0xff # a # Create a texture of the given size. glActiveTextureARB(GL_TEXTURE0) glGenTextures(1, &tex) glBindTexture(GL_TEXTURE_2D, tex) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, tex_internalformat, size, size, 0, tex_format, tex_type, bitmap) # Free the bitmap. free(bitmap) error = realGlGetError() if error != GL_NO_ERROR: renpy.display.log.write("- Error loading {0}px bitmap: {1:x}".format(size, error)) glDeleteTextures(1, &tex) break # Vertex coordinates. coords[0] = 0 coords[1] = 0 coords[2] = 0 coords[3] = size coords[4] = size coords[5] = 0 # Texture coordinates. texcoords[0] = 0 texcoords[1] = 0 texcoords[2] = 0 texcoords[3] = 1.0 texcoords[4] = 1.0 texcoords[5] = 0 # Draw the triangles. environ.viewport(0, 0, 800, 600) environ.ortho(0, 800, 0, 600, -1.0, 1.0) environ.unset_clip(draw) environ.blit() environ.set_color(1.0, 1.0, 1.0, 1.0) environ.set_texture(0, texcoords) environ.set_vertex(coords) glDrawArrays(GL_TRIANGLES, 0, 3) # Delete the texture. glDeleteTextures(1, &tex) error = realGlGetError() if error != GL_NO_ERROR: renpy.display.log.write("- Error drawing {0}px texture: {1:x}".format(size, error)) break # Check the pixel color. glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel) error = realGlGetError() if error != GL_NO_ERROR: renpy.display.log.write("- Error reading {0}px texture: {1:x}".format(size, error)) break if pixel[0] != 0xff or pixel[1] != 0x00 or pixel[2] != 0x00: renpy.display.log.write("- Incorrect pixel color in {0}px texture: ({1:x}, {1:x}, {1:x})".format(size, pixel[0], pixel[1], pixel[2])) break # Record success. renpy.display.log.write("- {0}px textures work.".format(size)) SIZES.append(size) MAX_SIZE = size # Double the size and try again. size *= 2 # Clean up. environ.set_texture(0, NULL) if MAX_SIZE > 2048: MAX_SIZE = 2048 if not SIZES: renpy.display.log.write("Textures are not rendering properly.") return False SIZES.reverse() return True cdef class TextureCore: """ This object stores information about an OpenGL texture. """ def __init__(TextureCore self, int width, int height): # The width and height of this texture. self.width = width self.height = height # The number of the OpenGL texture this texture object # represents. self.generation = 0 self.number = 0 # These contained the premultiplied (but not GPU-loaded) # surface. They allow us to defer loading until the surface is # needed. self.premult = None self.premult_size = None # True if we're in NEAREST mode. False if we're in LINEAR mode. self.nearest = False # The free list we should be put on, or None if we're already on # a free list. self.free_list = None def __del__(self): cdef unsigned int num # Release the surface. self.premult = None self.premult_size = None self.premult_left = 0 self.premult_right = 0 self.premult_top = 0 self.premult_bottom = 0 # The test needs to be here so we don't try to append during # interpreter shutdown. if self.free_list is not None: try: self.free_list.append(self) self.free_list = None except TypeError: pass # Let's not error on shutdown. def load_surface(self, surf, x, y, w, h, border_left, border_top, border_right, border_bottom): """ Loads a pygame surface into this texture rectangle. """ # This just queues the surface up for loading. The actual loading # occurs when the texture is first needed. This ensures that the # texture loading only occurs in the GL thread. self.premult = premultiply( surf, x, y, w, h, border_left, border_top, border_right, border_bottom) self.premult_size = (w, h) cdef void make_nearest(TextureCore self): """ Causes this texture to be rendered in nearest-neighbor mode. """ if self.nearest: return glBindTexture(GL_TEXTURE_2D, self.number) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) self.nearest = True cdef void make_linear(TextureCore self): """ Causes this texture to be rendered in linear interpolation mode. """ if not self.nearest: return glBindTexture(GL_TEXTURE_2D, self.number) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) self.nearest = False cdef void make_ready(TextureCore self): """ Makes the texture ready for use. """ self.allocate() if self.premult: w, h = self.premult_size glBindTexture(GL_TEXTURE_2D, self.number) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) self.nearest = False # If we haven't initialized the texture yet, and we're # smaller than it, load in the empty texture. if w < self.width or h < self.height: if self.format != tex_internalformat: load_premultiplied( None, self.width, self.height, 0, False, ) self.format = tex_internalformat # Otherwise, either load or replace the texture. load_premultiplied( self.premult, w, h, (self.format == tex_internalformat), False, ) # Needs to be here twice, since we may not go through the w < SIDE # h < SIDE thing all the time. self.format = tex_internalformat # Finally, load in the default math. self.xadd = self.yadd = 0 self.xmul = 1.0 / self.width self.ymul = 1.0 / self.height # We don't need to be loaded anymore. self.premult = None self.premult_size = None def render_to(self, x, y, draw_func, rtt, environ): self.allocate() if self.format != rtt_format: glBindTexture(GL_TEXTURE_2D, self.number) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) self.nearest = False load_premultiplied( None, self.width, self.height, 0, True) self.format = rtt_format rtt.render(environ, self.number, x, y, self.width, self.height, draw_func) self.xadd = 0 self.yadd = 0 self.xmul = 1.0 / self.width self.ymul = 1.0 / self.height cpdef int allocate(self): """ This allocates a texture number, if necessary. """ global total_texture_size global texture_count cdef unsigned int texnums[1] if self.number != 0: return 0 glGenTextures(1, texnums) self.number = texnums[0] self.format = 0 texture_generation[self.number] = generation texture_numbers.add(texnums[0]) total_texture_size += self.width * self.height * 4 texture_count += 1 return 0 def deallocate(self): """ Deallocates this texture. The texture must have been removed from a free list before this is called. """ global total_texture_size global texture_count if self.number == 0: return cdef GLuint texnums[1] texnums[0] = self.number if texture_generation[self.number] == self.generation: glDeleteTextures(1, texnums) self.number = 0 texture_numbers.discard(self.number) total_texture_size -= self.width * self.height * 4 texture_count -= 1 class Texture(TextureCore): """ We need to be a real python class, not a C extension, to ensure that the __del__ method is called. """ def __sizeof__(self): return TextureCore.__sizeof__(self) + self.width * self.height * 4 def __getstate__(self): if renpy.config.developer: raise Exception("Can't pickle a texture.") else: return { } def __setstate__(self, state): return # This is a map from texture sizes to a list of free textures of that # size. free_textures = collections.defaultdict(list) # A list of NPOT textures that need to be freed. npot_free_textures = [ ] # The number of textues that have been allocated but not deallocated. texture_count = 0 # The total size (in bytes) of all the textures that have been allocated # but not deallocated. total_texture_size = 0 # This allocates a texture, either from the free list, or by asking # gl. def alloc_texture(width, height): """ Allocate a texture, either from the freelist or by asking GL. The returned texture has a reference count of 1. """ if renpy.game.preferences.gl_npot: rv = Texture(width, height) rv.free_list = npot_free_textures rv.generation = generation return rv l = free_textures[width, height] if l: rv = l.pop() else: rv = Texture(width, height) rv.free_list = l return rv def dealloc_textures(): cdef GLuint texnums[1] for l in free_textures.values(): for t in l: t.deallocate() for i in npot_free_textures: i.deallocate() npot_free_textures[:] = [ ] if not renpy.game.preferences.gl_npot: for t in texture_numbers: texnums[0] = t glDeleteTextures(1, texnums) free_textures.clear() # Do not reset texture numbers - we don't want to reuse a number that's # in use, only to have it deallocated later. global generation generation += 1 def cleanup(): """ This is called once per frame. """ # If we have more than one of a texture size, deallocate the last one of # that size. This prevents us from leaking memory via textures, while # making it unlikely we'll constantly allocate/deallocate textures. for l in free_textures.values(): if len(l) > 1: t = l.pop() t.deallocate() for i in npot_free_textures: i.deallocate() npot_free_textures[:] = [ ] def compute_subrow(row, offset, width): """ Given a row (or column), this computes a subrow starting at the given offset and having the given width. It returns two list. The first is a list of (offset, width, output-tile) tuples. The second is a list of integers, giving the input tile corresponding to each output tile. """ # An iterator over row. rowi = iter(row) # The output row and the output tile list. outrow = [ ] tiles = [ ] # The output tile index. outtile = 0 try: ioff, iwidth, itile = rowi.next() # Consume the offset. while True: if offset < iwidth: ioff += offset iwidth -= offset break offset -= iwidth ioff, iwidth, itile = rowi.next() # Consume the width. while True: if width < iwidth: outrow.append((ioff, width, outtile)) tiles.append(itile) outtile += 1 break outrow.append((ioff, iwidth, outtile)) tiles.append(itile) outtile += 1 width -= iwidth ioff, iwidth, itile = rowi.next() except StopIteration: pass return outrow, tiles cdef class TextureGrid(object): """ This represents one or more textures that cover a rectangular area. """ def __init__(self, width, height): #@DuplicatedSignature # The width and height of this TextureGrid self.width = width self.height = height # For each row of tiles, a tuple giving: # - The y offset within the texture. # - The height of the row. # - The rowindex in tiles. self.rows = [ ] # For each column of tiles, a tuple giving. # - The x offset within the texture. # - The width of the column. # - The colindex in tiles. self.columns = [ ] # The actual grid of texture titles, a list of lists of # textures. This is looked up by looking up rowindex and # colindex. self.tiles = [ ] # If it exists, a TextureGrid that is half the size of this # one. self.half_cache = None # Has the texture been made ready once? self.ready = False def __getstate__(self): #@DuplicatedSignature if renpy.config.developer: raise Exception("Can't pickle a texture.") else: return { } def __setstate__(self, state): #@DuplicatedSignature return def get_size(self): return self.width, self.height def subsurface(self, rect): """ This produces a texture grid containing a rectangle "cut out" of this texture grid. """ (x, y, w, h) = rect rv = TextureGrid(w, h) rv.rows, rowtiles = compute_subrow(self.rows, y, h) rv.columns, coltiles = compute_subrow(self.columns, x, w) for i in rowtiles: row = [ ] for j in coltiles: row.append(self.tiles[i][j]) rv.tiles.append(row) return rv cpdef void make_ready(self, bint nearest): """ Makes ready all the tile-textures in this texture grid. """ cdef list row cdef TextureCore t self.ready = True for row in self.tiles: for t in row: t.make_ready() if nearest: t.make_nearest() else: t.make_linear() # The old value of the gl_npot preference. old_gl_npot = None # This is a cache from (width, size) to the results of compute_tiling. tiling_cache = { } def compute_tiling(width, max_size, min_fill_factor): """ This computes a tiling for an image with the given width (or height). It takes a width as an argument, and returns two lists. The first is a list of (offset, width, index) tuples, as are used in TextureGrid to determine how to blit an image to the screen. The second is a list of (offset, copy-width, total-width) tuples that are used to create the tiles. While we're thinking about this as if it's working horizontally (x, width, etc), it """ global old_gl_npot gl_npot = renpy.game.preferences.gl_npot if old_gl_npot != gl_npot: old_gl_npot = gl_npot tiling_cache.clear() if gl_npot: max_size = min(SIZES[0], max_size) orig_width = width # Check the cache. key = (width, max_size) if key in tiling_cache: return tiling_cache[key] # width is the remaining width, not including any borders # required. # The x-offset, relative to the left side of the surface. x = 0 # The list of row tuples. row = [ ] # The list of tile tuples. tiles = [ ] # The index into the row. row_index = 0 if gl_npot and width <= max_size: left_border = 0 right_border = 0 else: left_border = 1 right_border = 1 while width: if gl_npot: size = min(width + left_border + right_border, max_size) else: # Figure out the texture size to use. for size in SIZES: if size > max_size: continue # Ensure each texture is full enough. if size * min_fill_factor <= width + left_border + right_border: break # The number of pixels to display to the user from this tile. row_size = min(width, size - left_border - right_border) #Add to the results. row.append((left_border, row_size, row_index)) tiles.append((x - left_border, row_size + left_border + right_border, size)) # Update the counters. row_index += 1 x += row_size width -= row_size tiling_cache[key] = (row, tiles) return row, tiles def texture_grid_from_surface(surf, transient): """ This takes a Surface and turns it into a TextureGrid. """ if renpy.game.preferences.gl_npot: max_size = SIZES[0] fill_factor = 0.5 elif transient: max_size = SIZES[0] fill_factor = 0.5 else: max_size = MAX_SIZE fill_factor = .66 width, height = surf.get_size() rv = TextureGrid(width, height) rv.columns, texcolumns = compute_tiling(width, max_size, fill_factor) rv.rows, texrows = compute_tiling(height, max_size, fill_factor) for rv_row, texrow in zip(rv.rows, texrows): border_top, _, border_bottom = rv_row y, height, texheight = texrow row = [ ] colnum = 0 for rv_col, texcol in zip(rv.columns, texcolumns): border_left, _, border_right = rv_col x, width, texwidth = texcol tex = alloc_texture(texwidth, texheight) tex.load_surface(surf, x, y, width, height, border_left, border_top, border_right, border_bottom) row.append(tex) rv.tiles.append(row) return rv def texture_grid_from_drawing(width, height, draw_func, rtt, environ): """ This creates a texture grid of `width` by `height` by using draw_func to draw to the screen. """ rv = TextureGrid(width, height) gldraw = renpy.display.draw pwidth, pheight = gldraw.physical_size rv.columns, texcolumns = compute_tiling(width, rtt.get_size_limit(pwidth), 0.5) rv.rows, texrows = compute_tiling(height, rtt.get_size_limit(pheight), 0.5) for y, height, texheight in texrows: row = [ ] for x, width, texwidth in texcolumns: tex = alloc_texture(texwidth, texheight) tex.render_to(x, y, draw_func, rtt, environ) row.append(tex) rv.tiles.append(row) return rv def align_axes(*args): """ This takes n axes, each a list consisting of (offset, size, index) tuples. It returns a list of n lists. For each of the n lists, the ith element will be the same size. The indexes will appear in the same order they did in the original list. This is used to combine the grids of two or more texgrids into a single grid. """ # The lists we're building. rv = [ [] for i in args ] # If we have an empty list, do nothing. for i in args: if not i: return rv # The index of the current element, for each member of args. cur = [ 0 for i in args ] # The parts of the current element. offset = [ i[0][0] for i in args ] size = [ i[0][1] for i in args ] index = [ i[0][2] for i in args ] # Cache it here to save function calls. nargs = len(args) loop = True while loop: # Figure out the minimum size. minsize = min(size) for i in xrange(nargs): # Create an entry of minsize. rv[i].append((offset[i], minsize, index[i])) # Adjust the offset and size. offset[i] += minsize size[i] -= minsize # If the size fell to 0, then load the next element from # the arguments into offset, size, index. If we can't # we're done. if size[i] == 0: cur[i] += 1 if cur[i] >= len(args[i]): loop = False else: offset[i], size[i], index[i] = args[i][cur[i]] return rv cpdef blit(TextureGrid tg, double sx, double sy, Matrix transform, double alpha, double over, Environ environ, bint nearest): """ This draws texgrid `tg` to the screen. `sx` and `sy` are offsets from the upper-left corner of the screen. `transform` is the transform to apply to the texgrid, when going from texgrid coordinates to screen coordinates. `alpha` is the alpha multiplier applied, from 0.0 to 1.0. `over` is the over blending factor. """ cdef int x, y cdef int texx, texy, texw, texh tg.make_ready(nearest) environ.blit() environ.set_color(alpha, alpha, alpha, over * alpha) y = 0 for texy, texh, rowindex in tg.rows: x = 0 for texx, texw, colindex in tg.columns: tex = tg.tiles[rowindex][colindex] draw_rectangle( environ, sx, sy, x, y, texw, texh, transform, tex, texx, texy, None, 0, 0, None, 0, 0, ) x += texw y += texh cpdef blend(TextureGrid tg0, TextureGrid tg1, double sx, double sy, Matrix transform, double alpha, double over, double fraction, Environ environ, bint nearest): """ Blends two textures to the screen. `tg0` and `tg1` are the texture. `sx` and `sy` are the offsets from the upper-left corner of the screen. `transform` is the transform to apply to the texgrid, when going from texgrid coordinates to screen coordinates. `alpha` is the alpha multiplier applied, from 0.0 to 1.0. `over` is the over blending factor. `fraction` is the fraction of the second texture to show. """ tg0.make_ready(nearest) tg1.make_ready(nearest) environ.blend(fraction) environ.set_color(alpha, alpha, alpha, over * alpha) y = 0 rows0, rows1 = align_axes(tg0.rows, tg1.rows) cols0, cols1 = align_axes(tg0.columns, tg1.columns) # t0 = texture 0, t1 = texture 1. # x, y - index into the texture. # w, h - width and height to draw. # ri, ci - row index, column index in tiles. for (t0y, t0h, t0ri), (t1y, t1h, t1ri) in zip(rows0, rows1): x = 0 for (t0x, t0w, t0ci), (t1x, t1w, t1ci) in zip(cols0, cols1): t0 = tg0.tiles[t0ri][t0ci] t1 = tg1.tiles[t1ri][t1ci] draw_rectangle( environ, sx, sy, x, y, t0w, t0h, transform, t0, t0x, t0y, t1, t1x, t1y, None, 0, 0, ) x += t0w y += t0h cpdef imageblend(TextureGrid tg0, TextureGrid tg1, TextureGrid tg2, double sx, double sy, Matrix transform, double alpha, double over, double fraction, int ramp, Environ environ, bint nearest): """ This uses texture 0 to control the blending of tetures 1 and 2 to the screen. `tg0`, `tg1`, and `tg2` are the textures. `sx` and `sy` are the offsets from the upper-left corner of the screen. `transform` is the transform to apply to the texgrid, when going from texgrid coordinates to screen coordinates. `over` is the over blending factor. `additive` is the additive blending factor, which is 1.0 for fully additive, and 0.0 for fully over blending. `fraction` is the fraction of the second texture to show. `ramp` is the length of the blending ramp to use. """ tg0.make_ready(nearest) tg1.make_ready(nearest) tg2.make_ready(nearest) environ.imageblend(fraction, ramp) environ.set_color(alpha, alpha, alpha, over * alpha) y = 0 rows0, rows1, rows2 = align_axes(tg0.rows, tg1.rows, tg2.rows) cols0, cols1, cols2 = align_axes(tg0.columns, tg1.columns, tg2.columns) # t0 = texture 0, t1 = texture 1. # x, y - index into the texture. # w, h - width and height to draw. # ri, ci - row index, column index in tiles. for (t0y, t0h, t0ri), (t1y, t1h, t1ri), (t2y, t2h, t2ri) in zip(rows0, rows1, rows2): x = 0 for (t0x, t0w, t0ci), (t1x, t1w, t1ci), (t2x, t2w, t2ci) in zip(cols0, cols1, cols2): t0 = tg0.tiles[t0ri][t0ci] t1 = tg1.tiles[t1ri][t1ci] t2 = tg2.tiles[t2ri][t2ci] draw_rectangle( environ, sx, sy, x, y, t0w, t0h, transform, t0, t0x, t0y, t1, t1x, t1y, t2, t2x, t2y, ) x += t0w y += t0h def premultiply( object pysurf, int x, int y, int w, int h, bint border_left, bint border_top, bint border_right, bint border_bottom): """ Creates a string containing the premultiplied image data for for the (x, y, w, h) box inside pysurf. The various border_ parameters control the addition of a border on the sides. """ # Iterator y and x. cdef int ix, iy # Does the original image have an alpha channel? cdef unsigned int alpha if pysurf.get_masks()[3]: alpha = True else: alpha = False # Allocate an uninitialized string. rv = PyString_FromStringAndSize(NULL, w * h * 4) # Out is where we put the output. cdef unsigned char *out = rv # The pixels in the source image. cdef unsigned char *pixels cdef unsigned char *pixels_end cdef SDL_Surface *surf # Pointer to the current pixel. cdef unsigned char *p # Pointer to the current output pixel. cdef unsigned char *op # Pointer to the row end. cdef unsigned char *pend # alpha value. cdef unsigned int a # pixel pointer. cdef unsigned int *pp cdef unsigned int *ppend surf = PySurface_AsSurface(pysurf) pixels = surf.pixels # The start of the pixel data to read out. pixels += y * surf.pitch pixels += x * 4 # A pointer to the row past the last pixel data we read out. pixels_end = pixels + h * surf.pitch # A pointer to the output byte to write. op = out with nogil: while pixels < pixels_end: # The start and end of the current row. p = pixels pend = p + w * 4 # Advance to the next row. pixels += surf.pitch if tex_format == GL_RGBA: # RGBA path. if alpha: while p < pend: a = p[3] op[0] = (p[0] * a + a) >> 8 op[1] = (p[1] * a + a) >> 8 op[2] = (p[2] * a + a) >> 8 op[3] = a p += 4 op += 4 else: while p < pend: ( op)[0] = ( p)[0] op[3] = 255 p += 4 op += 4 else: # BGRA Path. if alpha: while p < pend: a = p[3] op[0] = (p[2] * a + a) >> 8 # b op[1] = (p[1] * a + a) >> 8 # g op[2] = (p[0] * a + a) >> 8 # r op[3] = a p += 4 op += 4 else: while p < pend: op[0] = p[2] # b op[1] = p[1] # g op[2] = p[0] # r op[3] = 0xff # a p += 4 op += 4 if border_left: pp = (out) ppend = pp + w * h while pp < ppend: pp[0] = pp[1] pp += w if border_right: pp = (out) pp += w - 2 ppend = pp + w * h while pp < ppend: pp[1] = pp[0] pp += w if border_top: pp = (out) ppend = pp + w while pp < ppend: pp[0] = pp[w] pp += 1 if border_bottom: pp = (out) pp += (h - 2) * w ppend = pp + w while pp < ppend: pp[w] = pp[0] pp += 1 return rv def load_premultiplied(data, width, height, update, rtt): cdef char *pixels cdef GLenum internalformat cdef GLenum format cdef GLenum type if rtt: internalformat = rtt_internalformat format = rtt_format type = rtt_type else: internalformat = tex_internalformat format = tex_format type = tex_type if data: pixels = data else: pixels = NULL if update: glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, pixels) else: glTexImage2D( GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, pixels) cdef void draw_rectangle( Environ environ, double sx, double sy, double x, double y, double w, double h, Matrix transform, TextureCore tex0, float tex0x, float tex0y, TextureCore tex1, float tex1x, float tex1y, TextureCore tex2, float tex2x, float tex2y, ): """ This draws a rectangle (textured with up to three textures) to the screen. `sx`, `sy` The location in the untransformed screen coordinate of the upper-left corner of the drawing region. (Think of this as an offset that is applied to coordinates.) `x`, `y` The location in the transformed coordinates to draw the upper-left corner of the texture. `w`, `h` The width and height of texture. `tex0` The texture to bind to texture unit 0. `tex0x`, `tex0y` The coordinates within that texture of the upper-left corner. `tex1...`, `tex2...` Same, but for the other two textures. `toff_...` Texture offset to apply to the given side of the texture. """ # Do we have the given texture? cdef int has_tex0, has_tex1, has_tex2 # Texture coordinates. cdef double t0u0 = 0, t0v0 = 0, t0u1 = 0, t0v1 = 0 cdef double t1u0 = 0, t1v0 = 0, t1u1 = 0, t1v1 = 0 cdef double t2u0 = 0, t2v0 = 0, t2u1 = 0, t2v1 = 0 # Pull apart the transform. cdef double xdx = transform.xdx cdef double xdy = transform.xdy cdef double ydx = transform.ydx cdef double ydy = transform.ydy # Transform the vertex coordinates to screen-space. cdef double x0 = (x + 0) * xdx + (y + 0) * xdy + sx cdef double y0 = (x + 0) * ydx + (y + 0) * ydy + sy cdef double x1 = (x + w) * xdx + (y + 0) * xdy + sx cdef double y1 = (x + w) * ydx + (y + 0) * ydy + sy cdef double x2 = (x + 0) * xdx + (y + h) * xdy + sx cdef double y2 = (x + 0) * ydx + (y + h) * ydy + sy cdef double x3 = (x + w) * xdx + (y + h) * xdy + sx cdef double y3 = (x + w) * ydx + (y + h) * ydy + sy # Compute the texture coordinates, and set up the textures. cdef double xadd, yadd, xmul, ymul if tex0 is not None: has_tex0 = 1 glActiveTextureARB(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, tex0.number) xadd = tex0.xadd yadd = tex0.yadd xmul = tex0.xmul ymul = tex0.ymul t0u0 = xadd + xmul * (tex0x + 0) t0u1 = xadd + xmul * (tex0x + w) t0v0 = yadd + ymul * (tex0y + 0) t0v1 = yadd + ymul * (tex0y + h) else: has_tex0 = 0 if tex1 is not None: has_tex1 = 1 glActiveTextureARB(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, tex1.number) xadd = tex1.xadd yadd = tex1.yadd xmul = tex1.xmul ymul = tex1.ymul t1u0 = xadd + xmul * (tex1x + 0) t1u1 = xadd + xmul * (tex1x + w) t1v0 = yadd + ymul * (tex1y + 0) t1v1 = yadd + ymul * (tex1y + h) else: has_tex1 = 0 if RENPY_THIRD_TEXTURE: if tex2 is not None: has_tex2 = 1 glActiveTextureARB(GL_TEXTURE2) glBindTexture(GL_TEXTURE_2D, tex2.number) xadd = tex2.xadd yadd = tex2.yadd xmul = tex2.xmul ymul = tex2.ymul t2u0 = xadd + xmul * (tex2x + 0) t2u1 = xadd + xmul * (tex2x + w) t2v0 = yadd + ymul * (tex2y + 0) t2v1 = yadd + ymul * (tex2y + h) else: has_tex2 = 0 # Now, actually draw the textured rectangle. cdef GLfloat tex0coords[8] cdef GLfloat tex1coords[8] cdef GLfloat tex2coords[8] cdef GLfloat vcoords[8] if has_tex0: tex0coords[0] = t0u0 tex0coords[1] = t0v0 tex0coords[2] = t0u1 tex0coords[3] = t0v0 tex0coords[4] = t0u0 tex0coords[5] = t0v1 tex0coords[6] = t0u1 tex0coords[7] = t0v1 environ.set_texture(0, tex0coords) else: environ.set_texture(0, NULL) if has_tex1: tex1coords[0] = t1u0 tex1coords[1] = t1v0 tex1coords[2] = t1u1 tex1coords[3] = t1v0 tex1coords[4] = t1u0 tex1coords[5] = t1v1 tex1coords[6] = t1u1 tex1coords[7] = t1v1 environ.set_texture(1, tex1coords) else: environ.set_texture(1, NULL) if RENPY_THIRD_TEXTURE: if has_tex2: tex2coords[0] = t2u0 tex2coords[1] = t2v0 tex2coords[2] = t2u1 tex2coords[3] = t2v0 tex2coords[4] = t2u0 tex2coords[5] = t2v1 tex2coords[6] = t2u1 tex2coords[7] = t2v1 environ.set_texture(2, tex2coords) else: environ.set_texture(2, NULL) vcoords[0] = x0 vcoords[1] = y0 vcoords[2] = x1 vcoords[3] = y1 vcoords[4] = x2 vcoords[5] = y2 vcoords[6] = x3 vcoords[7] = y3 environ.set_vertex(vcoords) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)