#-*- coding:utf-8 -*-
# cython: profile=False

#  Copyright © 2009-2015  B. Clausius <barcc@gmx.de>
#
#  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, either version 3 of the License, or
#  (at your option) any later version.
#
#  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.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.


# although this file is compiled with Python3 syntax, Cython needs at least division from __future__
from __future__ import print_function, division

# This line makes cython happy
global __name__, __package__    # pylint: disable=W0604
#px/__compiled = True
__compiled = False

#px/from libc.math cimport cos, sin, tan, atan2, M_PI
from math import cos, sin, tan, atan2, pi as M_PI

# pylint: disable=W0614,W0401
#px/from gl cimport *
from OpenGL.GL import *
#px-
from OpenGL.GL.ARB.vertex_shader import glGetActiveAttribARB as glGetActiveAttrib

#px/from _gldraw cimport *
from .gldraw import *
# pylint: enable=W0614,W0401

from .debug import debug, DEBUG, DEBUG_DRAW, DEBUG_PICK, DEBUG_MSG, DEBUG_NOVSHADER, DEBUG_NOFSHADER, DEBUG_NOSHADER

#px/DEF SOURCEGLVERSION = 'GL'
SOURCEGLVERSION = 'GL'

if DEBUG:
    debug('Importing module:', __name__)
    debug('  from package:', __package__)
    debug('  compiled:', __compiled)
    debug('  GL/GLES:', SOURCEGLVERSION)


#px/cdef struct Terrain:
class terrain: pass     # pylint: disable=W0232, C0321, R0903
    #px+float red
    #px+float green
    #px+float blue
    #px+int width
    #px+int height
#px+cdef Terrain terrain

#px/cdef struct Frustum:
class frustum:
    #px+float fovy_angle  # field of view angle
    #px+float fovy_radius
    #px+float fovy_radius_zoom
    #px+double bounding_sphere_radius
    #px+bint multisample
    #px/mat4 modelview_matrix
    modelview_matrix = mat4()
    #px/mat4 projection_matrix
    projection_matrix = mat4()
    #px/mat4 picking_matrix
    picking_matrix = mat4()
#px+cdef Frustum frustum

#px/cdef struct Program:
class program: pass
    #px+GLuint prog_render
    #px+GLuint projection_location
    #px+GLuint modelview_location
    #px+GLuint prog_hud  # only used if DEBUG_DRAW
    #px+GLuint prog_pick
    #px+GLuint picking_location
    #px+GLuint projection_pick_location
    #px+GLuint modelview_pick_location
#px+cdef Program program

#px/cdef struct SelectionDebugPoints:
class selection_debug_points:
    #px/float modelview1[3]
    modelview1 = [0.] * 3
    #px/float modelview2[3]
    modelview2 = [0.] * 3
    #px/int viewport1[2]
    viewport1 = [0] * 2
    #px/int viewport2[2]
    viewport2 = [0] * 2
#px+cdef SelectionDebugPoints selection_debug_points

### module state

def init_module():
    terrain.red = 0.0
    terrain.green = 0.0
    terrain.blue = 0.0
    terrain.width = 1
    terrain.height = 1
    
    frustum.fovy_angle = 33.0  # field of view angle
    frustum.fovy_radius = 1 / tan(frustum.fovy_angle * M_PI / 360.0)
    frustum.fovy_radius_zoom = frustum.fovy_radius  # zoom == 1.
    frustum.bounding_sphere_radius = 1.
    frustum.multisample = 0
    # fill modelview_matrix
    matrix_set_identity(frustum.modelview_matrix)
    _update_modelview_matrix_translation()
    # fill projection_matrix, see doc of _glFrustum
    matrix_set_identity(frustum.projection_matrix)
    frustum.projection_matrix[2][3] = -1.
    frustum.projection_matrix[3][3] = 0.
    _update_projection_matrix()
    # fill picking_matrix
    matrix_set_identity(frustum.picking_matrix)
    
    program.prog_render = 0
    program.prog_hud = 0
    program.prog_pick = 0

