diff options
Diffstat (limited to 'unrpyc/renpy/display/swdraw.py')
-rw-r--r-- | unrpyc/renpy/display/swdraw.py | 1102 |
1 files changed, 1102 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/swdraw.py b/unrpyc/renpy/display/swdraw.py new file mode 100644 index 0000000..d3ecb51 --- /dev/null +++ b/unrpyc/renpy/display/swdraw.py @@ -0,0 +1,1102 @@ +# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us> +# +# 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. + +import renpy.display +import pygame +import math +import weakref +import time +import os + +from renpy.display.render import blit_lock, IDENTITY, BLIT, DISSOLVE, IMAGEDISSOLVE, PIXELLATE + +# A map from cached surface to rle version of cached surface. +rle_cache = weakref.WeakKeyDictionary() + +class Clipper(object): + """ + This is used to calculate the clipping rectangle and update rectangles + used for a particular draw of the screen. + """ + + def __init__(self): + + # Lists of (x0, y0, x1, y1, clip, surface, transform) tuples, + # representing how a displayable is drawn to the screen. + self.blits = [ ] + self.old_blits = [ ] + + # Sets of (x0, y0, x1, y1) tuples, representing areas that + # aren't part of any displayable. + self.forced = set() + self.old_forced = set() + + # The set of surfaces that have been mutated recently. + self.mutated = set() + + def compute(self, full_redraw): + """ + This returns a clipping rectangle, and a list of update rectangles + that cover the changes between the old and new frames. + """ + + # First, get things out of the fields, and update them. This + # allows us to just return without having to do any cleanup + # code. + bl0 = self.old_blits + bl1 = self.blits + old_forced = self.old_forced + forced = self.forced + mutated = self.mutated + + self.old_blits = bl1 + self.blits = [ ] + self.old_forced = forced + self.forced = set() + self.mutated = set() + + sw = renpy.config.screen_width + sh = renpy.config.screen_height + sa = sw * sh + + # A tuple representing the size of the fullscreen. + fullscreen = (0, 0, sw, sh) + + # Check to see if a full redraw has been forced, and return + # early. + if full_redraw: + return fullscreen, [ fullscreen ] + + # Quick checks to see if a dissolve is happening, or something like + # that. + changes = forced | old_forced + + if fullscreen in changes: + return fullscreen, [ fullscreen ] + + # Compute the differences between the two sets, and add those + # to changes. + i0 = 0 + i1 = 0 + bl1set = set(bl1) + + while True: + if i0 >= len(bl0) or i1 >= len(bl1): + break + + b0 = bl0[i0] + b1 = bl1[i1] + + if b0 == b1: + if id(b0[5]) in mutated: + changes.add(b0[:5]) + + i0 += 1 + i1 += 1 + + elif b0 not in bl1set: + changes.add(b0[:5]) + i0 += 1 + + else: + changes.add(b1[:5]) + i1 += 1 + + changes.update(i[:5] for i in bl0[i0:]) + changes.update(i[:5] for i in bl1[i1:]) + + # No changes? Quit. + if not changes: + return None, [ ] + + # Compute the sizes of the updated rectangles. + sized = [ ] + + for x0, y0, x1, y1, (sx0, sy0, sx1, sy1) in changes: + + # Round up by a pixel, to prevent visual artifacts when scaled down. + x1 += 1 + y1 += 1 + + if x0 < sx0: + x0 = sx0 + if y0 < sy0: + y0 = sy0 + if x1 > sx1: + x1 = sx1 + if y1 > sy1: + y1 = sy1 + + w = x1 - x0 + h = y1 - y0 + + if w <= 0 or h <= 0: + continue + + area = w * h + + if area >= sa: + return fullscreen, [ fullscreen ] + + sized.append((area, x0, y0, x1, y1)) + + sized.sort() + + # The list of non-contiguous updates. + noncont = [ ] + + # The total area of noncont. + nca = 0 + + # Pick the largest area, merge with all overlapping smaller areas, repeat + # until no merge possible. + while sized: + area, x0, y0, x1, y1 = sized.pop() + + + merged = False + + if nca + area >= sa: + return (0, 0, sw, sh), [ (0, 0, sw, sh) ] + + i = 0 + + while i < len(sized): + _iarea, ix0, iy0, ix1, iy1 = sized[i] + + if (x0 <= ix0 <= x1 or x0 <= ix1 <= x1) and \ + (y0 <= iy0 <= y1 or y0 <= iy1 <= y1): + + merged = True + x0 = min(x0, ix0) + x1 = max(x1, ix1) + y0 = min(y0, iy0) + y1 = max(y1, iy1) + + area = (x1 - x0) * (y1 - y0) + + sized.pop(i) + + else: + i += 1 + + if merged: + sized.append((area, x0, y0, x1, y1)) + else: + noncont.append((x0, y0, x1, y1)) + nca += area + + if not noncont: + return None, [ ] + + x0, y0, x1, y1 = noncont.pop() + x0 = int(x0) + y0 = int(y0) + x1 = int(math.ceil(x1)) + y1 = int(math.ceil(y1)) + + # A list of (x, y, w, h) tuples for each update. + updates = [ (x0, y0, x1 - x0, y1 - y0) ] + + for ix0, iy0, ix1, iy1 in noncont: + + ix0 = int(ix0) + iy0 = int(iy0) + ix1 = int(math.ceil(ix1)) + iy1 = int(math.ceil(iy1)) + + x0 = min(x0, ix0) + y0 = min(y0, iy0) + x1 = max(x1, ix1) + y1 = max(y1, iy1) + + updates.append((ix0, iy0, ix1 - ix0, iy1 - iy0)) + + return (x0, y0, x1 - x0, y1 - y0), updates + +clippers = [ Clipper() ] + +def surface(w, h, alpha): + """ + Creates a surface that shares a pixel format with the screen. The created + surface will + """ + + if alpha: + rv = pygame.Surface((w + 4, h + 4), pygame.SRCALPHA) + else: + rv = pygame.Surface((w + 4, h + 4), 0) + + return rv.subsurface((2, 2, w, h)) + +def copy_surface(surf): + w, h = surf.get_size() + rv = surface(w, h, True) + + renpy.display.accelerator.nogil_copy(surf, rv) # @UndefinedVariable + return rv + +def draw_special(what, dest, x, y): + """ + This handles the special drawing operations, such as dissolve and + image dissolve. `x` and `y` are the offsets of the thing to be drawn + relative to the destination rectangle, and are always negative. + """ + + dw, dh = dest.get_size() + + w = min(dw, what.width + x) + h = min(dh, what.height + y) + + if w <= 0 or h <= 0: + return + + if what.operation == DISSOLVE: + + bottom = what.children[0][0].render_to_texture(True) + top = what.children[1][0].render_to_texture(True) + + if what.operation_alpha: + target = surface(w, h, True) + else: + target = dest.subsurface((0, 0, w, h)) + + renpy.display.module.blend( + bottom.subsurface((-x, -y, w, h)), + top.subsurface((-x, -y, w, h)), + target, + int(what.operation_complete * 255)) + + if what.operation_alpha: + dest.blit(target, (0, 0)) + + elif what.operation == IMAGEDISSOLVE: + + image = what.children[0][0].render_to_texture(True) + bottom = what.children[1][0].render_to_texture(True) + top = what.children[2][0].render_to_texture(True) + + if what.operation_alpha: + target = surface(w, h, True) + else: + target = dest.subsurface((0, 0, w, h)) + + ramplen = what.operation_parameter + + ramp = "\x00" * 256 + + for i in range(0, ramplen): + ramp += chr(255 * i / ramplen) + + ramp += "\xff" * 256 + + step = int( what.operation_complete * (256 + ramplen) ) + ramp = ramp[step:step+256] + + renpy.display.module.imageblend( + bottom.subsurface((-x, -y, w, h)), + top.subsurface((-x, -y, w, h)), + target, + image.subsurface((-x, -y, w, h)), + ramp) + + if what.operation_alpha: + dest.blit(target, (0, 0)) + + elif what.operation == PIXELLATE: + + surf = what.children[0][0].render_to_texture(False) + + px = what.operation_parameter + + renpy.display.module.pixellate( + surf.subsurface((-x, -y, w, h)), + dest.subsurface((0, 0, w, h)), + px, px, px, px) + + else: + raise Exception("Unknown operation: %d" % what.operation) + + +def draw(dest, clip, what, xo, yo, screen): + """ + This is the simple draw routine, which only works when alpha is 1.0 + and the matrices are None. If those aren't the case, draw_complex + is used instead. + + `dest` - Either a destination surface, or a clipper. + `clip` - If None, we should draw. Otherwise we should clip, and this is + the rectangle to clip to. + `what` - The Render or Surface we're drawing to. + `xo` - The X offset. + `yo` - The Y offset. + `screen` - True if this is a blit to the screen, False otherwise. + """ + + if not isinstance(what, renpy.display.render.Render): + + # Pixel-Aligned blit. + if isinstance(xo, int) and isinstance(yo, int): + if screen: + what = rle_cache.get(what, what) + + if clip: + w, h = what.get_size() + dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None)) + else: + try: + blit_lock.acquire() + dest.blit(what, (xo, yo)) + finally: + blit_lock.release() + + # Subpixel blit. + else: + if clip: + w, h = what.get_size() + dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None)) + else: + renpy.display.module.subpixel(what, dest, xo, yo) + + return + + # Deal with draw functions. + if what.operation != BLIT: + + xo = int(xo) + yo = int(yo) + + if clip: + dx0, dy0, dx1, dy1 = clip + dw = dx1 - dx0 + dh = dy1 - dy0 + else: + dw, dh = dest.get_size() + + if xo >= 0: + newx = 0 + subx = xo + else: + newx = xo + subx = 0 + + if yo >= 0: + newy = 0 + suby = yo + else: + newy = yo + suby = 0 + + if subx >= dw or suby >= dh: + return + + # newx and newy are the offset of this render relative to the + # subsurface. They can only be negative or 0, as otherwise we + # would make a smaller subsurface. + + subw = min(dw - subx, what.width + newx) + subh = min(dh - suby, what.height + newy) + + if subw <= 0 or subh <= 0: + return + + if clip: + dest.forced.add((subx, suby, subx + subw, suby + subh, clip)) + else: + newdest = dest.subsurface((subx, suby, subw, subh)) + # what.draw_func(newdest, newx, newy) + draw_special(what, newdest, newx, newy) + + + return + + # Deal with clipping, if necessary. + if what.clipping: + + if clip: + cx0, cy0, cx1, cy1 = clip + + cx0 = max(cx0, xo) + cy0 = max(cy0, yo) + cx1 = min(cx1, xo + what.width) + cy1 = min(cy1, yo + what.height) + + if cx0 > cx1 or cy0 > cy1: + return + + clip = (cx0, cy0, cx1, cy1) + + dest.forced.add(clip + (clip,)) + return + + else: + + # After this code, x and y are the coordinates of the subsurface + # relative to the destination. xo and yo are the offset of the + # upper-left corner relative to the subsurface. + + if xo >= 0: + x = xo + xo = 0 + else: + x = 0 + # xo = xo + + if yo >= 0: + y = yo + yo = 0 + else: + y = 0 + # yo = yo + + dw, dh = dest.get_size() + + width = min(dw - x, what.width + xo) + height = min(dh - y, what.height + yo) + + if width < 0 or height < 0: + return + + dest = dest.subsurface((x, y, width, height)) + + # Deal with alpha and transforms by passing them off to draw_transformed. + if what.alpha != 1 or (what.forward is not None and what.forward is not IDENTITY): + for child, cxo, cyo, _focus, _main in what.visible_children: + draw_transformed(dest, clip, child, xo + cxo, yo + cyo, + what.alpha, what.forward, what.reverse) + return + + for child, cxo, cyo, _focus, _main in what.visible_children: + draw(dest, clip, child, xo + cxo, yo + cyo, screen) + +def draw_transformed(dest, clip, what, xo, yo, alpha, forward, reverse): + + # If our alpha has hit 0, don't do anything. + if alpha <= 0.003: # (1 / 256) + return + + if forward is None: + forward = IDENTITY + reverse = IDENTITY + + if not isinstance(what, renpy.display.render.Render): + + # Figure out where the other corner of the transformed surface + # is on the screen. + sw, sh = what.get_size() + if clip: + + dx0, dy0, dx1, dy1 = clip + dw = dx1 - dx0 + dh = dy1 - dy0 + + else: + dw, dh = dest.get_size() + + x0, y0 = 0.0, 0.0 + x1, y1 = reverse.transform(sw, 0.0) + x2, y2 = reverse.transform(sw, sh) + x3, y3 = reverse.transform(0.0, sh) + + minx = math.floor(min(x0, x1, x2, x3) + xo) + maxx = math.ceil(max(x0, x1, x2, x3) + xo) + miny = math.floor(min(y0, y1, y2, y3) + yo) + maxy = math.ceil(max(y0, y1, y2, y3) + yo) + + if minx < 0: + minx = 0 + if miny < 0: + miny = 0 + + if maxx > dw: + maxx = dw + if maxy > dh: + maxy = dh + + if minx > dw or miny > dh or maxx < 0 or maxy < 0: + return + + cx, cy = forward.transform(minx - xo, miny - yo) + + if clip: + + dest.blits.append( + (minx, miny, maxx + dx0, maxy + dy0, clip, what, + (cx, cy, + forward.xdx, forward.ydx, + forward.xdy, forward.ydy, + alpha))) + + else: + + dest = dest.subsurface((minx, miny, maxx - minx, maxy - miny)) + + renpy.display.module.transform( + what, dest, + cx, cy, + forward.xdx, forward.ydx, + forward.xdy, forward.ydy, + alpha, True) + + return + + if what.clipping: + + if reverse.xdy or reverse.ydx: + draw_transformed(dest, clip, what.pygame_surface(True), xo, yo, alpha, forward, reverse) + return + + width = what.width * reverse.xdx + height = what.height * reverse.ydy + + if clip: + cx0, cy0, cx1, cy1 = clip + + cx0 = max(cx0, xo) + cy0 = max(cy0, yo) + cx1 = min(cx1, xo + width) + cy1 = min(cy1, yo + height) + + if cx0 > cx1 or cy0 > cy1: + return + + clip = (cx0, cy0, cx1, cy1) + + dest.forced.add(clip + (clip,)) + return + + else: + + # After this code, x and y are the coordinates of the subsurface + # relative to the destination. xo and yo are the offset of the + # upper-left corner relative to the subsurface. + + if xo >= 0: + x = xo + xo = 0 + else: + x = 0 + # xo = xo + + if yo >= 0: + y = yo + yo = 0 + else: + y = 0 + # yo = yo + + dw, dh = dest.get_size() + + width = min(dw - x, width + xo) + height = min(dh - y, height + yo) + + if width < 0 or height < 0: + return + + dest = dest.subsurface((x, y, width, height)) + + if what.draw_func or what.operation != BLIT: + child = what.pygame_surface(True) + draw_transformed(dest, clip, child, xo, yo, alpha, forward, reverse) + return + + for child, cxo, cyo, _focus, _main in what.visible_children: + + cxo, cyo = reverse.transform(cxo, cyo) + + if what.forward: + child_forward = forward * what.forward + child_reverse = what.reverse * reverse + else: + child_forward = forward + child_reverse = reverse + + draw_transformed(dest, clip, child, xo + cxo, yo + cyo, alpha * what.alpha, child_forward, child_reverse) + + + +def do_draw_screen(screen_render, full_redraw, swdraw): + """ + Draws the render produced by render_screen to the screen. + """ + + yoffset = xoffset = 0 + + screen_render.is_opaque() + + clip = (xoffset, yoffset, xoffset + screen_render.width, yoffset + screen_render.height) + clipper = clippers[0] + + draw(clipper, clip, screen_render, xoffset, yoffset, True) + + cliprect, updates = clipper.compute(full_redraw) + + if cliprect is None: + return [ ] + + x, y, _w, _h = cliprect + + dest = swdraw.window.subsurface(cliprect) + draw(dest, None, screen_render, -x, -y, True) + + return updates + + +class SWDraw(object): + """ + This uses the software renderer to draw to the screen. + """ + + def __init__(self): + self.display_info = None + + self.reset() + + def reset(self): + + # Should we draw the screen? + self.suppressed_blit = False + + # The earliest time at which the next frame can be redrawn. + self.next_frame = 0 + + # Mouse re-drawing. + self.mouse_location = None + self.mouse_backing = None + self.mouse_backing_pos = None + self.mouse_info = None + + + # Is the mouse currently visible? + self.mouse_old_visible = None + + # This is used to cache the surface->texture operation. + self.texture_cache = weakref.WeakKeyDictionary() + + # This is used to display video to the screen. + self.fullscreen_surface = None + + # Info. + self.info = { "renderer" : "sw", "resizable" : False } + + pygame.display.init() + renpy.display.interface.post_init() + + if self.display_info is None: + self.display_info = pygame.display.Info() + + # The scale factor we use for this display. + self.scale_factor = 1.0 + + # Should we scale fast, or scale good-looking? + self.scale_fast = "RENPY_SCALE_FAST" in os.environ + + # The screen returned to us from pygame. + self.screen = None + + # The window that we render into, if not the screen. This has a + # 1px border around it iff we're scaling. + self.window = None + + def set_mode(self, virtual_size, physical_size, fullscreen): + + # Reset before resize. + renpy.display.interface.kill_textures_and_surfaces() + self.reset() + + width, height = virtual_size + + # Set up scaling, if necessary. + screen_width = self.display_info.current_w + screen_height = self.display_info.current_h + + if not fullscreen: + screen_height -= 102 + screen_width -= 102 + + scale_factor = min(1.0 * screen_width / width, 1.0 * screen_height / height, 1.0) + if "RENPY_SCALE_FACTOR" in os.environ: + scale_factor = float(os.environ["RENPY_SCALE_FACTOR"]) + self.scale_factor = scale_factor + + # Figure out the fullscreen info. + if fullscreen: + fsflag = pygame.FULLSCREEN + else: + fsflag = 0 + + # If a window exists of the right size and flags, use it. Otherwise, + # make our own window. + old_screen = pygame.display.get_surface() + + scaled_width = int(width * scale_factor) + scaled_height = int(height * scale_factor) + + if ((old_screen is not None) and + (old_screen.get_size() == (scaled_width, scaled_height)) and + (old_screen.get_flags() & pygame.FULLSCREEN == fsflag)): + + self.screen = old_screen + + else: + self.screen = pygame.display.set_mode((scaled_width, scaled_height), fsflag, 32) + + if scale_factor != 1.0: + self.window = surface(width, height, True) + else: + self.window = self.screen + + renpy.display.pgrender.set_rgba_masks() + + # Should we redraw the screen from scratch? + self.full_redraw = True + + # The surface used to display fullscreen video. + self.fullscreen_surface = self.screen + + # Reset this on a mode change. + self.mouse_location = None + self.mouse_backing = None + self.mouse_backing_pos = None + self.mouse_info = None + + return True + + # private + def show_mouse(self, pos, info): + """ + Actually shows the mouse. + """ + + self.mouse_location = pos + self.mouse_info = info + + mxo, myo, tex = info + + mx, my = pos + mw, mh = tex.get_size() + + bx = mx - mxo + by = my - myo + + self.mouse_backing_pos = (bx, by) + self.mouse_backing = surface(mw, mh, False) + self.mouse_backing.blit(self.window, (0, 0), (bx, by, mw, mh)) + + self.screen.blit(tex, (bx, by)) + + return bx, by, mw, mh + + # private + def hide_mouse(self): + """ + Actually hides the mouse. + """ + + size = self.mouse_backing.get_size() + self.screen.blit(self.mouse_backing, self.mouse_backing_pos) + + rv = self.mouse_backing_pos + size + + self.mouse_backing = None + self.mouse_backing_pos = None + self.mouse_location = None + + return rv + + # private + def draw_mouse(self, show_mouse): + """ + This draws the mouse to the screen, if necessary. It uses the + buffer to minimize the amount of the screen that needs to be + drawn, and only redraws if the mouse has actually been moved. + """ + + hardware, x, y, tex = renpy.game.interface.get_mouse_info() + + if self.mouse_old_visible != hardware: + pygame.mouse.set_visible(hardware) + self.mouse_old_visible = hardware + + # The rest of this is for the software mouse. + + if self.suppressed_blit: + return [ ] + + if not show_mouse: + tex = None + + info = (x, y, tex) + pos = pygame.mouse.get_pos() + + if (pos == self.mouse_location and tex and info == self.mouse_info): + return [ ] + + updates = [ ] + + if self.mouse_location: + updates.append(self.hide_mouse()) + + if tex and pos and renpy.game.interface.focused: + updates.append(self.show_mouse(pos, info)) + + return updates + + def update_mouse(self): + """ + Draws the mouse, and then updates the screen. + """ + + updates = self.draw_mouse(True) + + if updates: + pygame.display.update(updates) + + def mouse_event(self, ev): + x, y = getattr(ev, 'pos', pygame.mouse.get_pos()) + + x /= self.scale_factor + y /= self.scale_factor + + return x, y + + def get_mouse_pos(self): + x, y = pygame.mouse.get_pos() + + x /= self.scale_factor + y /= self.scale_factor + + return x, y + + + def screenshot(self, surftree, fullscreen_video): + """ + Returns a pygame surface containing a screenshot. + """ + + return self.window + + def should_redraw(self, needs_redraw, first_pass): + """ + Uses the framerate to determine if we can and should redraw. + """ + + if not needs_redraw: + return False + + framerate = renpy.config.framerate + + if framerate is None: + return True + + next_frame = self.next_frame + now = pygame.time.get_ticks() + + frametime = 1000.0 / framerate + + # Handle timer rollover. + if next_frame > now + frametime: + next_frame = now + + # It's not yet time for the next frame. + if now < next_frame and not first_pass: + return False + + # Otherwise, it is. Schedule the next frame. + # if next_frame + frametime < now: + next_frame = now + frametime + # else: + # next_frame += frametime + + self.next_frame = next_frame + + return True + + + def draw_screen(self, surftree, fullscreen_video): + """ + Draws the screen. + """ + + if not fullscreen_video: + + updates = [ ] + + updates.extend(self.draw_mouse(False)) + + damage = do_draw_screen(surftree, self.full_redraw, self) + + if damage: + updates.extend(damage) + + self.full_redraw = False + + if self.window is self.screen: + + updates.extend(self.draw_mouse(True)) + pygame.display.update(updates) + + else: + + if self.scale_fast: + pygame.transform.scale(self.window, self.screen.get_size(), self.screen) + else: + renpy.display.scale.smoothscale(self.window, self.screen.get_size(), self.screen) + + self.draw_mouse(True) + pygame.display.flip() + + else: + pygame.display.flip() + self.full_redraw = True + + self.suppressed_blit = fullscreen_video + + + def render_to_texture(self, render, alpha): + + rv = surface(render.width, render.height, alpha) + draw(rv, None, render, 0, 0, False) + + return rv + + def is_pixel_opaque(self, what, x, y): + + if x < 0 or y < 0 or x >= what.width or y >= what.height: + return 0 + + for (child, xo, yo, _focus, _main) in what.visible_children: + cx = x - xo + cy = y - yo + + if what.forward: + cx, cy = what.forward.transform(cx, cy) + + + if isinstance(child, renpy.display.render.Render): + if self.is_pixel_opaque(child, x, y): + return True + + else: + cx = int(cx) + cy = int(cy) + + cw, ch = child.get_size() + if cx >= cw or cy >= ch: + return False + + + + if not child.get_masks()[3] or child.get_at((cx, cy))[3]: + return True + + return False + + + def mutated_surface(self, surf): + """ + Called to indicate that the given surface has changed. + """ + + for i in clippers: + i.mutated.add(id(surf)) + + if surf in rle_cache: + del rle_cache[surf] + + + def load_texture(self, surf, transient=False): + """ + Creates a texture from the surface. In the software implementation, + the only difference between a texture and a surface is that a texture + is in the RLE cache. + """ + + surf = copy_surface(surf) + self.mutated_surface(surf) + + if transient: + return surf + + if renpy.game.less_memory: + return surf + + if surf not in rle_cache: + rle_surf = copy_surface(surf) + rle_surf.set_alpha(255, pygame.RLEACCEL) + self.mutated_surface(rle_surf) + + rle_cache[surf] = rle_surf + + return surf + + def solid_texture(self, w, h, color): + """ + Creates a texture filled to the edges with color. + """ + + surf = surface(w + 4, h + 4, True) + surf.fill(color) + self.mutated_surface(surf) + + surf = surf.subsurface((2, 2, w, h)) + + self.mutated_surface(surf) + return surf + + + def free_memory(self): + """ + Frees up memory. + """ + + rle_cache.clear() + + def deinit(self): + """ + Called when we're restarted. + """ + + renpy.display.render.free_memory() + + return + + def quit(self): #@ReservedAssignment + """ + Shuts down the drawing system. + """ + + pygame.display.quit() + + return + + def event_peek_sleep(self): + """ + Wait a little bit so the CPU doesn't speed up. + """ + + time.sleep(.0001) + + def get_physical_size(self): + """ + Return the physical width and height of the screen. + """ + return renpy.config.screen_width, renpy.config.screen_height |