view pytouhou/ui/opengl/shader.pyx @ 612:73f134f84c7f

Request a RGB888 context, since SDL2’s default of RGB332 sucks. On X11/GLX, it will select the first config available, that is the best one, while on EGL it will iterate over them to select the one closest to what the application requested. Of course, anything lower than RGB888 looks bad and we really don’t want that.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 26 Mar 2015 20:20:37 +0100
parents 3c2f96f1d715
children a6af3ff86612
line wrap: on
line source

# -*- encoding: utf-8 -*-
#
# Copyright Tristam Macdonald 2008.
# Copyright Emmanuel Gil Peyrot 2012.
#
# Distributed under the Boost Software License, Version 1.0
# (see http://www.boost.org/LICENSE_1_0.txt)
#
# Source: https://swiftcoder.wordpress.com/2008/12/19/simple-glsl-wrapper-for-pyglet/
#

from pytouhou.lib.opengl cimport \
         (glCreateProgram, glCreateShader, GL_VERTEX_SHADER,
          GL_FRAGMENT_SHADER, glShaderSource, glCompileShader, glGetShaderiv,
          GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, glGetShaderInfoLog,
          glAttachShader, glLinkProgram, glGetProgramiv, glGetProgramInfoLog,
          GL_LINK_STATUS, glUseProgram, glGetUniformLocation, glUniform1fv,
          glUniform4fv, glUniformMatrix4fv, glBindAttribLocation,
          glPushDebugGroup, GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup)

from libc.stdlib cimport malloc, free
from .backend cimport shader_header, use_debug_group


class GLSLException(Exception):
    pass


cdef class Shader:
    def __init__(self, vert=None, frag=None):
        if use_debug_group:
            glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Program creation")

        # create the program handle
        self.handle = glCreateProgram()
        # we are not linked yet
        self.linked = False

        # cache the uniforms location
        self.location_cache = {}

        # create the vertex shader
        vert_src = vert.encode()
        self.create_shader(vert_src, GL_VERTEX_SHADER)

        # create the fragment shader
        frag_src = frag.encode()
        self.create_shader(frag_src, GL_FRAGMENT_SHADER)

        #TODO: put those elsewhere.
        glBindAttribLocation(self.handle, 0, 'in_position')
        glBindAttribLocation(self.handle, 1, 'in_texcoord')
        glBindAttribLocation(self.handle, 2, 'in_color')

        # attempt to link the program
        self.link()

        if use_debug_group:
            glPopDebugGroup()

    cdef void create_shader(self, const GLchar *string, GLenum_shader shader_type) except *:
        cdef GLint temp
        cdef const GLchar *strings[2]
        strings[:] = [shader_header, string]

        # create the shader handle
        shader = glCreateShader(shader_type)

        # upload the source strings
        glShaderSource(shader, 2, strings, NULL)

        # compile the shader
        glCompileShader(shader)

        # retrieve the compile status
        glGetShaderiv(shader, GL_COMPILE_STATUS, &temp)

        # if compilation failed, print the log
        if not temp:
            # retrieve the log length
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &temp)
            # create a buffer for the log
            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
            # retrieve the log text
            glGetShaderInfoLog(shader, temp, NULL, temp_buf)
            buf = temp_buf[:temp]
            free(temp_buf)
            # print the log to the console
            raise GLSLException(buf)
        else:
            # all is well, so attach the shader to the program
            glAttachShader(self.handle, shader)

    cdef void link(self) except *:
        cdef GLint temp

        # link the program
        glLinkProgram(self.handle)

        # retrieve the link status
        glGetProgramiv(self.handle, GL_LINK_STATUS, &temp)

        # if linking failed, print the log
        if not temp:
            #   retrieve the log length
            glGetProgramiv(self.handle, GL_INFO_LOG_LENGTH, &temp)
            # create a buffer for the log
            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
            # retrieve the log text
            glGetProgramInfoLog(self.handle, temp, NULL, temp_buf)
            buf = temp_buf[:temp]
            free(temp_buf)
            # print the log to the console
            raise GLSLException(buf)
        else:
            # all is well, so we are linked
            self.linked = True

    cdef GLint get_uniform_location(self, name) except -1:
        if isinstance(name, str):
            name = name.encode()
        if name not in self.location_cache:
            loc = glGetUniformLocation(self.handle, name)
            if loc == -1:
                raise GLSLException('Undefined {} uniform.'.format(name))
            self.location_cache[name] = loc
        return self.location_cache[name]

    cdef void bind(self) nogil:
        # bind the program
        glUseProgram(self.handle)

    # upload a floating point uniform
    # this program must be currently bound
    cdef void uniform_1(self, name, GLfloat val) except *:
        glUniform1fv(self.get_uniform_location(name), 1, &val)

    # upload a vec4 uniform
    cdef void uniform_4(self, name, GLfloat a, GLfloat b, GLfloat c, GLfloat d) except *:
        cdef GLfloat vals[4]
        vals[0] = a
        vals[1] = b
        vals[2] = c
        vals[3] = d
        glUniform4fv(self.get_uniform_location(name), 1, vals)

    # upload a uniform matrix
    # works with matrices stored as lists,
    # as well as euclid matrices
    cdef void uniform_matrix(self, name, Matrix *mat) except *:
        # obtain the uniform location
        loc = self.get_uniform_location(name)
        # uplaod the 4x4 floating point matrix
        glUniformMatrix4fv(loc, 1, False, <GLfloat*>mat)