view pytouhou/ui/opengl/renderer.pyx @ 694:3ff1af76e413

anm0: only use recoverable errors, no panics except for anm0 asserts.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 23 Aug 2019 02:24:08 +0200
parents ec972eb44391
children
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##

from libc.stdlib cimport malloc, free
from libc.string cimport memset
from os.path import join

from pytouhou.lib.opengl cimport \
         (glVertexPointer, glTexCoordPointer, glColorPointer,
          glVertexAttribPointer, glEnableVertexAttribArray, glBlendFunc,
          glBindTexture, glDrawElements, glBindBuffer, glBufferData,
          GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, GL_UNSIGNED_BYTE,
          GL_UNSIGNED_SHORT, GL_SHORT, GL_FLOAT, GL_SRC_ALPHA,
          GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_TEXTURE_2D, glGenBuffers,
          glDeleteBuffers, GLuint, glDeleteTextures, glGenVertexArrays,
          glDeleteVertexArrays, glBindVertexArray, glPushDebugGroup,
          GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup, glDrawArrays)

from pytouhou.lib.sdl import SDLError

from pytouhou.game.element cimport Element
from .sprite cimport get_sprite_rendering_data
from .backend cimport primitive_mode, is_legacy, use_debug_group, use_vao, use_primitive_restart

from pytouhou.utils.helpers import get_logger

logger = get_logger(__name__)


cdef class Texture:
    def __cinit__(self, GLuint texture, Renderer renderer):
        self.texture = texture

        # Find an unused key in the textures array.
        for key in range(MAX_TEXTURES):
            if renderer.textures[key] == 0:
                break
        else:
            raise MemoryError('Too many textures currently loaded, consider increasing MAX_TEXTURES (currently %d).' % MAX_TEXTURES)

        self.key = key
        self.pointer = &renderer.textures[key]
        self.pointer[0] = texture
        for i in range(2):
            renderer.indices[key][i] = self.indices[i]

        #XXX: keep a reference so that when __dealloc__ is called self.pointer is still valid.
        self.renderer = renderer

    def __dealloc__(self):
        if self.texture:
            glDeleteTextures(1, &self.texture)
        if self.pointer != NULL:
            self.pointer[0] = 0
        # The dangling pointers in renderer.indices doesn’t matter, since we
        # won’t use them if no texture is loaded in that slot.


cdef long find_objects(Renderer self, object elements) except -1:
    # Don’t type element as Element, or else the overriding of objects won’t work.
    cdef Element obj
    cdef long i = 0
    for element in elements:
        for obj in element.objects:
            sprite = obj.sprite
            if sprite and sprite.visible:
                # warning: no reference is preserved on the object—assuming the object will not die accidentally
                self.elements[i] = <PyObject*>obj
                i += 1
                if i >= 640*3-4:
                    return i
    return i