#px/cdef void _update_modelview_matrix_translation():
def _update_modelview_matrix_translation():
    frustum.modelview_matrix[3][2] = -frustum.bounding_sphere_radius * (frustum.fovy_radius_zoom + 1.)
    
#px/cdef void _set_modelview_matrix_rotation(float radiansx, float radiansy):
def _set_modelview_matrix_rotation(radiansx, radiansy):
    #px+cdef vec4 *M
    #px+cdef float sx, sy, cx, cy
    #px+cdef float m00, m11, m12, m20
    
    #px/M = <vec4*>&frustum.modelview_matrix[0][0]
    M = frustum.modelview_matrix
    
    sx = sin(radiansx/2)
    sy = sin(radiansy/2)
    cx = cos(radiansx/2)
    cy = cos(radiansy/2)
    
    m00 = 2*cx*cx - 1.
    m11 = 2*cy*cy - 1.
    m12 = 2*sy*cy
    m20 = 2*sx*cx
    # pylint: disable=C0321,C0326
    M[0][0] =  m00;         M[1][0] = 0.;   M[2][0] =  m20
    M[0][1] =  m12 * m20;   M[1][1] = m11;  M[2][1] = -m00 * m12
    M[0][2] = -m11 * m20;   M[1][2] = m12;  M[2][2] =  m00 * m11
    
#px/cdef void _update_projection_matrix():
def _update_projection_matrix():
    if terrain.width < terrain.height:
        aspectx = 1.
        aspecty = terrain.height / terrain.width
    else:
        aspectx = terrain.width / terrain.height
        aspecty = 1.
    # see doc of _glFrustum
    frustum.projection_matrix[0][0] = frustum.fovy_radius / aspectx
    frustum.projection_matrix[1][1] = frustum.fovy_radius / aspecty
    frustum.projection_matrix[2][2] = -(frustum.fovy_radius_zoom + 1.)
    frustum.projection_matrix[3][2] = -(frustum.fovy_radius_zoom + 2.
                    ) * frustum.bounding_sphere_radius * frustum.fovy_radius_zoom
        
#px/cdef void _set_picking_matrix(int x, int y):
def _set_picking_matrix(x, y):
    # Set picking matrix, restrict drawing to one pixel of the viewport
    # same as:  _glLoadIdentity()
    #           _gluPickMatrix(x, y, 1, 1, viewport)
    # same as:  _glLoadIdentity()
    #           _glTranslatef(terrain.width - 2*x, terrain.height - 2*y, 0.)
    #           _glScalef(terrain.width, terrain.height, 1.0)
    frustum.picking_matrix[3][0] = terrain.width - 2*x
    frustum.picking_matrix[3][1] = terrain.height - 2*y
    frustum.picking_matrix[0][0] = terrain.width
    frustum.picking_matrix[1][1] = terrain.height
    
#px/cdef void _set_picking_matrix_identity():
def _set_picking_matrix_identity():
    frustum.picking_matrix[3][0] = 0.
    frustum.picking_matrix[3][1] = 0.
    frustum.picking_matrix[0][0] = 1.
    frustum.picking_matrix[1][1] = 1.
    
def set_frustum(bounding_sphere_radius, zoom):
    if bounding_sphere_radius > 0:
        frustum.bounding_sphere_radius = bounding_sphere_radius
    frustum.fovy_radius_zoom = frustum.fovy_radius / zoom
    _update_modelview_matrix_translation()
    _update_projection_matrix()
    
def set_background_color(red, green, blue):
    terrain.red = red
    terrain.green = green
    terrain.blue = blue
    
def set_antialiasing(multisample):
    frustum.multisample = multisample
    
def set_rotation_xy(x, y):
    x %= 360
    # pylint: disable=C0321
    if y < -90:     y = -90
    elif y > 90:    y = 90
    _set_modelview_matrix_rotation(M_PI * x / 180.0, M_PI * y / 180.0)
    return x, y
    
