CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/gl2/gl2texture.pyx
2025-03-03 23:00:33 +01:00

553 lines
16 KiB
Cython

#@PydevCodeAnalysisIgnore
#cython: profile=False
# 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 sdl2 cimport *
from pygame_sdl2 cimport *
import_pygame_sdl2()
from libc.stdlib cimport malloc, free
from libc.string cimport memcpy
import sys
import time
import collections
import weakref
import math
import renpy
from renpy.gl2.uguugl cimport *
from renpy.gl2.gl2draw cimport GL2Draw
from renpy.gl2.gl2geometry cimport rectangle, texture_rectangle, Mesh
from renpy.display.matrix cimport Matrix
# This has different names in GL and GLES, but the same value.
cdef GLenum RGBA8 = 0x8058
################################################################################
cdef class TextureLoader:
def __init__(TextureLoader self, GL2Draw draw):
self.allocated = set()
self.free_list = [ ]
self.total_texture_size = 0
self.texture_load_queue = weakref.WeakSet()
self.draw = draw
self.ftl_program = draw.shader_cache.get(("renpy.ftl",))
def quit(self):
"""
Gets rid of this TextureLoader.
"""
cdef GLuint texnums[1]
for texture_number in self.allocated:
texnums[0] = texture_number
glDeleteTextures(1, texnums)
def get_texture_size(self):
"""
Returns the amount of memory locked up in textures.
"""
return self.total_texture_size, len(self.allocated)
def load_one_surface(self, surf, bl, bt, br, bb):
"""
Converts a surface into a texture.
"""
size = surf.get_size()
rv = Texture(size, self)
rv.from_surface(surf)
if bl or bt or br or bb:
w, h = size
mesh = Mesh()
mesh.add_attribute("aTexCoord", 2)
pw = w - bl - br
ph = h - bt - bb
if (w and h):
mesh.add_texture_rectangle(
0.0, 0.0, pw, ph,
1.0 * bl / w, 1.0 * bt / h, 1.0 - 1.0 * br / w, 1.0 - 1.0 * bb / h)
rv = Model((pw, ph), mesh, ("renpy.texture",), { "uTex0" : rv })
return rv
def texture_axis(self, length, limit, border):
"""
Splits `length` up into multiple textures.
This returns a series of (offset, width, left/top border, right/bottom border) tuples.
"""
if length <= limit:
return [ (0, length, 0, 0) ]
elif length <= 2 * (limit - border):
right = length // 2
left = length - right
return [ (0, left, 0, border), (left, right, border, 0) ]
else:
tiles = math.ceil(1.0 * length / (limit - 2 * border))
tile_length = length / tiles
tiles = int(tiles)
rv = [ ]
for i in xrange(tiles):
start = int(i * tile_length)
end = int((i + 1) * tile_length)
if i > 0:
left = border
else:
left = 0
if i < tiles - 1:
right = border
else:
right = 0
rv.append((start, end - start, left, right))
return rv
def load_surface(self, surf):
border = 1
size = surf.get_size()
w, h = size
if (w <= self.max_texture_width) and (h <= self.max_texture_height):
return self.load_one_surface(surf, 0, 0, 0, 0)
htiles = self.texture_axis(w, self.max_texture_width, border)
vtiles = self.texture_axis(h, self.max_texture_height, border)
rv = renpy.display.render.Render(w, h)
for ty, th, bt, bb in vtiles:
for tx, tw, bl, br in htiles:
ss = surf.subsurface((tx - bl, ty - bt, tw + bl + br, th + bt + bb))
t = self.load_one_surface(ss, bl, bt, br, bb)
rv.blit(t, (tx, ty))
return rv
def render_to_texture(self, what):
"""
Renders `what` to a texture.
"""
rv = Texture(what.get_size(), self)
rv.from_render(what)
return rv
def cleanup(self):
"""
This is called once per frame, to free textures that are no longer used.
"""
cdef GLuint texnums[1]
for texture_number in self.free_list:
texnums[0] = texture_number
glDeleteTextures(1, texnums)
self.allocated.remove(texture_number)
self.free_list = [ ]
def ready_one_texture(self):
"""
Called by GL2Draw to implement ready_one_texture.
"""
while True:
try:
tex = self.texture_load_queue.pop()
except KeyError:
return False
if not tex.loaded:
tex.load()
return True
return False
cdef class GLTexture:
"""
This class represents an OpenGL texture that needs to be loaded by
Ren'Py. It's responsible for handling deferred loading of textures,
and using the Python reference counting system to free textures that
are no longer required.
"""
def __init__(GLTexture self, size, TextureLoader loader):
cdef unsigned char *pixels
cdef unsigned char *data
cdef unsigned char *p
# The width and height of this texture.
self.width, self.height = size
# The number of the OpenGL texture this texture object
# represents.
self.number = 0
# True if the texture has been loaded into OpenGL, False otherwise.
self.loaded = False
# Used for loading surfaces.
self.data = NULL
self.surface = None
# Update the loader.
self.loader = loader
self.loader.total_texture_size += self.width * self.height * 4
def from_surface(GLTexture self, surface):
"""
Called to indicate this texture should be loaded from a surface.
"""
# Make a copy of the texture data.
cdef SDL_Surface *s
s = PySurface_AsSurface(surface)
pitch_pixels = s.pitch / 4
margin = pitch_pixels - s.w
if s.w and s.h and (margin < 64) and s.w < self.loader.max_texture_width:
# In-place path.
self.data = NULL
self.surface = surface
else:
# Copying path.
pixels = <unsigned char *> s.pixels
data = <unsigned char *> malloc(s.h * s.w * 4)
p = data
if s.pitch == s.w * 4:
memcpy(p, pixels, s.h * s.w * 4)
else:
for 0 <= i < s.h:
memcpy(p, pixels, s.w * 4)
pixels += s.pitch
p += (s.w * 4)
self.data = data
self.surface = None
self.loader.texture_load_queue.add(self)
def from_render(GLTexture self, what):
"""
This renders `what` to this texture.
"""
cw, ch = size = what.get_size()
loader = self.loader
draw = self.loader.draw
# The visible size of the texture.
tw = min(int(math.ceil(cw * draw.draw_per_virt)), loader.max_texture_width)
th = min(int(math.ceil(ch * draw.draw_per_virt)), loader.max_texture_height)
cdef GLuint premultiplied
glGenTextures(1, &premultiplied)
# Bind the framebuffer.
draw.change_fbo(draw.fbo)
# Set up the viewport.
glViewport(0, 0, tw, th)
# Clear the screen.
clear_r, clear_g, clear_b = renpy.color.Color(renpy.config.gl_clear_color).rgb
glClearColor(clear_r, clear_g, clear_b, 0.0)
glClear(GL_COLOR_BUFFER_BIT)
# Project the child from virtual space to the screen space.
cdef Matrix transform
transform = renpy.display.matrix.texture_projection(cw, ch)
if what.reverse:
transform *= what.reverse
# Set up the default modes.
glEnable(GL_BLEND)
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
context = renpy.gl2.gl2draw.GL2DrawingContext(draw)
context.draw(what, transform)
glBindTexture(GL_TEXTURE_2D, premultiplied)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, tw, th, 0)
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.number = premultiplied
self.loader.allocated.add(self.number)
self.loaded = True
def __repr__(self):
return "<GLTexture {}x{} {}>".format(self.width, self.height, self.number)
def load_gltexture(GLTexture self):
"""
Loads this texture. When it's loaded, generation and number are set,
and the texture is ready to use.
"""
cdef GLuint tex
cdef GLuint premultiplied
cdef Program program
cdef SDL_Surface *s
if self.loaded:
return
draw = self.loader.draw
# Generate the old textures.
glGenTextures(1, &tex)
glGenTextures(1, &premultiplied)
# Bind the framebuffer.
draw.change_fbo(draw.fbo)
# Load the pixel data into tex, and set it up for drawing.
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
mesh = Mesh()
mesh.add_attribute("aTexCoord", 2)
# Load the texture through zero-copy and normal paths.
if self.surface is not None:
s = PySurface_AsSurface(self.surface)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s.pitch / 4, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, s.pixels)
mesh.add_texture_rectangle(-1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 4.0 * s.w / s.pitch, 1.0)
else:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data)
mesh.add_texture_rectangle(-1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0)
# Set up the viewport.
glViewport(0, 0, self.width, self.height)
# Set up the blend mode for premultiplication.
glEnable(GL_BLEND)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ZERO, GL_ONE, GL_ZERO)
# Draw.
program = self.loader.ftl_program
program.start()
program.set_uniform("uTex0", tex)
program.draw(mesh)
program.finish()
# Create premultiplied.
glBindTexture(GL_TEXTURE_2D, premultiplied)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, self.width, self.height, 0)
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)
# Delete tex.
glDeleteTextures(1, &tex)
# Store the loaded texture.
self.number = premultiplied
self.loader.allocated.add(self.number)
self.loaded = True
# Free the data memory.
if self.data != NULL:
free(self.data)
self.data = NULL
self.surface = None
def __dealloc__(self):
if self.data:
free(self.data)
self.data = NULL
def __del__(self):
try:
if self.loaded:
self.loader.free_list.append(self.number)
self.loader.total_texture_size -= self.width * self.height * 4
except TypeError:
pass # Let's not error on shutdown.
################################################################################
class Model(object):
"""
A Model can be placed in the render tree, and contains the information
required to be drawn to a screen.
"""
def __init__(self, size, mesh, shaders, uniforms):
# The size of this model's bounding box, in virtual pixels.
self.size = size
# The mesh.
self.mesh = mesh
# A tuple giving the shaders used with this model.
self.shaders = shaders
# Either a dictionary giving uniforms associated with this model,
# or None.
self.uniforms = uniforms
# The cached_texture that comes from this model.
self.cached_texture = None
def load(self):
"""
Loads the textures associated with this model.
"""
for i in self.uniforms.itervalues():
if isinstance(i, GLTexture):
i.load_gltexture()
def program_uniforms(self, shader):
"""
Called by the rest of the drawing code to set up the textures associated
with this model.
"""
shader.set_uniforms(self.uniforms)
def get_size(self):
"""
Returns the size of this Model.
"""
return self.size
def subsurface(self, rect):
"""
Creates Model that fits within `rect`.
"""
x, y, w, h = rect
mesh = self.mesh.crop(rectangle(x, y, x+w, y+h))
if mesh is self.mesh:
if (x == 0) and (y == 0):
return self
mesh = mesh.offset(-x, -y, 0)
else:
mesh.offset_inplace(-x, -y, 0)
rv = Model((w, h), self.mesh, self.shaders, self.uniforms)
rv.mesh = mesh
return rv
class Texture(GLTexture, Model):
"""
A texture is Model and a GLTexture at the same time.
This also implies that the Model has texture coordinates that range from
(0.0, 0.0) to (1.0, 1.0) - and hence, that the GLTexture can be used to
represent it.
"""
def __init__(self, size, loader):
GLTexture.__init__(self, size, loader)
mesh = Mesh()
mesh.add_attribute("aTexCoord", 2)
w, h = size
mesh.add_texture_rectangle(
0.0, 0.0, w, h,
0.0, 0.0, 1.0, 1.0,
)
Model.__init__(self, size, mesh, ("renpy.texture",), None)
def load(self):
self.load_gltexture()
def program_uniforms(self, shader):
shader.set_uniform("uTex0", self)
def subsurface(self, rect):
rv = Model.subsurface(self, rect)
if rv is not self:
rv.uniforms = { "uTex0" : self }
return rv