1558 lines
44 KiB
Cython
1558 lines
44 KiB
Cython
#cython: profile=False
|
|
#@PydevCodeAnalysisIgnore
|
|
# Copyright 2004-2019 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.
|
|
|
|
from __future__ import print_function
|
|
|
|
DEF ANGLE = False
|
|
|
|
from libc.stdlib cimport malloc, free
|
|
from sdl2 cimport *
|
|
from gl cimport *
|
|
|
|
from pygame_sdl2 cimport *
|
|
import_pygame_sdl2()
|
|
|
|
import renpy
|
|
import pygame_sdl2 as pygame
|
|
import os
|
|
import os.path
|
|
import weakref
|
|
import array
|
|
import time
|
|
import math
|
|
|
|
cimport renpy.display.render as render
|
|
cimport gltexture
|
|
import gltexture
|
|
|
|
|
|
cdef extern from "glcompat.h":
|
|
GLenum glewInit()
|
|
GLubyte *glewGetErrorString(GLenum)
|
|
GLboolean glewIsSupported(char *)
|
|
|
|
enum:
|
|
GLEW_OK
|
|
|
|
cdef extern from "eglsupport.h":
|
|
int egl_available()
|
|
char *egl_init(SDL_Window *, int)
|
|
void egl_swap()
|
|
void egl_quit()
|
|
|
|
# EGL is a flag we check to see if we have EGL on this platform.
|
|
cdef bint EGL
|
|
EGL = egl_available()
|
|
|
|
# Cache various externals, so we can use them more efficiently.
|
|
cdef int DISSOLVE, IMAGEDISSOLVE, PIXELLATE, FLATTEN
|
|
DISSOLVE = renpy.display.render.DISSOLVE
|
|
IMAGEDISSOLVE = renpy.display.render.IMAGEDISSOLVE
|
|
PIXELLATE = renpy.display.render.PIXELLATE
|
|
FLATTEN = renpy.display.render.FLATTEN
|
|
|
|
cdef object IDENTITY
|
|
IDENTITY = renpy.display.render.IDENTITY
|
|
|
|
# Should we try to vsync?
|
|
vsync = True
|
|
|
|
# A list of frame end times, used for the same purpose.
|
|
frame_times = [ ]
|
|
|
|
cdef class GLDraw:
|
|
|
|
def __init__(self, allow_fixed=True):
|
|
|
|
# Did we do the first-time init?
|
|
self.did_init = False
|
|
|
|
# The GL environment to use.
|
|
self.environ = None
|
|
|
|
# The GL render-to-texture to use.
|
|
self.rtt = None
|
|
|
|
# The screen.
|
|
self.window = None
|
|
|
|
# The virtual size of the screen, as requested by the game.
|
|
self.virtual_size = None
|
|
|
|
# The physical size of the window we got.
|
|
self.physical_size = None
|
|
|
|
# Is the mouse currently visible?
|
|
self.mouse_old_visible = None
|
|
|
|
# The (x, y) and texture of the software mouse.
|
|
self.mouse_info = (0, 0, None)
|
|
|
|
# This is used to cache the surface->texture operation.
|
|
self.texture_cache = weakref.WeakKeyDictionary()
|
|
|
|
# The time of the last redraw.
|
|
self.last_redraw_time = 0
|
|
|
|
# The time between redraws.
|
|
self.redraw_period = .2
|
|
|
|
# Info.
|
|
self.info = { "resizable" : True, "additive" : True }
|
|
|
|
if not ANGLE:
|
|
self.info["renderer"] = "gl"
|
|
else:
|
|
self.info["renderer"] = "angle"
|
|
|
|
# Old value of fullscreen.
|
|
self.old_fullscreen = None
|
|
|
|
# We don't use a fullscreen surface, so this needs to be set
|
|
# to None at all times.
|
|
self.fullscreen_surface = None
|
|
|
|
# The display info, from pygame.
|
|
self.display_info = None
|
|
|
|
# Should we use the fast (but incorrect) dissolve mode?
|
|
self.fast_dissolve = False # renpy.android
|
|
|
|
# Should we allow the fixed-function environment?
|
|
self.allow_fixed = allow_fixed
|
|
|
|
# Did we do the texture test at least once?
|
|
self.did_texture_test = False
|
|
|
|
# Did we do a render_to_texture?
|
|
self.did_render_to_texture = False
|
|
|
|
# The DPI scale factor.
|
|
self.dpi_scale = renpy.display.interface.dpi_scale
|
|
|
|
# The number of frames to draw fast if the screen needs to be
|
|
# updated.
|
|
self.fast_redraw_frames = 0
|
|
|
|
# The queue of textures that might need to be made ready.
|
|
self.ready_texture_queue = weakref.WeakSet()
|
|
|
|
|
|
def get_texture_size(self):
|
|
"""
|
|
Returns the amount of memory locked up in textures.
|
|
"""
|
|
|
|
return gltexture.total_texture_size, gltexture.texture_count
|
|
|
|
|
|
def set_mode(self, virtual_size, physical_size, fullscreen):
|
|
"""
|
|
This changes the video mode. It also initializes OpenGL, if it
|
|
can. It returns True if it was successful, or False if OpenGL isn't
|
|
working for some reason.
|
|
"""
|
|
|
|
global vsync
|
|
|
|
cdef char *egl_error
|
|
|
|
if not renpy.config.gl_enable:
|
|
renpy.display.log.write("GL Disabled.")
|
|
return False
|
|
|
|
if self.did_init:
|
|
self.kill_textures()
|
|
|
|
if renpy.android:
|
|
fullscreen = False
|
|
|
|
# Refresh fullscreen status (e.g. user pressed Esc. in browser)
|
|
main_window = pygame.display.get_window()
|
|
self.old_fullscreen = main_window is not None and bool(main_window.get_window_flags() & (pygame.WINDOW_FULLSCREEN_DESKTOP|pygame.WINDOW_FULLSCREEN))
|
|
|
|
if fullscreen != self.old_fullscreen:
|
|
|
|
self.did_init = False
|
|
|
|
if renpy.windows and (self.old_fullscreen is not None):
|
|
renpy.display.interface.kill_textures_and_surfaces()
|
|
pygame.display.quit()
|
|
|
|
pygame.display.init()
|
|
|
|
if self.display_info is None:
|
|
self.display_info = renpy.display.get_info()
|
|
|
|
self.old_fullscreen = fullscreen
|
|
|
|
renpy.display.interface.post_init()
|
|
|
|
renpy.display.log.write("")
|
|
|
|
self.virtual_size = virtual_size
|
|
|
|
vwidth, vheight = virtual_size
|
|
pwidth, pheight = physical_size
|
|
|
|
if pwidth is None:
|
|
pwidth = vwidth
|
|
pheight = vheight
|
|
|
|
virtual_ar = 1.0 * vwidth / vheight
|
|
|
|
pwidth *= self.dpi_scale
|
|
pheight *= self.dpi_scale
|
|
|
|
window_args = { }
|
|
|
|
info = renpy.display.get_info()
|
|
|
|
old_surface = pygame.display.get_surface()
|
|
if old_surface is not None:
|
|
maximized = old_surface.get_flags() & pygame.WINDOW_MAXIMIZED
|
|
else:
|
|
maximized = False
|
|
|
|
|
|
visible_w = info.current_w
|
|
visible_h = info.current_h
|
|
|
|
if renpy.windows and renpy.windows <= (6, 1):
|
|
visible_h -= 102
|
|
|
|
bounds = pygame.display.get_display_bounds(0)
|
|
|
|
renpy.display.log.write("primary display bounds: %r", bounds)
|
|
|
|
head_full_w = bounds[2]
|
|
head_w = bounds[2] - 102
|
|
head_h = bounds[3] - 102
|
|
|
|
# Figure out the default window size.
|
|
bound_w = min(vwidth, visible_w, head_w)
|
|
bound_h = min(vwidth, visible_h, head_h)
|
|
|
|
if (not renpy.mobile) and (not maximized):
|
|
|
|
pwidth = min(visible_w, pwidth)
|
|
pheight = min(visible_h, pheight)
|
|
|
|
# The first time through.
|
|
if not self.did_init:
|
|
pwidth = min(pwidth, head_w)
|
|
pheight = min(pheight, head_h)
|
|
|
|
pwidth, pheight = min(pheight * virtual_ar, pwidth), min(pwidth / virtual_ar, pheight)
|
|
|
|
pwidth = int(round(pwidth))
|
|
pheight = int(round(pheight))
|
|
|
|
pwidth = max(pwidth, 256)
|
|
pheight = max(pheight, 256)
|
|
|
|
# Handle swap control.
|
|
target_framerate = renpy.game.preferences.gl_framerate
|
|
refresh_rate = info.refresh_rate
|
|
|
|
if not refresh_rate:
|
|
refresh_rate = 60
|
|
|
|
if target_framerate is None:
|
|
sync_frames = 1
|
|
else:
|
|
sync_frames = int(round(1.0 * refresh_rate) / target_framerate)
|
|
if sync_frames < 1:
|
|
sync_frames = 1
|
|
|
|
if renpy.game.preferences.gl_tearing:
|
|
sync_frames = -sync_frames
|
|
|
|
vsync = int(os.environ.get("RENPY_GL_VSYNC", sync_frames))
|
|
|
|
renpy.display.interface.frame_duration = 1.0 * abs(vsync) / refresh_rate
|
|
|
|
renpy.display.log.write("swap interval: %r frames", vsync)
|
|
|
|
# Set the display mode.
|
|
|
|
if ANGLE:
|
|
opengl = 0
|
|
resizable = pygame.RESIZABLE
|
|
|
|
elif EGL:
|
|
opengl = 0
|
|
resizable = 0
|
|
|
|
elif renpy.android:
|
|
opengl = pygame.OPENGL
|
|
resizable = 0
|
|
|
|
pwidth = 0
|
|
pheight = 0
|
|
|
|
elif renpy.ios:
|
|
opengl = pygame.OPENGL | pygame.WINDOW_ALLOW_HIGHDPI
|
|
resizable = pygame.RESIZABLE
|
|
|
|
pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 2);
|
|
pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 0);
|
|
|
|
pwidth = 0
|
|
pheight = 0
|
|
|
|
else:
|
|
opengl = pygame.OPENGL
|
|
|
|
if self.dpi_scale == 1.0:
|
|
opengl |= pygame.WINDOW_ALLOW_HIGHDPI
|
|
|
|
if renpy.config.gl_resize:
|
|
resizable = pygame.RESIZABLE
|
|
else:
|
|
resizable = 0
|
|
|
|
if opengl:
|
|
pygame.display.gl_set_attribute(pygame.GL_SWAP_CONTROL, vsync)
|
|
pygame.display.gl_set_attribute(pygame.GL_ALPHA_SIZE, 8)
|
|
|
|
self.window = None
|
|
|
|
if (self.window is None) and fullscreen:
|
|
try:
|
|
renpy.display.log.write("Fullscreen mode.")
|
|
self.window = pygame.display.set_mode((0, 0), pygame.WINDOW_FULLSCREEN_DESKTOP | resizable | opengl | pygame.DOUBLEBUF)
|
|
except pygame.error as e:
|
|
renpy.display.log.write("Opening in fullscreen failed: %r", e)
|
|
self.window = None
|
|
|
|
if self.window is None:
|
|
try:
|
|
renpy.display.log.write("Windowed mode.")
|
|
self.window = pygame.display.set_mode((pwidth, pheight), resizable | opengl | pygame.DOUBLEBUF, **window_args)
|
|
|
|
except pygame.error, e:
|
|
renpy.display.log.write("Could not get pygame screen: %r", e)
|
|
return False
|
|
|
|
# Use EGL to get the OpenGL ES 2 context, if necessary.
|
|
if EGL:
|
|
|
|
# This ensures the display is shown.
|
|
pygame.display.flip()
|
|
|
|
egl_error = egl_init(PyWindow_AsWindow(None), vsync)
|
|
|
|
if egl_error is not NULL:
|
|
renpy.display.log.write("Initializing EGL: %s" % egl_error)
|
|
return False
|
|
|
|
# Get the size of the created screen.
|
|
pwidth, pheight = self.window.get_size()
|
|
|
|
self.physical_size = (pwidth, pheight)
|
|
self.drawable_size = pygame.display.get_drawable_size()
|
|
|
|
renpy.display.log.write("Screen sizes: virtual=%r physical=%r drawable=%r" % (self.virtual_size, self.physical_size, self.drawable_size))
|
|
|
|
if renpy.config.adjust_view_size is not None:
|
|
view_width, view_height = renpy.config.adjust_view_size(pwidth, pheight)
|
|
else:
|
|
|
|
# Figure out the virtual box, which includes padding around
|
|
# the borders.
|
|
physical_ar = 1.0 * pwidth / pheight
|
|
|
|
ratio = min(1.0 * pwidth / vwidth, 1.0 * pheight / vheight)
|
|
|
|
view_width = max(int(vwidth * ratio), 1)
|
|
view_height = max(int(vheight * ratio), 1)
|
|
|
|
px_padding = pwidth - view_width
|
|
py_padding = pheight - view_height
|
|
|
|
x_padding = px_padding * vwidth / view_width
|
|
y_padding = py_padding * vheight / view_height
|
|
|
|
# The position of the physical screen, in virtual pixels
|
|
# (x, y, w, h). Since the physical screen will always contain
|
|
# the virtual screen, the corners are often off the virtual
|
|
# screen.
|
|
self.virtual_box = (
|
|
-x_padding / 2.0,
|
|
-y_padding / 2.0,
|
|
vwidth + x_padding,
|
|
vheight + y_padding)
|
|
|
|
# The location of the virtual screen on the physical screen, in
|
|
# physical pixels. (May not be 100% accurate, but it's good
|
|
# enough for screenshots.)
|
|
self.physical_box = (
|
|
int(px_padding / 2),
|
|
int(py_padding / 2),
|
|
pwidth - int(px_padding),
|
|
pheight - int(py_padding),
|
|
)
|
|
|
|
# Scale from the rtt size to the virtual size.
|
|
if renpy.config.use_drawable_resolution:
|
|
self.draw_per_virt = (1.0 * self.drawable_size[0] / pwidth) * (1.0 * view_width / vwidth)
|
|
else:
|
|
self.draw_per_virt = 1.0
|
|
|
|
self.virt_to_draw = Matrix2D(self.draw_per_virt, 0, 0, self.draw_per_virt)
|
|
self.draw_to_virt = Matrix2D(1.0 / self.draw_per_virt, 0, 0, 1.0 / self.draw_per_virt)
|
|
|
|
if not self.did_init:
|
|
if not self.init():
|
|
return False
|
|
|
|
if "RENPY_FAIL_" + self.info["renderer"].upper() in os.environ:
|
|
return False
|
|
|
|
self.did_init = True
|
|
|
|
# Set some default settings.
|
|
glEnable(GL_BLEND)
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
|
|
|
|
# Prepare a mouse display.
|
|
self.mouse_old_visible = None
|
|
|
|
self.environ.init()
|
|
self.rtt.init()
|
|
|
|
if self.window.get_flags() & pygame.WINDOW_MAXIMIZED:
|
|
self.info["max_window_size"] = self.window.get_size()
|
|
else:
|
|
self.info["max_window_size"] = (
|
|
int(round(min(bound_h * virtual_ar, bound_w))),
|
|
int(round(min(bound_w / virtual_ar, bound_h))),
|
|
)
|
|
|
|
return True
|
|
|
|
def quit(self):
|
|
"""
|
|
This shuts down the module and all use of the GL context.
|
|
"""
|
|
|
|
self.kill_textures()
|
|
|
|
if self.rtt:
|
|
self.rtt.deinit()
|
|
|
|
if self.environ:
|
|
self.environ.deinit()
|
|
|
|
if not self.old_fullscreen:
|
|
renpy.display.gl_size = self.physical_size
|
|
|
|
gltexture.dealloc_textures()
|
|
|
|
self.old_fullscreen = None
|
|
|
|
def init(self):
|
|
"""
|
|
This does the first-time initialization of OpenGL, deciding
|
|
which subsystems to use.
|
|
"""
|
|
|
|
if not EGL:
|
|
|
|
# Init glew.
|
|
err = glewInit()
|
|
|
|
if err != GLEW_OK:
|
|
renpy.display.log.write("Glew init failed: %s" % <char *> glewGetErrorString(err))
|
|
return False
|
|
|
|
# Log the GL version.
|
|
renderer = <char *> glGetString(GL_RENDERER)
|
|
version = <char *> glGetString(GL_VERSION)
|
|
|
|
renpy.display.log.write("Vendor: %r", str(<char *> glGetString(GL_VENDOR)))
|
|
renpy.display.log.write("Renderer: %r", renderer)
|
|
renpy.display.log.write("Version: %r", version)
|
|
renpy.display.log.write("Display Info: %s", self.display_info)
|
|
|
|
|
|
allow_shader = True
|
|
allow_fixed = self.allow_fixed
|
|
|
|
if not allow_shader:
|
|
renpy.display.log.write("Shaders are blacklisted.")
|
|
if not allow_fixed:
|
|
renpy.display.log.write("Fixed-function is blacklisted.")
|
|
|
|
if not allow_shader and not allow_fixed:
|
|
renpy.display.log.write("GL is totally blacklisted.")
|
|
return False
|
|
|
|
if EGL:
|
|
gltexture.use_gles()
|
|
|
|
elif renpy.android or renpy.ios:
|
|
self.redraw_period = 1.0
|
|
gltexture.use_gles()
|
|
|
|
elif renpy.emscripten:
|
|
# give back control to browser regularly
|
|
self.redraw_period = 0.1
|
|
# WebGL is GLES
|
|
gltexture.use_gles()
|
|
|
|
else:
|
|
gltexture.use_gl()
|
|
|
|
extensions_string = <char *> glGetString(GL_EXTENSIONS)
|
|
extensions = set(extensions_string.split(" "))
|
|
|
|
renpy.display.log.write("Extensions:")
|
|
|
|
for i in sorted(extensions):
|
|
renpy.display.log.write(" %s", i)
|
|
|
|
def use_subsystem(module, envvar, envval, *req_ext):
|
|
"""
|
|
Decides if we should used a particular subsystem, based on
|
|
environment variables and/or extensions. If the `envvar`
|
|
environment variable exists, this will return true iff
|
|
its value is `envval`. Otherwise, this will return true if
|
|
all of the required extensions are present, and false
|
|
otherwise.
|
|
"""
|
|
|
|
if module is None:
|
|
return False
|
|
|
|
value = os.environ.get(envvar, "")
|
|
if value:
|
|
if value == envval:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
for i in req_ext:
|
|
if i not in extensions:
|
|
return False
|
|
|
|
return True
|
|
|
|
# Count the number of texture units.
|
|
cdef GLint texture_units = 0
|
|
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_units)
|
|
|
|
renpy.display.log.write("Number of texture units: %d", texture_units)
|
|
|
|
# Pick a texture environment subsystem.
|
|
|
|
if EGL or renpy.android or renpy.ios or renpy.emscripten or (allow_shader and use_subsystem(
|
|
glenviron_shader,
|
|
"RENPY_GL_ENVIRON",
|
|
"shader",
|
|
"GL_ARB_vertex_shader",
|
|
"GL_ARB_fragment_shader")):
|
|
|
|
try:
|
|
renpy.display.log.write("Using shader environment.")
|
|
self.environ = glenviron_shader.ShaderEnviron()
|
|
self.info["environ"] = "shader"
|
|
self.environ.init()
|
|
|
|
except Exception, e:
|
|
renpy.display.log.write("Initializing shader environment failed:")
|
|
renpy.display.log.exception()
|
|
self.environ = None
|
|
|
|
if self.environ is None:
|
|
|
|
if allow_fixed and use_subsystem(
|
|
glenviron_fixed,
|
|
"RENPY_GL_ENVIRON",
|
|
"fixed",
|
|
"GL_ARB_texture_env_crossbar",
|
|
"GL_ARB_texture_env_combine"):
|
|
|
|
renpy.display.log.write("Using fixed-function environment (clause 1).")
|
|
self.environ = glenviron_fixed.FixedFunctionEnviron()
|
|
self.info["environ"] = "fixed"
|
|
self.environ.init()
|
|
|
|
elif allow_fixed and use_subsystem(
|
|
glenviron_fixed,
|
|
"RENPY_GL_ENVIRON",
|
|
"fixed",
|
|
"GL_NV_texture_env_combine4"):
|
|
|
|
renpy.display.log.write("Using fixed-function environment (clause 2).")
|
|
self.environ = glenviron_fixed.FixedFunctionEnviron()
|
|
self.info["environ"] = "fixed"
|
|
self.environ.init()
|
|
|
|
elif use_subsystem(
|
|
glenviron_limited,
|
|
"RENPY_GL_ENVIRON",
|
|
"limited",
|
|
"RENPY_bogus_extension"):
|
|
|
|
renpy.display.log.write("Using limited environment.")
|
|
self.environ = glenviron_limited.LimitedEnviron()
|
|
self.info["environ"] = "limited"
|
|
self.environ.init()
|
|
|
|
else:
|
|
renpy.display.log.write("Can't find a workable environment.")
|
|
return False
|
|
|
|
# Pick a Render-to-texture method.
|
|
use_fbo = renpy.ios or renpy.android or renpy.emscripten or EGL or use_subsystem(
|
|
glrtt_fbo,
|
|
"RENPY_GL_RTT",
|
|
"fbo",
|
|
"GL_ARB_framebuffer_object")
|
|
|
|
if use_fbo:
|
|
renpy.display.log.write("Using FBO RTT.")
|
|
self.rtt = glrtt_fbo.FboRtt()
|
|
self.info["rtt"] = "fbo"
|
|
self.rtt.init()
|
|
|
|
elif glrtt_copy:
|
|
renpy.display.log.write("Using copy RTT.")
|
|
self.rtt = glrtt_copy.CopyRtt()
|
|
self.info["rtt"] = "copy"
|
|
self.rtt.init()
|
|
|
|
else:
|
|
renpy.display.log.write("Can't find a workable rtt.")
|
|
return False
|
|
|
|
|
|
renpy.display.log.write("Using {0} renderer.".format(self.info["renderer"]))
|
|
|
|
# Figure out the sizes of texture that render properly.
|
|
if not self.did_texture_test:
|
|
rv = gltexture.test_texture_sizes(self.environ, self)
|
|
else:
|
|
rv = True
|
|
|
|
self.rtt.deinit()
|
|
self.environ.deinit()
|
|
|
|
if not rv:
|
|
return False
|
|
|
|
self.did_texture_test = True
|
|
|
|
# Do additional setup needed.
|
|
renpy.display.pgrender.set_rgba_masks()
|
|
|
|
return True
|
|
|
|
|
|
def can_block(self):
|
|
"""
|
|
Returns True if we can block to wait for input, False if the screen
|
|
needs to be immediately redrawn.
|
|
"""
|
|
|
|
powersave = renpy.game.preferences.gl_powersave
|
|
|
|
if not powersave:
|
|
return False
|
|
|
|
return not self.fast_redraw_frames
|
|
|
|
def should_redraw(self, needs_redraw, first_pass, can_block):
|
|
"""
|
|
Redraw whenever the screen needs it, but at least once every
|
|
.2 seconds. We rely on VSYNC to slow down our maximum
|
|
draw speed.
|
|
"""
|
|
|
|
rv = False
|
|
|
|
if needs_redraw:
|
|
rv = True
|
|
elif first_pass:
|
|
rv = True
|
|
else:
|
|
# Redraw if the mouse moves.
|
|
mx, my, tex = self.mouse_info
|
|
|
|
if tex and (mx, my) != pygame.mouse.get_pos():
|
|
rv = True
|
|
|
|
# Handle fast redraw.
|
|
if rv:
|
|
self.fast_redraw_frames = renpy.config.fast_redraw_frames
|
|
elif self.fast_redraw_frames > 0:
|
|
self.fast_redraw_frames -= 1
|
|
rv = True
|
|
|
|
if time.time() > self.last_redraw_time + self.redraw_period:
|
|
rv = True
|
|
|
|
# Store the redraw time.
|
|
if rv or (not can_block):
|
|
self.last_redraw_time = time.time()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def mutated_surface(self, surf):
|
|
if surf in self.texture_cache:
|
|
del self.texture_cache[surf]
|
|
|
|
def load_texture(self, surf, transient=False):
|
|
"""
|
|
Loads a texture into memory.
|
|
"""
|
|
|
|
# Turn a surface into a texture grid.
|
|
|
|
rv = self.texture_cache.get(surf, None)
|
|
|
|
if rv is None:
|
|
rv = gltexture.texture_grid_from_surface(surf, transient)
|
|
self.texture_cache[surf] = rv
|
|
self.ready_texture_queue.add(rv)
|
|
|
|
return rv
|
|
|
|
def ready_one_texture(self):
|
|
"""
|
|
Call from the main thread to make a single texture ready.
|
|
"""
|
|
|
|
while True:
|
|
|
|
try:
|
|
tex = self.ready_texture_queue.pop()
|
|
except KeyError:
|
|
return False
|
|
|
|
if not tex.ready:
|
|
tex.make_ready(False)
|
|
return True
|
|
|
|
return False
|
|
|
|
def solid_texture(self, w, h, color):
|
|
surf = renpy.display.pgrender.surface((w + 4, h + 4), True)
|
|
surf.fill(color)
|
|
surf = surf.subsurface((2, 2, w, h))
|
|
|
|
return self.load_texture(surf)
|
|
|
|
# private
|
|
def clip_mode_screen(self):
|
|
"""
|
|
This does two things. First, it shuts down clipping, and clears
|
|
the cache so it will be reset by the next call to set_clip. Then
|
|
it flags that we are in the screen clip mode, which control how
|
|
coordinates are mapped to the scissor box.
|
|
"""
|
|
|
|
self.clip_cache = None
|
|
self.clip_rtt_box = None
|
|
|
|
self.environ.unset_clip(self)
|
|
|
|
# private
|
|
def clip_mode_rtt(self, x, y, w, h):
|
|
"""
|
|
The same thing, except the screen is projected in RTT mode.
|
|
"""
|
|
|
|
self.clip_cache = None
|
|
self.clip_rtt_box = (x, y, w, h)
|
|
|
|
self.environ.unset_clip(self)
|
|
|
|
# private
|
|
cpdef set_clip(GLDraw self, tuple clip):
|
|
|
|
if self.clip_cache == clip:
|
|
return
|
|
|
|
self.clip_cache = clip
|
|
|
|
self.environ.set_clip(clip, self)
|
|
|
|
|
|
def draw_screen(self, surftree, fullscreen_video, flip=True):
|
|
"""
|
|
Draws the screen.
|
|
"""
|
|
|
|
renpy.plog(1, "start draw_screen")
|
|
|
|
if renpy.config.use_drawable_resolution:
|
|
reverse = self.virt_to_draw
|
|
else:
|
|
reverse = IDENTITY
|
|
|
|
surftree.is_opaque()
|
|
|
|
self.draw_render_textures(surftree, 0)
|
|
|
|
xmul = 1.0 * self.drawable_size[0] / self.physical_size[0]
|
|
ymul = 1.0 * self.drawable_size[1] / self.physical_size[1]
|
|
|
|
if reverse != IDENTITY:
|
|
xsize = xmul * self.physical_box[2]
|
|
ysize = ymul * self.physical_box[3]
|
|
else:
|
|
xsize = self.virtual_size[0]
|
|
ysize = self.virtual_size[1]
|
|
|
|
self.environ.viewport(xmul * self.physical_box[0], ymul * self.physical_box[1], xmul * self.physical_box[2], ymul * self.physical_box[3])
|
|
self.environ.ortho(0, xsize, ysize, 0, -1.0, 1.0)
|
|
|
|
self.clip_mode_screen()
|
|
|
|
clear_r, clear_g, clear_b = renpy.color.Color(renpy.config.gl_clear_color).rgb
|
|
glClearColor(clear_r, clear_g, clear_b, 1.0)
|
|
glClear(GL_COLOR_BUFFER_BIT)
|
|
|
|
self.default_clip = (0, 0, xsize, ysize)
|
|
clip = self.default_clip
|
|
|
|
if renpy.display.video.fullscreen:
|
|
surf = renpy.display.video.render_movie("movie", self.virtual_size[0], self.virtual_size[1])
|
|
if surf is not None:
|
|
self.draw_transformed(surf, clip, 0, 0, 1.0, 1.0, reverse, renpy.config.nearest_neighbor, False)
|
|
else:
|
|
flip = False
|
|
|
|
else:
|
|
self.draw_transformed(surftree, clip, 0, 0, 1.0, 1.0, reverse, renpy.config.nearest_neighbor, False)
|
|
|
|
if flip:
|
|
|
|
self.draw_mouse()
|
|
|
|
start = time.time()
|
|
|
|
renpy.plog(1, "flip")
|
|
|
|
if EGL:
|
|
egl_swap()
|
|
else:
|
|
pygame.display.flip()
|
|
|
|
end = time.time()
|
|
|
|
if vsync:
|
|
|
|
# When the window is covered, we can get into a state where no
|
|
# drawing occurs and everything goes fast. Detect that and
|
|
# sleep.
|
|
|
|
frame_times.append(end)
|
|
|
|
if len(frame_times) > 10:
|
|
frame_times.pop(0)
|
|
|
|
# If we're running at over 1000 fps, vsync is broken.
|
|
if (frame_times[-1] - frame_times[0] < .001 * 10):
|
|
time.sleep(1.0 / 120.0)
|
|
renpy.plog(1, "after broken vsync sleep")
|
|
|
|
|
|
gltexture.cleanup()
|
|
|
|
cpdef int draw_render_textures(GLDraw self, what, bint non_aligned) except 1:
|
|
"""
|
|
This is responsible for rendering things to textures,
|
|
as necessary.
|
|
"""
|
|
|
|
cdef render.Render rend
|
|
cdef bint render_what
|
|
|
|
if not isinstance(what, render.Render):
|
|
return 0
|
|
|
|
rend = <render.Render> what
|
|
|
|
render_what = False
|
|
|
|
if (rend.xclipping or rend.yclipping) and non_aligned:
|
|
if rend.forward.xdy != 0 or rend.forward.ydx != 0:
|
|
render_what = True
|
|
non_aligned = False
|
|
|
|
first = True
|
|
|
|
if rend.forward:
|
|
non_aligned |= (rend.forward.xdy != 0)
|
|
non_aligned |= (rend.forward.ydy != 0)
|
|
|
|
for child, cxo, cyo, focus, main in rend.visible_children:
|
|
|
|
self.draw_render_textures(child, non_aligned)
|
|
|
|
if rend.operation == DISSOLVE:
|
|
if not self.fast_dissolve:
|
|
child.render_to_texture(what.operation_alpha)
|
|
|
|
elif rend.operation == IMAGEDISSOLVE:
|
|
child.render_to_texture(first or what.operation_alpha)
|
|
first = False
|
|
|
|
elif rend.operation == PIXELLATE:
|
|
p = rend.operation_parameter
|
|
pc = child
|
|
|
|
while p > 1:
|
|
p /= 2
|
|
pc = self.get_half(pc)
|
|
|
|
elif rend.operation == FLATTEN:
|
|
child.render_to_texture(True)
|
|
|
|
if render_what:
|
|
what.render_to_texture(True)
|
|
|
|
cpdef int draw_transformed(
|
|
GLDraw self,
|
|
object what,
|
|
tuple clip,
|
|
double xo,
|
|
double yo,
|
|
double alpha,
|
|
double over,
|
|
Matrix reverse,
|
|
bint nearest,
|
|
bint subpixel) except 1:
|
|
|
|
cdef render.Render rend
|
|
cdef double cxo, cyo, tcxo, tcyo
|
|
cdef Matrix child_reverse
|
|
|
|
if not isinstance(what, render.Render):
|
|
|
|
if isinstance(what, gltexture.TextureGrid):
|
|
|
|
if (not subpixel) and reverse.is_unit_aligned():
|
|
xo = round(xo)
|
|
yo = round(yo)
|
|
|
|
self.set_clip(clip)
|
|
|
|
gltexture.blit(
|
|
<gltexture.TextureGrid> what,
|
|
xo,
|
|
yo,
|
|
reverse,
|
|
alpha,
|
|
over,
|
|
self.environ,
|
|
nearest)
|
|
|
|
return 0
|
|
|
|
if isinstance(what, pygame.Surface):
|
|
|
|
tex = self.load_texture(what)
|
|
self.draw_transformed(tex, clip, xo, yo, alpha, over, reverse, nearest, subpixel)
|
|
return 0
|
|
|
|
raise Exception("Unknown drawing type. " + repr(what))
|
|
|
|
rend = what
|
|
|
|
if rend.text_input:
|
|
renpy.display.interface.text_rect = rend.screen_rect(xo, yo, reverse)
|
|
|
|
# Other draw modes.
|
|
|
|
if rend.operation == DISSOLVE:
|
|
|
|
if self.fast_dissolve:
|
|
|
|
# This is a fast version of dissolve that's used on
|
|
# GLES systems. The semantics are different than that
|
|
# of dissolve on Ren'Py proper.
|
|
|
|
self.draw_transformed(rend.children[0][0], clip, xo, yo, alpha, over, reverse, nearest, subpixel)
|
|
|
|
self.draw_transformed(rend.children[1][0], clip, xo, yo, alpha * what.operation_complete, over, reverse, nearest, subpixel)
|
|
|
|
else:
|
|
|
|
self.set_clip(clip)
|
|
|
|
gltexture.blend(
|
|
rend.children[0][0].render_to_texture(what.operation_alpha),
|
|
rend.children[1][0].render_to_texture(what.operation_alpha),
|
|
xo,
|
|
yo,
|
|
reverse * self.draw_to_virt,
|
|
alpha,
|
|
over,
|
|
rend.operation_complete,
|
|
self.environ,
|
|
nearest)
|
|
|
|
return 0
|
|
|
|
elif rend.operation == IMAGEDISSOLVE:
|
|
|
|
self.set_clip(clip)
|
|
|
|
gltexture.imageblend(
|
|
rend.children[0][0].render_to_texture(True),
|
|
rend.children[1][0].render_to_texture(what.operation_alpha),
|
|
rend.children[2][0].render_to_texture(what.operation_alpha),
|
|
xo,
|
|
yo,
|
|
reverse * self.draw_to_virt,
|
|
alpha,
|
|
over,
|
|
rend.operation_complete,
|
|
rend.operation_parameter,
|
|
self.environ,
|
|
nearest)
|
|
|
|
return 0
|
|
|
|
|
|
elif rend.operation == PIXELLATE:
|
|
self.set_clip(clip)
|
|
|
|
p = rend.operation_parameter
|
|
pc = rend.children[0][0]
|
|
|
|
while p > 1:
|
|
p /= 2
|
|
pc = self.get_half(pc)
|
|
|
|
reverse *= Matrix2D(1.0 * what.width / pc.width, 0, 0, 1.0 * what.height / pc.height)
|
|
|
|
gltexture.blit(
|
|
pc,
|
|
xo,
|
|
yo,
|
|
reverse,
|
|
alpha,
|
|
over,
|
|
self.environ,
|
|
True)
|
|
|
|
return 0
|
|
|
|
elif rend.operation == FLATTEN:
|
|
self.set_clip(clip)
|
|
|
|
gltexture.blit(
|
|
rend.children[0][0].render_to_texture(True),
|
|
xo,
|
|
yo,
|
|
reverse * self.draw_to_virt,
|
|
alpha,
|
|
over,
|
|
self.environ,
|
|
nearest)
|
|
|
|
return 0
|
|
|
|
|
|
# Compute clipping.
|
|
if rend.xclipping or rend.yclipping:
|
|
|
|
# Non-aligned clipping uses RTT.
|
|
if reverse.ydx != 0 or reverse.xdy != 0:
|
|
tex = what.render_to_texture(True)
|
|
self.draw_transformed(tex, clip, xo, yo, alpha, over, reverse * self.draw_to_virt, nearest, subpixel)
|
|
return 0
|
|
|
|
minx, miny, maxx, maxy = clip
|
|
|
|
# Figure out the transformed width and height of this
|
|
# surface.
|
|
tw, th = reverse.transform(what.width, what.height)
|
|
|
|
if rend.xclipping:
|
|
minx = max(minx, min(xo, xo + tw))
|
|
maxx = min(maxx, max(xo, xo + tw))
|
|
|
|
if rend.yclipping:
|
|
miny = max(miny, min(yo, yo + th))
|
|
maxy = min(maxy, max(yo, yo + th))
|
|
|
|
clip = (minx, miny, maxx, maxy)
|
|
|
|
alpha = alpha * rend.alpha
|
|
over = over * rend.over
|
|
|
|
if rend.nearest is not None:
|
|
nearest = rend.nearest
|
|
|
|
# If our alpha has hit 0, don't do anything.
|
|
if alpha <= 0.003: # (1 / 256)
|
|
return 0
|
|
|
|
if rend.reverse is not None and rend.reverse is not IDENTITY:
|
|
child_reverse = reverse * rend.reverse
|
|
else:
|
|
child_reverse = reverse
|
|
|
|
for child, cx, cy, focus, main in rend.visible_children:
|
|
|
|
# The type of cx and cy depends on if this is a subpixel blit or not.
|
|
if type(cx) is float:
|
|
subpixel = True
|
|
|
|
cxo = cx
|
|
cyo = cy
|
|
|
|
tcxo = reverse.xdx * cxo + reverse.xdy * cyo
|
|
tcyo = reverse.ydx * cxo + reverse.ydy * cyo
|
|
|
|
self.draw_transformed(child, clip, xo + tcxo, yo + tcyo, alpha, over, child_reverse, nearest, subpixel)
|
|
|
|
return 0
|
|
|
|
def render_to_texture(self, what, alpha):
|
|
|
|
width = int(math.ceil(what.width * self.draw_per_virt))
|
|
height = int(math.ceil(what.height * self.draw_per_virt))
|
|
|
|
def draw_func(x, y, w, h):
|
|
|
|
self.clip_mode_rtt(x, y, w, h)
|
|
|
|
if alpha:
|
|
glClearColor(0.0, 0.0, 0.0, 0.0)
|
|
else:
|
|
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT)
|
|
|
|
self.default_clip = (0, 0, width, height)
|
|
clip = self.default_clip
|
|
|
|
self.draw_transformed(what, clip, 0, 0, 1.0, 1.0, self.virt_to_draw, renpy.config.nearest_neighbor, False)
|
|
|
|
if isinstance(what, render.Render):
|
|
what.is_opaque()
|
|
|
|
rv = gltexture.texture_grid_from_drawing(width, height, draw_func, self.rtt, self.environ)
|
|
|
|
self.did_render_to_texture = True
|
|
|
|
return rv
|
|
|
|
|
|
def is_pixel_opaque(self, what, x, y):
|
|
"""
|
|
Returns true if the pixel is not 100% transparent.
|
|
"""
|
|
|
|
if x < 0 or y < 0 or x >= what.width or y >= what.height:
|
|
return 0
|
|
|
|
what = what.subsurface((x, y, 1, 1))
|
|
|
|
reverse = IDENTITY
|
|
|
|
alpha_holder = [ 0 ]
|
|
|
|
def draw_func(x, y, w, h):
|
|
self.environ.viewport(0, 0, 1, 1)
|
|
self.environ.ortho(0, 1, 0, 1, -1, 1)
|
|
|
|
self.clip_mode_rtt(0, 0, 1, 1)
|
|
clip = (0, 0, 1, 1)
|
|
|
|
glClearColor(0.0, 0.0, 0.0, 0.0)
|
|
glClear(GL_COLOR_BUFFER_BIT)
|
|
|
|
self.draw_transformed(what, clip, 0, 0, 1.0, 1.0, reverse, renpy.config.nearest_neighbor, False)
|
|
|
|
cdef unsigned char pixel[4]
|
|
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel)
|
|
|
|
alpha_holder[0] = (pixel[3])
|
|
|
|
self.did_render_to_texture = False
|
|
|
|
# We need to render a second time if a render-to-texture occurs, as it
|
|
# has overwritten the buffer we're drawing to.
|
|
for _i in range(2):
|
|
|
|
gltexture.texture_grid_from_drawing(1, 1, draw_func, self.rtt, self.environ)
|
|
|
|
if not self.did_render_to_texture:
|
|
break
|
|
|
|
what.kill()
|
|
|
|
return alpha_holder[0]
|
|
|
|
|
|
def get_half(self, what):
|
|
"""
|
|
Gets a texture grid that's half the size of what..
|
|
"""
|
|
# Used to work around a bug in cython where self was not getting
|
|
# the right type when being assigned to the closure.
|
|
cdef GLDraw draw = self
|
|
|
|
if what.half_cache:
|
|
return what.half_cache
|
|
|
|
reverse = Matrix2D(0.5, 0, 0, .5)
|
|
|
|
width = max(what.width / 2, 1)
|
|
height = max(what.height / 2, 1)
|
|
|
|
def draw_func(x, y, w, h):
|
|
|
|
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
glClear(GL_COLOR_BUFFER_BIT)
|
|
|
|
draw.clip_mode_rtt(x, y, w, h)
|
|
|
|
clip = (0, 0, width, height)
|
|
|
|
draw.draw_transformed(what, clip, 0, 0, 1.0, 1.0, reverse, renpy.config.nearest_neighbor, False)
|
|
|
|
if isinstance(what, render.Render):
|
|
what.is_opaque()
|
|
|
|
rv = gltexture.texture_grid_from_drawing(width, height, draw_func, self.rtt, self.environ)
|
|
|
|
what.half_cache = rv
|
|
|
|
return rv
|
|
|
|
|
|
def translate_point(self, x, y):
|
|
"""
|
|
Translates (x, y) from physical to virtual coordinates.
|
|
"""
|
|
|
|
# Screen sizes.
|
|
pw, ph = self.physical_size
|
|
vw, vh = self.virtual_size
|
|
vx, vy, vbw, vbh = self.virtual_box
|
|
|
|
# Translate to fractional screen.
|
|
x = 1.0 * x / pw
|
|
y = 1.0 * y / ph
|
|
|
|
# Translate to virtual size.
|
|
x = vx + vbw * x
|
|
y = vy + vbh * y
|
|
|
|
x = int(x)
|
|
y = int(y)
|
|
|
|
x = max(0, x)
|
|
x = min(vw, x)
|
|
y = max(0, y)
|
|
y = min(vh, y)
|
|
|
|
return x, y
|
|
|
|
def untranslate_point(self, x, y):
|
|
"""
|
|
Untranslates (x, y) from virtual to physical coordinates.
|
|
"""
|
|
|
|
# Screen sizes.
|
|
pw, ph = self.physical_size
|
|
vx, vy, vbw, vbh = self.virtual_box
|
|
|
|
# Translate from virtual to fractional screen.
|
|
x = ( x - vx ) / vbw
|
|
y = ( y - vy ) / vbh
|
|
|
|
# Translate from fractional screen to physical.
|
|
x = x * pw
|
|
y = y * ph
|
|
|
|
x = int(x)
|
|
y = int(y)
|
|
|
|
return x, y
|
|
|
|
def update_mouse(self):
|
|
# The draw routine updates the mouse. There's no need to
|
|
# redraw it event-by-event.
|
|
|
|
return
|
|
|
|
def mouse_event(self, ev):
|
|
x, y = getattr(ev, 'pos', pygame.mouse.get_pos())
|
|
return self.translate_point(x, y)
|
|
|
|
def get_mouse_pos(self):
|
|
x, y = pygame.mouse.get_pos()
|
|
return self.translate_point(x, y)
|
|
|
|
def set_mouse_pos(self, x, y):
|
|
x, y = self.untranslate_point(x, y)
|
|
pygame.mouse.set_pos([x, y])
|
|
|
|
# Private.
|
|
def draw_mouse(self):
|
|
|
|
hardware, mx, my, tex = renpy.game.interface.get_mouse_info()
|
|
|
|
self.mouse_info = (mx, my, tex)
|
|
|
|
if self.mouse_old_visible != hardware:
|
|
pygame.mouse.set_visible(hardware)
|
|
self.mouse_old_visible = hardware
|
|
|
|
if not tex:
|
|
return
|
|
|
|
x, y = pygame.mouse.get_pos()
|
|
|
|
x -= mx
|
|
y -= my
|
|
|
|
pw, ph = self.physical_size
|
|
pbx, pby, pbw, pbh = self.physical_box
|
|
|
|
xmul = 1.0 * self.drawable_size[0] / self.physical_size[0]
|
|
ymul = 1.0 * self.drawable_size[1] / self.physical_size[1]
|
|
|
|
self.environ.viewport(0, 0, xmul * pw, ymul * ph)
|
|
self.environ.ortho(0, pw, ph, 0, -1.0, 1.0)
|
|
|
|
self.clip_mode_screen()
|
|
self.set_clip((-pbx, -pby, pw, ph))
|
|
|
|
gltexture.blit(
|
|
tex,
|
|
x,
|
|
y,
|
|
IDENTITY,
|
|
1.0,
|
|
1.0,
|
|
self.environ,
|
|
False)
|
|
|
|
def screenshot(self, surftree, fullscreen_video):
|
|
cdef unsigned char *pixels
|
|
cdef SDL_Surface *surf
|
|
|
|
cdef unsigned char *raw_pixels
|
|
cdef unsigned char *rpp
|
|
cdef int x, y, pitch
|
|
|
|
# A surface the size of the framebuffer.
|
|
full = renpy.display.pgrender.surface_unscaled(self.drawable_size, False)
|
|
surf = PySurface_AsSurface(full)
|
|
|
|
# Create an array that can hold densely-packed pixels.
|
|
raw_pixels = <unsigned char *> malloc(surf.w * surf.h * 4)
|
|
|
|
# Draw the last screen to the back buffer.
|
|
if surftree is not None:
|
|
self.draw_screen(surftree, fullscreen_video, flip=False)
|
|
glFinish()
|
|
|
|
# Read the pixels.
|
|
glReadPixels(
|
|
0,
|
|
0,
|
|
surf.w,
|
|
surf.h,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
raw_pixels)
|
|
|
|
# Copy the pixels from raw_pixels to the surface.
|
|
pixels = <unsigned char *> surf.pixels
|
|
pitch = surf.pitch
|
|
rpp = raw_pixels
|
|
|
|
with nogil:
|
|
for y from 0 <= y < surf.h:
|
|
for x from 0 <= x < (surf.w * 4):
|
|
pixels[x] = rpp[x]
|
|
|
|
pixels += pitch
|
|
rpp += surf.w * 4
|
|
|
|
free(raw_pixels)
|
|
|
|
px, py, pw, ph = self.physical_box
|
|
xmul = self.drawable_size[0] / self.physical_size[0]
|
|
ymul = self.drawable_size[1] / self.physical_size[1]
|
|
|
|
# Crop and flip it, since it's upside down.
|
|
rv = full.subsurface((px * xmul, py * ymul, pw * xmul, ph * ymul))
|
|
rv = renpy.display.pgrender.flip_unscaled(rv, False, True)
|
|
|
|
return rv
|
|
|
|
def kill_textures(self):
|
|
self.texture_cache.clear()
|
|
gltexture.dealloc_textures()
|
|
|
|
def event_peek_sleep(self):
|
|
pass
|
|
|
|
def get_physical_size(self):
|
|
x, y = self.physical_size
|
|
|
|
x = int(x / self.dpi_scale)
|
|
y = int(y / self.dpi_scale)
|
|
|
|
return (x, y)
|
|
|
|
|
|
|
|
|
|
class Rtt(object):
|
|
"""
|
|
Subclasses of this class handle rendering to a texture.
|
|
"""
|
|
|
|
def init(self):
|
|
return
|
|
|
|
def deinit(self):
|
|
return
|
|
|
|
def render(self, texture, x, y, w, h, draw_func):
|
|
"""
|
|
This function is called to trigger a rendering to a texture.
|
|
`x`, `y`, `w`, and `h` specify the location and dimensions of
|
|
the sub-image to render to the texture. `draw_func` is called
|
|
to render the texture.
|
|
"""
|
|
|
|
raise Exception("Not implemented.")
|
|
|
|
def get_size_limit(self, dimension):
|
|
"""
|
|
Get the maximum size of a texture.
|
|
"""
|
|
|
|
raise Exception("Not implemented.")
|
|
|
|
|
|
cdef class Environ(object):
|
|
|
|
cdef void blit(self):
|
|
"""
|
|
Set up a normal blit environment. The texture to be blitted should
|
|
be TEXTURE0.
|
|
"""
|
|
|
|
cdef void blend(self, double fraction):
|
|
"""
|
|
Set up an environment that blends from TEXTURE0 to TEXTURE1.
|
|
|
|
`fraction` is the fraction of the blend complete.
|
|
"""
|
|
|
|
cdef void imageblend(self, double fraction, int ramp):
|
|
"""
|
|
Setup an environment that does an imageblend from TEXTURE1 to TEXTURE2.
|
|
The controlling image is TEXTURE0.
|
|
|
|
`fraction` is the fraction of the blend complete.
|
|
`ramp` is the length of the ramp.
|
|
"""
|
|
|
|
cdef void set_vertex(self, float *vertices):
|
|
"""
|
|
Sets the array of vertices to be shown. Vertices should be an packed
|
|
array of 2`n` floats.
|
|
"""
|
|
|
|
cdef void set_texture(self, int unit, float *coords):
|
|
"""
|
|
Sets the array of texture coordinates for unit `unit`.
|
|
"""
|
|
|
|
cdef void set_color(self, double r, double g, double b, double a):
|
|
"""
|
|
Sets the color to be shown.
|
|
"""
|
|
|
|
cdef void set_clip(self, tuple clip_box, GLDraw draw):
|
|
"""
|
|
Sets the clipping rectangle.
|
|
"""
|
|
|
|
cdef void unset_clip(self, GLDraw draw):
|
|
"""
|
|
Removes the clipping rectangle.
|
|
"""
|
|
|
|
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far):
|
|
"""
|
|
Enables orthographic projection. `left`, `right`, `top`, `bottom` are the coordinates of the various
|
|
sides of the viewport. `top` and `bottom` are the depth limits.
|
|
"""
|
|
|
|
cdef void viewport(self, int x, int y, int w, int h):
|
|
"""
|
|
Sets the GL viewport.
|
|
"""
|
|
|
|
|
|
# These imports need to be down at the bottom, after the Rtt and Environ
|
|
# classes have been created.
|
|
try:
|
|
import glrtt_copy
|
|
except:
|
|
glrtt_copy = None
|
|
|
|
# Copy doesn't work on iOS.
|
|
if renpy.ios:
|
|
glrtt_copy = None
|
|
|
|
try:
|
|
import glrtt_fbo
|
|
except ImportError:
|
|
glrtt_fbo = None
|
|
|
|
try:
|
|
import glenviron_fixed
|
|
except ImportError:
|
|
glenviron_fixed = None
|
|
|
|
try:
|
|
import glenviron_shader
|
|
except ImportError:
|
|
glenviron_shader = None
|
|
|
|
try:
|
|
import glenviron_limited
|
|
except ImportError:
|
|
glenviron_limited = None
|
|
|