### GL state

#px/cdef void _gl_print_string(msg, GLenum name):
def _gl_print_string(msg, name):
    #px/print(msg, <char*>glGetString(name))
    print(msg, glGetString(name))
    
#px/cdef void _gl_print_float(msg, GLenum name):
def _gl_print_float(msg, name):
    #px+cdef GLfloat i
    #px/glGetFloatv(name, &i)
    i = glGetFloatv(name)
    print(msg, i)
    
#px/cdef void _gl_print_integer(msg, GLenum name):
def _gl_print_integer(msg, name):
    #px+cdef GLint i
    #px/glGetIntegerv(name, &i)
    i = glGetIntegerv(name)
    print(msg, i)
    
#px/cdef void _gl_print_bool(msg, GLenum name):
def _gl_print_bool(msg, name):
    #px+cdef GLboolean i
    #px/glGetBooleanv(name, &i)
    i = glGetBooleanv(name)
    print(msg, i)
    
def gl_init():
    if DEBUG_MSG:
        print('GL Strings:')
        _gl_print_string('  GL Vendor:', GL_VENDOR)
        _gl_print_string('  GL Renderer:', GL_RENDERER)
        _gl_print_string('  GL Version:', GL_VERSION)
        _gl_print_string('  GL Shading Language Version:', GL_SHADING_LANGUAGE_VERSION)
        #_gl_print_string('  GL Extensions:', GL_EXTENSIONS)
        _gl_print_integer('  GL_SAMPLE_BUFFERS:', GL_SAMPLE_BUFFERS)
        _gl_print_float('  GL_SAMPLE_COVERAGE_VALUE:', GL_SAMPLE_COVERAGE_VALUE)
        _gl_print_bool('  GL_SAMPLE_COVERAGE_INVERT:', GL_SAMPLE_COVERAGE_INVERT)
        _gl_print_integer('  GL_SAMPLES:', GL_SAMPLES)
        #px/IF SOURCEGLVERSION == 'GL':
        if True:
            print('  GL_MULTISAMPLE:', glIsEnabled(GL_MULTISAMPLE))
        print('  GL_SAMPLE_ALPHA_TO_COVERAGE:', glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE))
        #print('  GL_SAMPLE_ALPHA_TO_ONE:', glIsEnabled(GL_SAMPLE_ALPHA_TO_ONE))
        print('  GL_SAMPLE_COVERAGE:', glIsEnabled(GL_SAMPLE_COVERAGE))
        _gl_print_integer('  GL_MAX_VERTEX_ATTRIBS:', GL_MAX_VERTEX_ATTRIBS)
    glClearColor(0., 0., 0., 1.)
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_CULL_FACE)
    glCullFace(GL_BACK)
    glFrontFace(GL_CCW)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    gl_init_buffers()
    
def gl_exit():
    gl_delete_buffers()
    #px+cdef GLuint prog
    for prog in [program.prog_render, program.prog_hud, program.prog_pick]:
        if prog > 0:
            glDeleteProgram(prog)
    program.prog_render = 0
    program.prog_hud = 0
    program.prog_pick = 0
    
def gl_resize(width, height):
    terrain.width = width
    terrain.height = height
    glViewport(0, 0, terrain.width, terrain.height)
    _update_projection_matrix()
    
### render functions

#px/cdef void _gl_set_matrix(GLint location, mat4 &matrix):
def _gl_set_matrix(location, matrix):
    #px/glUniformMatrix4fv(location, 1, GL_FALSE, &matrix[0][0])
    glUniformMatrix4fv(location, 1, GL_FALSE, matrix)
    
