diff --git a/kitty/gl.h b/kitty/gl.h index da6dc2435..1da7b3571 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -22,19 +22,10 @@ static char glbuf[4096]; #define REQUIRED_VERSION_MINOR 3 #define GLSL_VERSION (REQUIRED_VERSION_MAJOR * 100 + REQUIRED_VERSION_MINOR * 10) -#ifndef GL_STACK_UNDERFLOW -#define GL_STACK_UNDERFLOW 0x0504 -#endif - -#ifndef GL_STACK_OVERFLOW -#define GL_STACK_OVERFLOW 0x0503 -#endif - -#ifdef ENABLE_DEBUG_GL static void -check_for_gl_error(int line) { -#define f(msg) fatal("%s (at line: %d)", msg, line); break; - int code = glGetError(); +check_for_gl_error(const char *name, void UNUSED *funcptr, int UNUSED len_args, ...) { +#define f(msg) fatal("OpenGL error: %s (calling function: %s)", msg, name); break; + GLenum code = glad_glGetError(); switch(code) { case GL_NO_ERROR: break; case GL_INVALID_ENUM: @@ -52,16 +43,11 @@ check_for_gl_error(int line) { case GL_STACK_OVERFLOW: f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_OVERFLOW)"); default: - fatal("An unknown OpenGL error occurred with code: %d (at line: %d)", code, line); + fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name); break; } } -#define check_gl() { check_for_gl_error(__LINE__); } -#else -#define check_gl() {} -#endif - static PyObject* gl_init(PyObject UNUSED *self, PyObject *args) { int is_wayland, debug; @@ -69,6 +55,7 @@ gl_init(PyObject UNUSED *self, PyObject *args) { if (!init_glad((GLADloadproc) glfwGetProcAddress, debug)) { fatal("Loading the OpenGL library failed"); } + glad_set_post_callback(check_for_gl_error); #define ARB_TEST(name) \ if (!GLAD_GL_ARB_##name) { \ fatal("The OpenGL driver on this system is missing the required extension: ARB_%s", #name); \ @@ -81,25 +68,25 @@ gl_init(PyObject UNUSED *self, PyObject *args) { static void update_viewport_size_impl(int w, int h) { - glViewport(0, 0, w, h); check_gl(); + glViewport(0, 0, w, h); } static void free_texture_impl(GLuint *tex_id) { - glDeleteTextures(1, tex_id); check_gl(); + glDeleteTextures(1, tex_id); *tex_id = 0; } static void send_image_to_gpu_impl(GLuint *tex_id, const void* data, GLsizei width, GLsizei height, bool is_opaque, bool is_4byte_aligned) { - if (!(*tex_id)) { glGenTextures(1, tex_id); check_gl(); } - glBindTexture(GL_TEXTURE_2D, *tex_id); check_gl(); - glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1); check_gl(); + if (!(*tex_id)) { glGenTextures(1, tex_id); } + glBindTexture(GL_TEXTURE_2D, *tex_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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); check_gl(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); check_gl(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); } @@ -132,11 +119,8 @@ static Program programs[64] = {{0}}; static inline GLuint compile_shader(GLenum shader_type, const char *source) { GLuint shader_id = glCreateShader(shader_type); - check_gl(); glShaderSource(shader_id, 1, (const GLchar **)&source, NULL); - check_gl(); glCompileShader(shader_id); - check_gl(); GLint ret = GL_FALSE; glGetShaderiv(shader_id, GL_COMPILE_STATUS, &ret); if (ret != GL_TRUE) { @@ -157,11 +141,9 @@ static inline void init_uniforms(int program) { Program *p = programs + program; glGetProgramiv(p->id, GL_ACTIVE_UNIFORMS, &(p->num_of_uniforms)); - check_gl(); for (GLint i = 0; i < p->num_of_uniforms; i++) { Uniform *u = p->uniforms + i; glGetActiveUniform(p->id, (GLuint)i, sizeof(u->name)/sizeof(u->name[0]), NULL, &(u->size), &(u->type), u->name); - check_gl(); u->location = glGetUniformLocation(p->id, u->name); u->idx = i; } @@ -181,14 +163,12 @@ get_uniform_information(int program, const char *name, GLenum information_type) static inline GLint attrib_location(int program, const char *name) { GLint ans = glGetAttribLocation(programs[program].id, name); - check_gl(); return ans; } static inline GLuint block_index(int program, const char *name) { GLuint ans = glGetUniformBlockIndex(programs[program].id, name); - check_gl(); if (ans == GL_INVALID_INDEX) { fatal("Could not find block index"); } return ans; } @@ -198,20 +178,17 @@ static inline GLint block_size(int program, GLuint block_index) { GLint ans; glGetActiveUniformBlockiv(programs[program].id, block_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ans); - check_gl(); return ans; } static inline void bind_program(int program) { glUseProgram(programs[program].id); - check_gl(); } static inline void unbind_program() { glUseProgram(0); - check_gl(); } // }}} @@ -230,7 +207,6 @@ static ssize_t create_buffer(GLenum usage) { GLuint buffer_id; glGenBuffers(1, &buffer_id); - check_gl(); for (size_t i = 0; i < sizeof(buffers)/sizeof(buffers[0]); i++) { if (buffers[i].id == 0) { buffers[i].id = buffer_id; @@ -247,7 +223,6 @@ create_buffer(GLenum usage) { static void delete_buffer(ssize_t buf_idx) { glDeleteBuffers(1, &(buffers[buf_idx].id)); - check_gl(); buffers[buf_idx].id = 0; buffers[buf_idx].size = 0; } @@ -255,14 +230,12 @@ delete_buffer(ssize_t buf_idx) { static GLuint bind_buffer(ssize_t buf_idx) { glBindBuffer(buffers[buf_idx].usage, buffers[buf_idx].id); - check_gl(); return buffers[buf_idx].id; } static void unbind_buffer(ssize_t buf_idx) { glBindBuffer(buffers[buf_idx].usage, 0); - check_gl(); } static inline void @@ -271,20 +244,17 @@ alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) { if (b->size == size) return; b->size = size; glBufferData(b->usage, size, NULL, usage); - check_gl(); } static inline void* map_buffer(ssize_t idx, GLenum access) { void *ans = glMapBuffer(buffers[idx].usage, access); - check_gl(); return ans; } static inline void unmap_buffer(ssize_t idx) { glUnmapBuffer(buffers[idx].usage); - check_gl(); } // }}} @@ -303,13 +273,11 @@ static ssize_t create_vao() { GLuint vao_id; glGenVertexArrays(1, &vao_id); - check_gl(); for (size_t i = 0; i < sizeof(vaos)/sizeof(vaos[0]); i++) { if (!vaos[i].id) { vaos[i].id = vao_id; vaos[i].num_buffers = 0; glBindVertexArray(vao_id); - check_gl(); return i; } } @@ -336,7 +304,6 @@ add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum dat ssize_t buf = vao->buffers[vao->num_buffers - 1]; bind_buffer(buf); glEnableVertexAttribArray(aloc); - check_gl(); switch(data_type) { case GL_BYTE: case GL_UNSIGNED_BYTE: @@ -350,10 +317,8 @@ add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum dat glVertexAttribPointer(aloc, size, data_type, GL_FALSE, stride, offset); break; } - check_gl(); if (divisor) { glVertexAttribDivisor(aloc, divisor); - check_gl(); } unbind_buffer(buf); } @@ -374,20 +339,17 @@ remove_vao(ssize_t vao_idx) { delete_buffer(vao->buffers[vao->num_buffers]); } glDeleteVertexArrays(1, &(vao->id)); - check_gl(); vaos[vao_idx].id = 0; } static void bind_vertex_array(ssize_t vao_idx) { glBindVertexArray(vaos[vao_idx].id); - check_gl(); } static void unbind_vertex_array() { glBindVertexArray(0); - check_gl(); } static ssize_t @@ -415,7 +377,6 @@ static void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; glBindBufferBase(GL_UNIFORM_BUFFER, block_index, buffers[buf_idx].id); - check_gl(); } static void diff --git a/kitty/shaders.c b/kitty/shaders.c index fbba51865..610ed7b0a 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -36,15 +36,15 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int uint8_t *src = malloc(5 * width * height * num_levels); if (src == NULL) { fatal("Out of memory."); } uint8_t *dest = src + (4 * width * height * num_levels); - glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); check_gl(); - glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); check_gl(); - glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); check_gl(); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl(); + glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); + glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for(size_t i = 0; i < width * height * num_levels; i++) dest[i] = src[4*i]; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest); check_gl(); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest); free(src); } else { - glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); check_gl(); + glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); } } @@ -52,24 +52,24 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int static void realloc_sprite_texture() { GLuint tex; - glGenTextures(1, &tex); check_gl(); - glBindTexture(GL_TEXTURE_2D_ARRAY, tex); check_gl(); + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D_ARRAY, tex); // We use GL_NEAREST otherwise glyphs that touch the edge of the cell // often show a border between cells glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_gl(); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); unsigned int xnum, ynum, z, znum, width, height, src_ynum; sprite_map_current_layout(&xnum, &ynum, &z); znum = z + 1; width = xnum * sprite_map.cell_width; height = ynum * sprite_map.cell_height; - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R8, width, height, znum); check_gl(); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R8, width, height, znum); if (sprite_map.texture_id) { // need to re-alloc src_ynum = MAX(1, sprite_map.last_ynum); copy_image_sub_data(sprite_map.texture_id, tex, width, src_ynum * sprite_map.cell_height, sprite_map.last_num_of_layers); - glDeleteTextures(1, &sprite_map.texture_id); check_gl(); + glDeleteTextures(1, &sprite_map.texture_id); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); sprite_map.last_num_of_layers = znum; @@ -91,8 +91,8 @@ ensure_sprite_map() { static GLuint bound_texture_id = 0; if (!sprite_map.texture_id) realloc_sprite_texture(); if (bound_texture_id != sprite_map.texture_id) { - glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); check_gl(); - glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl(); + glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); bound_texture_id = sprite_map.texture_id; } } @@ -102,14 +102,14 @@ sprite_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, PyObject *buf unsigned int xnum, ynum, znum; sprite_map_current_layout(&xnum, &ynum, &znum); if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture(); - glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl(); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl(); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); x *= sprite_map.cell_width; y *= sprite_map.cell_height; PyObject *ret = PyObject_CallObject(buf, NULL); if (ret == NULL) { PyErr_Print(); fatal("Failed to get address of rendered cell buffer"); } void *address = PyLong_AsVoidPtr(ret); Py_DECREF(ret); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map.cell_width, sprite_map.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, address); check_gl(); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map.cell_width, sprite_map.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, address); Py_DECREF(buf); } @@ -139,8 +139,8 @@ layout_sprite_map(unsigned int cell_width, unsigned int cell_height, PyObject *r global_state.cell_width = sprite_map.cell_width; global_state.cell_height = sprite_map.cell_height; if (sprite_map.max_texture_size == 0) { - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size)); check_gl(); - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers)); check_gl(); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size)); + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers)); sprite_map_set_limits(sprite_map.max_texture_size, sprite_map.max_array_texture_layers); } sprite_map_set_layout(sprite_map.cell_width, sprite_map.cell_height); @@ -161,7 +161,6 @@ destroy_sprite_map() { Py_CLEAR(sprite_map.render_cell); if (sprite_map.texture_id) { glDeleteTextures(1, &(sprite_map.texture_id)); - check_gl(); sprite_map.texture_id = 0; } } @@ -314,16 +313,16 @@ draw_graphics(ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *data, GLuint s glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); static bool graphics_constants_set = false; if (!graphics_constants_set) { - glUniform1i(glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "image"), GRAPHICS_UNIT); check_gl(); + glUniform1i(glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "image"), GRAPHICS_UNIT); graphics_constants_set = true; } - glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); check_gl(); + glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); GLuint base = 4 * start; glEnable(GL_SCISSOR_TEST); for (GLuint i=0; i < count;) { ImageRenderData *rd = data + start + i; - glBindTexture(GL_TEXTURE_2D, rd->texture_id); check_gl(); + glBindTexture(GL_TEXTURE_2D, rd->texture_id); // You could reduce the number of draw calls by using // glDrawArraysInstancedBaseInstance but Apple chose to abandon OpenGL // before implementing it. @@ -338,26 +337,26 @@ draw_all_cells(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) { bind_program(CELL_PROGRAM); static bool cell_constants_set = false; if (!cell_constants_set) { - glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT); check_gl(); + glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT); cell_constants_set = true; } - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); check_gl(); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); if (screen->grman->count) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->count); } static void draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) { bind_program(CELL_BACKGROUND_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); check_gl(); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); if (screen->grman->num_of_negative_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs); bind_program(CELL_SPECIAL_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); check_gl(); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); bind_program(CELL_FOREGROUND_PROGRAM); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); check_gl(); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs); } @@ -401,10 +400,10 @@ init_cursor_program() { static void draw_cursor_impl(CursorRenderInfo *cursor) { - bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array); check_gl(); - glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0); check_gl(); - glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom); check_gl(); - glDrawArrays(global_state.application_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4); check_gl(); + bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array); + glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0); + glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom); + glDrawArrays(global_state.application_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4); unbind_vertex_array(); unbind_program(); } // }}} @@ -442,7 +441,6 @@ draw_borders_impl() { bind_program(BORDERS_PROGRAM); bind_vertex_array(border_vertex_array); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects); - check_gl(); unbind_vertex_array(); unbind_program(); } @@ -469,7 +467,6 @@ send_borders_rects(GLuint vw, GLuint vh) { } bind_program(BORDERS_PROGRAM); glUniform2ui(border_uniform_locations[BORDER_viewport], vw, vh); - check_gl(); unbind_program(); } // }}} @@ -484,12 +481,11 @@ compile_program(PyObject UNUSED *self, PyObject *args) { if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; } if (programs[which].id != 0) { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; } programs[which].id = glCreateProgram(); - check_gl(); - vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader); check_gl(); - fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader); check_gl(); - glAttachShader(programs[which].id, vertex_shader_id); check_gl(); - glAttachShader(programs[which].id, fragment_shader_id); check_gl(); - glLinkProgram(programs[which].id); check_gl(); + vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader); + fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader); + glAttachShader(programs[which].id, vertex_shader_id); + glAttachShader(programs[which].id, fragment_shader_id); + glLinkProgram(programs[which].id); GLint ret = GL_FALSE; glGetProgramiv(programs[which].id, GL_LINK_STATUS, &ret); if (ret != GL_TRUE) { @@ -504,7 +500,6 @@ compile_program(PyObject UNUSED *self, PyObject *args) { end: if (vertex_shader_id != 0) glDeleteShader(vertex_shader_id); if (fragment_shader_id != 0) glDeleteShader(fragment_shader_id); - check_gl(); if (PyErr_Occurred()) { glDeleteProgram(programs[which].id); programs[which].id = 0; return NULL;} return Py_BuildValue("I", programs[which].id); Py_RETURN_NONE;