diff --git a/kitty/gl.h b/kitty/gl.h index 38cb7865d..16fb584ff 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -152,6 +152,175 @@ DrawArraysInstanced(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +CreateProgram(PyObject UNUSED *self) { + GLuint ans = glCreateProgram(); + if (!ans) { SET_GL_ERR; return NULL; } + return PyLong_FromUnsignedLong(ans); +} + +static PyObject* +AttachShader(PyObject UNUSED *self, PyObject *args) { + unsigned int program_id, shader_id; + if (!PyArg_ParseTuple(args, "II", &program_id, &shader_id)) return NULL; + glAttachShader(program_id, shader_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +LinkProgram(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + glLinkProgram(program_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +GetProgramiv(PyObject UNUSED *self, PyObject *args) { + unsigned int program_id; + int pname, ans = 0; + if (!PyArg_ParseTuple(args, "Ii", &program_id, &pname)) return NULL; + glGetProgramiv(program_id, pname, &ans); + CHECK_ERROR; + return PyLong_FromLong((long)ans); +} + +static PyObject* +GetProgramInfoLog(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + int log_len = 0; + GLchar *buf = NULL; + glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &log_len); + buf = PyMem_Calloc(log_len + 10, sizeof(GLchar)); + if (buf == NULL) return PyErr_NoMemory(); + glGetProgramInfoLog(program_id, log_len, &log_len, buf); + PyObject *ans = PyBytes_FromStringAndSize(buf, log_len); + PyMem_Free(buf); + return ans; +} + +static PyObject* +GetShaderInfoLog(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + int log_len = 0; + GLchar *buf = NULL; + glGetShaderiv(program_id, GL_INFO_LOG_LENGTH, &log_len); + buf = PyMem_Calloc(log_len + 10, sizeof(GLchar)); + if (buf == NULL) return PyErr_NoMemory(); + glGetShaderInfoLog(program_id, log_len, &log_len, buf); + PyObject *ans = PyBytes_FromStringAndSize(buf, log_len); + PyMem_Free(buf); + return ans; +} + + +static PyObject* +DeleteProgram(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + glDeleteProgram(program_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +DeleteShader(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + glDeleteShader(program_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +GenVertexArrays(PyObject UNUSED *self, PyObject *val) { + GLuint arrays[256] = {0}; + unsigned long n = PyLong_AsUnsignedLong(val); + if (n > 256) { PyErr_SetString(PyExc_ValueError, "Generating more than 256 arrays in a single call is not supported"); return NULL; } + glGenVertexArrays(n, arrays); + CHECK_ERROR; + if (n == 1) return PyLong_FromUnsignedLong((unsigned long)arrays[0]); + PyObject *ans = PyTuple_New(n); + if (ans == NULL) return PyErr_NoMemory(); + for (size_t i = 0; i < n; i++) { + PyObject *t = PyLong_FromUnsignedLong((unsigned long)arrays[i]); + if (t == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(ans, i, t); + } + return ans; +} + +static PyObject* +CreateShader(PyObject UNUSED *self, PyObject *val) { + long shader_type = PyLong_AsLong(val); + GLuint ans = glCreateShader(shader_type); + CHECK_ERROR; + return PyLong_FromUnsignedLong(ans); +} + +static PyObject* +ShaderSource(PyObject UNUSED *self, PyObject *args) { + char *src; Py_ssize_t src_len = 0; + unsigned int shader_id; + if(!PyArg_ParseTuple(args, "Is#", &shader_id, &src, &src_len)) return NULL; + glShaderSource(shader_id, 1, (const GLchar * const*)&src, NULL); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +CompileShader(PyObject UNUSED *self, PyObject *val) { + unsigned long shader_id = PyLong_AsUnsignedLong(val); + glCompileShader((GLuint)shader_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +GetShaderiv(PyObject UNUSED *self, PyObject *args) { + unsigned int program_id; + int pname, ans = 0; + if (!PyArg_ParseTuple(args, "Ii", &program_id, &pname)) return NULL; + glGetShaderiv(program_id, pname, &ans); + CHECK_ERROR; + return PyLong_FromLong((long)ans); +} + +static PyObject* +GetUniformLocation(PyObject UNUSED *self, PyObject *args) { + char *name; + unsigned int program_id; + if(!PyArg_ParseTuple(args, "Is", &program_id, &name)) return NULL; + GLint ans = glGetUniformLocation(program_id, name); + CHECK_ERROR; + return PyLong_FromLong((long) ans); +} + +static PyObject* +GetAttribLocation(PyObject UNUSED *self, PyObject *args) { + char *name; + unsigned int program_id; + if(!PyArg_ParseTuple(args, "Is", &program_id, &name)) return NULL; + GLint ans = glGetAttribLocation(program_id, name); + CHECK_ERROR; + return PyLong_FromLong((long) ans); +} + +static PyObject* +UseProgram(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + glUseProgram(program_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + +static PyObject* +BindVertexArray(PyObject UNUSED *self, PyObject *val) { + unsigned long program_id = PyLong_AsUnsignedLong(val); + glBindVertexArray(program_id); + CHECK_ERROR; + Py_RETURN_NONE; +} + int add_module_gl_constants(PyObject *module) { #define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; } GLC(GL_VERSION); @@ -160,21 +329,44 @@ int add_module_gl_constants(PyObject *module) { GLC(GL_RENDERER); GLC(GL_TRIANGLE_FAN); GLC(GL_COLOR_BUFFER_BIT); + GLC(GL_VERTEX_SHADER); + GLC(GL_FRAGMENT_SHADER); + GLC(GL_TRUE); + GLC(GL_FALSE); + GLC(GL_COMPILE_STATUS); + GLC(GL_LINK_STATUS); return 1; } + #define GL_METHODS \ {"enable_automatic_opengl_error_checking", (PyCFunction)enable_automatic_error_checking, METH_O, NULL}, \ {"glewInit", (PyCFunction)_glewInit, METH_NOARGS, NULL}, \ METH(Viewport, METH_VARARGS) \ METH(CheckError, METH_NOARGS) \ METH(ClearColor, METH_VARARGS) \ + METH(GetProgramiv, METH_VARARGS) \ + METH(GetShaderiv, METH_VARARGS) \ METH(Uniform2ui, METH_VARARGS) \ METH(Uniform1i, METH_VARARGS) \ METH(Uniform2f, METH_VARARGS) \ METH(Uniform4f, METH_VARARGS) \ + METH(GetUniformLocation, METH_VARARGS) \ + METH(GetAttribLocation, METH_VARARGS) \ + METH(ShaderSource, METH_VARARGS) \ + METH(CompileShader, METH_O) \ METH(GetString, METH_O) \ METH(Clear, METH_O) \ + METH(CreateShader, METH_O) \ + METH(GenVertexArrays, METH_O) \ + METH(LinkProgram, METH_O) \ + METH(UseProgram, METH_O) \ + METH(BindVertexArray, METH_O) \ + METH(DeleteProgram, METH_O) \ + METH(DeleteShader, METH_O) \ + METH(GetProgramInfoLog, METH_O) \ + METH(GetShaderInfoLog, METH_O) \ METH(DrawArraysInstanced, METH_VARARGS) \ - + METH(CreateProgram, METH_NOARGS) \ + METH(AttachShader, METH_VARARGS) \ diff --git a/kitty/shaders.py b/kitty/shaders.py index e579c50bd..4f15b93ff 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -11,15 +11,17 @@ from OpenGL.GL.ARB.texture_storage import glTexStorage3D # only present in open from .fonts import render_cell from .data_types import ITALIC_MASK, BOLD_MASK +from .fast_data_types import ( + glCreateProgram, glAttachShader, GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, glLinkProgram, + GL_TRUE, GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog, glDeleteShader, glDeleteProgram, + glGenVertexArrays, glCreateShader, glShaderSource, glCompileShader, glGetShaderiv, GL_COMPILE_STATUS, + glGetShaderInfoLog, glGetUniformLocation, glGetAttribLocation, glUseProgram, glBindVertexArray, +) GL_VERSION = (3, 3) VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10 -def array(*args, dtype=gl.GLfloat): - return (dtype * len(args))(*args) - - class Sprites: ''' Maintain sprite sheets of all rendered characters on the GPU as a texture array with each texture being a sprite sheet. ''' @@ -163,24 +165,24 @@ class ShaderProgram: Create a shader program. """ - self.program_id = gl.glCreateProgram() + self.program_id = glCreateProgram() self.is_active = False - vs_id = self.add_shader(vertex, gl.GL_VERTEX_SHADER) - gl.glAttachShader(self.program_id, vs_id) + vs_id = self.add_shader(vertex, GL_VERTEX_SHADER) + glAttachShader(self.program_id, vs_id) - frag_id = self.add_shader(fragment, gl.GL_FRAGMENT_SHADER) - gl.glAttachShader(self.program_id, frag_id) + frag_id = self.add_shader(fragment, GL_FRAGMENT_SHADER) + glAttachShader(self.program_id, frag_id) - gl.glLinkProgram(self.program_id) - if gl.glGetProgramiv(self.program_id, gl.GL_LINK_STATUS) != gl.GL_TRUE: - info = gl.glGetProgramInfoLog(self.program_id) - gl.glDeleteProgram(self.program_id) - gl.glDeleteShader(vs_id) - gl.glDeleteShader(frag_id) + glLinkProgram(self.program_id) + if glGetProgramiv(self.program_id, GL_LINK_STATUS) != GL_TRUE: + info = glGetProgramInfoLog(self.program_id) + glDeleteProgram(self.program_id) + glDeleteShader(vs_id) + glDeleteShader(frag_id) raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8')) - gl.glDeleteShader(vs_id) - gl.glDeleteShader(frag_id) - self.vao_id = gl.glGenVertexArrays(1) + glDeleteShader(vs_id) + glDeleteShader(frag_id) + self.vao_id = glGenVertexArrays(1) def __hash__(self) -> int: return self.program_id @@ -193,35 +195,35 @@ class ShaderProgram: def add_shader(self, source: str, shader_type: int) -> int: ' Compile a shader and return its id, or raise an exception if compilation fails ' - shader_id = gl.glCreateShader(shader_type) + shader_id = glCreateShader(shader_type) source = '#version {}\n{}'.format(VERSION, source) try: - gl.glShaderSource(shader_id, source) - gl.glCompileShader(shader_id) - if gl.glGetShaderiv(shader_id, gl.GL_COMPILE_STATUS) != gl.GL_TRUE: - info = gl.glGetShaderInfoLog(shader_id) + glShaderSource(shader_id, source) + glCompileShader(shader_id) + if glGetShaderiv(shader_id, GL_COMPILE_STATUS) != GL_TRUE: + info = glGetShaderInfoLog(shader_id) raise ValueError('GLSL {} compilation failed: \n{}'.format(shader_type, info.decode('utf-8'))) return shader_id except Exception: - gl.glDeleteShader(shader_id) + glDeleteShader(shader_id) raise - @lru_cache(maxsize=None) + @lru_cache(maxsize=2**6) def uniform_location(self, name: str) -> int: ' Return the id for the uniform variable `name` or -1 if not found. ' - return gl.glGetUniformLocation(self.program_id, name) + return glGetUniformLocation(self.program_id, name) - @lru_cache(maxsize=None) + @lru_cache(maxsize=2**6) def attribute_location(self, name: str) -> int: ' Return the id for the attribute variable `name` or -1 if not found. ' - return gl.glGetAttribLocation(self.program_id, name) + return glGetAttribLocation(self.program_id, name) def __enter__(self): - gl.glUseProgram(self.program_id) - gl.glBindVertexArray(self.vao_id) + glUseProgram(self.program_id) + glBindVertexArray(self.vao_id) self.is_active = True def __exit__(self, *args): - gl.glUseProgram(0) - gl.glBindVertexArray(0) + glUseProgram(0) + glBindVertexArray(0) self.is_active = False