def gl_render():
    if DEBUG_PICK:
        _set_picking_matrix_identity()
        _gl_render_pick()
    else:
        glUseProgram(program.prog_render)
        #px/IF SOURCEGLVERSION == 'GL':
        if True:
            if frustum.multisample:
                glEnable(GL_MULTISAMPLE)
            else:
                glDisable(GL_MULTISAMPLE)
        glClearColor(terrain.red, terrain.green, terrain.blue, 1.)
        _gl_set_matrix(program.projection_location, frustum.projection_matrix)
        _gl_set_matrix(program.modelview_location, frustum.modelview_matrix)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        gl_draw_cube()
    if DEBUG_DRAW:
        gl_draw_cube_debug()
        
def gl_render_select_debug():
    #px/cdef GLfloat selectdata[12]
    selectdata = [0.] * 12
    selectdata[0] = selection_debug_points.modelview1[0]
    selectdata[1] = selection_debug_points.modelview1[1]
    selectdata[2] = selection_debug_points.modelview1[2]
    selectdata[3] = selection_debug_points.modelview2[0]
    selectdata[4] = selection_debug_points.modelview2[1]
    selectdata[5] = selection_debug_points.modelview2[2]
    selectdata[6] = selection_debug_points.viewport1[0] / terrain.width * 2 - 1
    selectdata[7] = selection_debug_points.viewport1[1] / terrain.height * 2 - 1
    selectdata[8] = 0
    selectdata[9] = selection_debug_points.viewport2[0] / terrain.width * 2 - 1
    selectdata[10] = selection_debug_points.viewport2[1] / terrain.height * 2 - 1
    selectdata[11] = 0
    #px/gl_draw_select_debug(&selectdata[0], sizeof(selectdata), program.prog_hud)
    gl_draw_select_debug(selectdata, 0, program.prog_hud)
    

### picking functions

#px/cdef void _gl_render_pick():
def _gl_render_pick():
    glUseProgram(program.prog_pick)
    #px/IF SOURCEGLVERSION == 'GL':
    if True:
        glDisable(GL_MULTISAMPLE)
    glClearColor(0., 0., 0., 1.)
    _gl_set_matrix(program.picking_location, frustum.picking_matrix)
    _gl_set_matrix(program.projection_pick_location, frustum.projection_matrix)
    _gl_set_matrix(program.modelview_pick_location, frustum.modelview_matrix)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    gl_pick_cube()
    
#px/cpdef gl_pick_polygons(int x, int y):
def gl_pick_polygons(x, y):
    #px+cdef unsigned char pixel[3]
    
    if not (0 <= x < terrain.width and 0 <= y < terrain.height):
        return 0
    _set_picking_matrix(x, y)
    _gl_render_pick()
    #px/glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel)
    pixel = glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, [[[0, 0, 0]]])[0][0]
    index = pixel[0]<<4 | pixel[1] | pixel[2]>>4
    return index
    
#px/cdef void _modelview_to_viewport(float *vvect, int *mvect):
def _modelview_to_viewport(vvect, mvect):
    #px+cdef vec4 *M
    #px+cdef vec4 *P
    #px+cdef float u0, u1, u2, v0, v1, v3
    
    #px/M = <vec4*>&frustum.modelview_matrix[0][0]
    M = frustum.modelview_matrix
    #px/P = <vec4*>&frustum.projection_matrix[0][0]
    P = frustum.projection_matrix
    
    # u = M^T * vvect
    #assert M[1][0] == 0
    u0 = M[0][0]*vvect[0] + M[1][0]*vvect[1] + M[2][0]*vvect[2] + M[3][0]
    u1 = M[0][1]*vvect[0] + M[1][1]*vvect[1] + M[2][1]*vvect[2] + M[3][1]
    u2 = M[0][2]*vvect[0] + M[1][2]*vvect[1] + M[2][2]*vvect[2] + M[3][2]
    #u3 = 1.
    
    # v = P * u
    v0 = P[0][0] * u0 + P[1][0] * u1 + P[2][0] * u2 + P[3][0] #* u3
    v1 = P[0][1] * u0 + P[1][1] * u1 + P[2][1] * u2 + P[3][1] #* u3
    #v2 = P[0][2] * u0 + P[1][2] * u1 + P[2][2] * u2 + P[3][2] * u3
    v3 = P[0][3] * u0 + P[1][3] * u1 + P[2][3] * u2 + P[3][3] #* u3
    
    mvect[0] = int((v0 / v3 + 1) / 2 * terrain.width)
    mvect[1] = int((v1 / v3 + 1) / 2 * terrain.height)
    