cdef class Renderer:
    def __dealloc__(self):
        if not is_legacy:
            glDeleteBuffers(1, &self.vbo)

            if use_vao:
                glDeleteVertexArrays(1, &self.vao)
                glDeleteVertexArrays(1, &self.text_vao)


    def __init__(self, resource_loader):
        self.texture_manager = TextureManager(resource_loader, self, Texture)
        font_name = join(resource_loader.game_dir, 'font.ttf')
        try:
            self.font_manager = FontManager(font_name, 16, self, Texture)
        except SDLError:
            self.font_manager = None
            logger.error('Font file “%s” not found, disabling text rendering altogether.', font_name)

        if not is_legacy:
            if use_debug_group:
                glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Renderer creation")

            glGenBuffers(1, &self.vbo)
            glGenBuffers(1, &self.text_vbo)

            if use_vao:
                glGenVertexArrays(1, &self.vao)
                glBindVertexArray(self.vao)
                self.set_state()

                glGenVertexArrays(1, &self.text_vao)
                glBindVertexArray(self.text_vao)
                self.set_text_state()

            if use_debug_group:
                glPopDebugGroup()


    cdef void set_state(self) nogil:
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)

        #TODO: find a way to use offsetof() instead of those ugly substractions.
        glVertexAttribPointer(0, 3, GL_SHORT, False, sizeof(Vertex), <void*>0)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>8)
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>16)
        glEnableVertexAttribArray(2)

        glBindBuffer(GL_ARRAY_BUFFER, 0)


    cdef void set_text_state(self) nogil:
        glBindBuffer(GL_ARRAY_BUFFER, self.text_vbo)

        #TODO: find a way to use offsetof() instead of those ugly substractions.
        glVertexAttribPointer(0, 2, GL_SHORT, False, sizeof(Vertex), <void*>0)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>4)
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>12)
        glEnableVertexAttribArray(2)

        glBindBuffer(GL_ARRAY_BUFFER, 0)


    cdef bint render_elements(self, elements) except True:
        cdef Element element

        nb_elements = find_objects(self, elements)
        if not nb_elements:
            return False

        nb_vertices = 0
        memset(self.last_indices, 0, sizeof(self.last_indices))

        for element_idx in range(nb_elements):
            element = <object>self.elements[element_idx]
            ox, oy = <short>element.x, <short>element.y
            data = get_sprite_rendering_data(element.sprite)
            key = data.key

            blendfunc = key & 1
            texture = key >> 1

            rec = self.indices[texture][blendfunc]
            next_indice = self.last_indices[key]

            # Pack data in buffer
            x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4 = <short>data.pos[0], <short>data.pos[1], <short>data.pos[2], <short>data.pos[3], <short>data.pos[4], <short>data.pos[5], <short>data.pos[6], <short>data.pos[7], <short>data.pos[8], <short>data.pos[9], <short>data.pos[10], <short>data.pos[11]
            r, g, b, a = data.color[0], data.color[1], data.color[2], data.color[3]
            self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, 0, data.left, data.bottom, r, g, b, a)
            self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, 0, data.right, data.bottom, r, g, b, a)
            self.vertex_buffer[nb_vertices+2] = Vertex(x4 + ox, y4 + oy, z4, 0, data.left, data.top, r, g, b, a)
            self.vertex_buffer[nb_vertices+3] = Vertex(x3 + ox, y3 + oy, z3, 0, data.right, data.top, r, g, b, a)

            # Add indices
            if is_legacy:
                rec[next_indice] = nb_vertices
                rec[next_indice+1] = nb_vertices + 1
                rec[next_indice+2] = nb_vertices + 3
                rec[next_indice+3] = nb_vertices + 2
                self.last_indices[key] += 4
            elif use_primitive_restart:
                rec[next_indice] = nb_vertices
                rec[next_indice+1] = nb_vertices + 1
                rec[next_indice+2] = nb_vertices + 2
                rec[next_indice+3] = nb_vertices + 3
                rec[next_indice+4] = 0xFFFF
                self.last_indices[key] += 5
            else:
                rec[next_indice] = nb_vertices
                rec[next_indice+1] = nb_vertices + 1
                rec[next_indice+2] = nb_vertices + 2
                rec[next_indice+3] = nb_vertices + 1
                rec[next_indice+4] = nb_vertices + 2
                rec[next_indice+5] = nb_vertices + 3
                self.last_indices[key] += 6

            nb_vertices += 4

        if use_debug_group:
            glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Elements drawing")

        if is_legacy:
            glVertexPointer(3, GL_SHORT, sizeof(Vertex), &self.vertex_buffer[0].x)
            glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &self.vertex_buffer[0].u)
            glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &self.vertex_buffer[0].r)
        else:
            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
            glBufferData(GL_ARRAY_BUFFER, nb_vertices * sizeof(Vertex), &self.vertex_buffer[0], GL_DYNAMIC_DRAW)
            glBindBuffer(GL_ARRAY_BUFFER, 0)

            if use_vao:
                glBindVertexArray(self.vao)
            else:
                self.set_state()

        # Don’t change the state when it’s not needed.
        previous_blendfunc = -1
        previous_texture = -1

        for key in range(2 * MAX_TEXTURES):
            nb_indices = self.last_indices[key]
            if not nb_indices:
                continue

            blendfunc = key & 1
            texture = key >> 1

            if blendfunc != previous_blendfunc:
                glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
            if texture != previous_texture:
                glBindTexture(GL_TEXTURE_2D, self.textures[texture])
            glDrawElements(primitive_mode, nb_indices, GL_UNSIGNED_SHORT, self.indices[texture][blendfunc])

            previous_blendfunc = blendfunc
            previous_texture = texture

        glBindTexture(GL_TEXTURE_2D, 0)

        if not is_legacy and use_vao:
            glBindVertexArray(0)

        if use_debug_group:
            glPopDebugGroup()


    cdef bint render_quads(self, rects, colors, GLuint texture) except True:
        # There is nothing that batch more than two quads on the same texture, currently.
        cdef TextVertex buf[8]
        cdef unsigned short indices[12]

        if not is_legacy:
            if use_primitive_restart:
                indices[:] = [0, 1, 2, 3, 0xffff, 4, 5, 6, 7, 0, 0, 0]
            else:
                indices[:] = [0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4]

        length = len(rects)
        assert length == len(colors)

        for i, r in enumerate(rects):
            c1, c2, c3, c4 = colors[i]

            buf[4*i] = TextVertex(r.x, r.y, 0, 0, c1.r, c1.g, c1.b, c1.a)
            buf[4*i+1] = TextVertex(r.x + r.w, r.y, 1, 0, c2.r, c2.g, c2.b, c2.a)
            buf[4*i+2] = TextVertex(r.x + r.w, r.y + r.h, 1, 1, c3.r, c3.g, c3.b, c3.a)
            buf[4*i+3] = TextVertex(r.x, r.y + r.h, 0, 1, c4.r, c4.g, c4.b, c4.a)

        if use_debug_group:
            glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Quads drawing")

        if is_legacy:
            glVertexPointer(2, GL_SHORT, sizeof(TextVertex), &buf[0].x)
            glTexCoordPointer(2, GL_FLOAT, sizeof(TextVertex), &buf[0].u)
            glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &buf[0].r)
        else:
            glBindBuffer(GL_ARRAY_BUFFER, self.text_vbo)
            glBufferData(GL_ARRAY_BUFFER, 4 * length * sizeof(TextVertex), buf, GL_DYNAMIC_DRAW)
            glBindBuffer(GL_ARRAY_BUFFER, 0)

            if use_vao:
                glBindVertexArray(self.text_vao)
            else:
                self.set_text_state()

        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glBindTexture(GL_TEXTURE_2D, texture)

        if is_legacy:
            glDrawArrays(primitive_mode, 0, 4 * length)
        else:
            nb_indices = 5 * length - 1 if use_primitive_restart else 6 * length
            glDrawElements(primitive_mode, nb_indices, GL_UNSIGNED_SHORT, indices)

        if use_debug_group:
            glPopDebugGroup()