#px/cdef void _viewport_to_modelview(int *vvect, float *mvect):
def _viewport_to_modelview(vvect, mvect):
    #px+cdef vec4 *MT
    #px+cdef vec4 *P
    #px+cdef float u0, u1, u2, u3, v0, v1, v2
    
    v0 = vvect[0] / terrain.width * 2 - 1
    v1 = vvect[1] / terrain.height * 2 - 1
    v2 = 0
    #v3 = 1
    
    # P * P^-1 = I
    # a 0 0 0     A 0 0 0     aA 0  0  0      E = -eD/c
    # 0 b 0 0  *  0 B 0 0  =  0  bB 0  0 ,    
    # 0 0 e c     0 0 0 D     0  0  cC 0      
    # 0 0 d 0     0 0 C E     0  0  0  dD     
    #px/P = <vec4*>&frustum.projection_matrix[0][0]
    P = frustum.projection_matrix
    # u = P^-1 * v
    u0 = 1 / P[0][0] * v0
    u1 = 1 / P[1][1] * v1
    u2 = 1 / P[2][3]
    #assert u2 == -1
    u3 = - P[2][2] / P[3][2] / P[2][3]
    
    # MT * MT^-1 = I
    # a b c 0     a 0 f 0     1 0 0 0    U = -cz
    # 0 d e 0  *  b d g 0  =  0 1 0 0,   V = -ez
    # f g h 0     c e h 0     0 0 1 0    W = -hz
    # 0 0 z 1     U V W 1     0 0 0 1
    #px/MT = <vec4*>&frustum.modelview_matrix[0][0]
    MT = frustum.modelview_matrix
    # v = M^-1 * u
    v0 = MT[0][0]*u0 + MT[0][1]*u1 + MT[0][2]*u2 - MT[0][2] * MT[3][2]*u3
    v1 = MT[1][0]*u0 + MT[1][1]*u1 + MT[1][2]*u2 - MT[1][2] * MT[3][2]*u3
    v2 = MT[2][0]*u0 + MT[2][1]*u1 + MT[2][2]*u2 - MT[2][2] * MT[3][2]*u3
    #v3 = u3
    
    mvect[0] = v0 / u3
    mvect[1] = v1 / u3
    mvect[2] = v2 / u3
    
def get_cursor_angle(xi, yi, d):
    #px+cdef float angle
    #px+cdef int i
    #px+cdef float x, y
    selection_debug_points.viewport1[0] = xi
    selection_debug_points.viewport1[1] = yi
    _viewport_to_modelview(selection_debug_points.viewport1, selection_debug_points.modelview1)
    for i in range(3):
        selection_debug_points.modelview2[i] = d[i] + selection_debug_points.modelview1[i]
    _modelview_to_viewport(selection_debug_points.modelview2, selection_debug_points.viewport2)
    x = selection_debug_points.viewport1[0] - selection_debug_points.viewport2[0]
    y = selection_debug_points.viewport1[1] - selection_debug_points.viewport2[1]
    angle = atan2(x, y) * 180.0 / M_PI
    return angle
    
    
### shader functions

#px/cdef void _gl_print_shader_log(GLuint shader):
def _gl_print_shader_log(shader):
    #px+cdef GLint log_len
    #px+cdef GLsizei length
    #px+cdef char log[1024]
    #px/glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len)
    log_len = glGetShaderiv(shader, GL_INFO_LOG_LENGTH)
    if log_len > 0:
        print('==== Error compiling shader:')
        #px/glGetShaderInfoLog(shader, 1023, &length, log)
        log = glGetShaderInfoLog(shader)
        print(log.decode('utf-8').rstrip())
        print('====')
    else:
        print('==== Error compiling shader (no log)')
        
#px/cdef void _gl_print_program_log(GLuint program):
def _gl_print_program_log(program):
    #px+cdef GLint log_len
    #px+cdef GLsizei length
    #px+cdef char log[1024]
    #px/glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len)
    log_len = glGetProgramiv(program, GL_INFO_LOG_LENGTH)
    if log_len > 0:
        print('==== Error linking shader program:')
        #px/glGetProgramInfoLog(program, 1023, &length, log)
        log = glGetProgramInfoLog(program)
        print(log.decode('utf-8').rstrip())
        print('====')
    else:
        print('==== Error linking shader program (no log)')
        
#px/cdef GLuint _gl_create_compiled_shader(GLenum shadertype, bytes source):
def _gl_create_compiled_shader(shadertype, source):
    #px+cdef GLuint shader
    #px+cdef const_GLchar_ptr pchar
    #px+cdef GLint compile_status
    
    shader = glCreateShader(shadertype)
    if shader == 0:
        print('Failed to create shader')
        return 0
    #px+pchar = <const_GLchar_ptr><char*>source
    #px/glShaderSource(shader, 1, &pchar, NULL)
    glShaderSource(shader, source.decode('utf-8')) # PyOpenGL needs str currently
    glCompileShader(shader)
    #px/glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status)
    compile_status = glGetShaderiv(shader, GL_COMPILE_STATUS)
    if not compile_status:
        _gl_print_shader_log(shader)
        return 0
    return shader
    
#px/cdef void _gl_print_program_info(GLuint program):
def _gl_print_program_info(program):
    #px+cdef GLint param
    #px+cdef int i
    #px+cdef GLsizei alength
    #px+cdef GLint asize, location
    #px+cdef GLenum atype
    #px+cdef char aname[1024]
    
    print('shader program info', program)
    glValidateProgram(program)
    program_info = {}
    for msg, pname in [('delete status', GL_DELETE_STATUS),
                       ('link status', GL_LINK_STATUS),
                       ('validate status', GL_VALIDATE_STATUS),
                       ('info log length', GL_INFO_LOG_LENGTH),
                       ('attached shaders', GL_ATTACHED_SHADERS),
                       ('active attributes', GL_ACTIVE_ATTRIBUTES),
                       ('active attribute max length', GL_ACTIVE_ATTRIBUTE_MAX_LENGTH),
                       ('active uniforms', GL_ACTIVE_UNIFORMS),
                       ('active uniform max length', GL_ACTIVE_UNIFORM_MAX_LENGTH)]:
        #px/glGetProgramiv(program, pname, &param)
        param = glGetProgramiv(program, pname)
        program_info[pname] = param
        print(' ', msg, param)
    print('active attributes:')
    for i in range(program_info[GL_ACTIVE_ATTRIBUTES]):
        #px/glGetActiveAttrib(program, i, 1023, &alength, &asize, &atype, aname)
        aname, asize, atype = glGetActiveAttrib(program, i); alength = len(aname)
        location = glGetAttribLocation(program, aname)
        print('  {}, {}: length={} size={} type={} location={}'.format(i, aname, alength, asize, atype, location))
    print('active uniforms:')
    for i in range(program_info[GL_ACTIVE_UNIFORMS]):
        #px/glGetActiveUniform(program, i, 1023, &alength, &asize, &atype, aname)
        aname, asize, atype = glGetActiveUniform(program, i); alength = len(aname)
        location = glGetUniformLocation(program, aname)
        print('  {}, {}: length={} size={} type={} location={}'.format(i, aname, alength, asize, atype, location))
        
#px/cdef GLuint _gl_create_program(bytes vertex_source, bytes fragment_source, int attrib_location, list attributes):
def _gl_create_program(vertex_source, fragment_source, attrib_location, attributes):
    #px+cdef GLuint vertex_shader = 0, fragment_shader = 0
    #px+cdef GLuint program
    #px+cdef GLint link_status
    
    if DEBUG_NOSHADER:
        return 0
        
    program = glCreateProgram()
    if not DEBUG_NOVSHADER:
        debug('  creating vertex shader')
        vertex_shader = _gl_create_compiled_shader(GL_VERTEX_SHADER, vertex_source)
        if vertex_shader:
            glAttachShader(program, vertex_shader)
        else:
            print('error: no vertex shader')
    if not DEBUG_NOFSHADER:
        debug('  creating fragment shader')
        fragment_shader = _gl_create_compiled_shader(GL_FRAGMENT_SHADER, fragment_source)
        if fragment_shader:
            glAttachShader(program, fragment_shader)
        else:
            print('error: no fragment shader')
    for index, name in enumerate(attributes):
        if name is not None:
            glBindAttribLocation(program, attrib_location+index, name)
    glLinkProgram(program)
    #px/glGetProgramiv(program, GL_LINK_STATUS, &link_status)
    link_status = glGetProgramiv(program, GL_LINK_STATUS)
    if not link_status:
        _gl_print_program_log(program)
        return 0
    if DEBUG_MSG:
        _gl_print_program_info(program)
    glDetachShader(program, vertex_shader)
    glDetachShader(program, fragment_shader)
    glDeleteShader(vertex_shader)
    glDeleteShader(fragment_shader)
    return program
    
#px/def gl_create_render_program(bytes vertex_source, bytes fragment_source):
def gl_create_render_program(vertex_source, fragment_source):
    #px+cdef GLint location
    if program.prog_render > 0:
        glDeleteProgram(program.prog_render)
    attributes = [
                  b'vertex_attr',
                  b'normal_attr',
                  b'color_attr',
                  b'texcoord_attr',
                  b'barycentric_attr',
                 ]
    program.prog_render = _gl_create_program(vertex_source, fragment_source, ATTRIB_LOCATION, attributes)
    if program.prog_render > 0:
        glUseProgram(program.prog_render)
        location = glGetUniformLocation(program.prog_render, b'tex')
        glUniform1i(location, 0) # 0 is the texture unit (-> glActiveTexture)
        program.modelview_location = glGetUniformLocation(program.prog_render, b'modelview')
        program.projection_location = glGetUniformLocation(program.prog_render, b'projection')
        location = glGetUniformLocation(program.prog_render, b'object')
        gl_init_object_location(location)
        
#px/def gl_create_hud_program(bytes vertex_source, bytes fragment_source):
def gl_create_hud_program(vertex_source, fragment_source):
    #px+cdef GLint location
    if program.prog_hud > 0:
        glDeleteProgram(program.prog_hud)
    attributes = [b'vertex_attr', None, b'color_attr']
    program.prog_hud = _gl_create_program(vertex_source, fragment_source, ATTRIB_LOCATION, attributes)
    if program.prog_hud > 0:
        glUseProgram(program.prog_hud)
    
#px/def gl_create_pick_program(bytes vertex_source, bytes fragment_source):
def gl_create_pick_program(vertex_source, fragment_source):
    if program.prog_pick > 0:
        glDeleteProgram(program.prog_pick)
    attributes = [b'vertex_attr', b'color_attr']
    program.prog_pick = _gl_create_program(vertex_source, fragment_source, PICKATTRIB_LOCATION, attributes)
    if program.prog_pick > 0:
        glUseProgram(program.prog_pick)
        program.picking_location = glGetUniformLocation(program.prog_pick, b'picking')
        program.projection_pick_location = glGetUniformLocation(program.prog_pick, b'projection')
        program.modelview_pick_location = glGetUniformLocation(program.prog_pick, b'modelview')
    

