diff --git a/kitty/develop_gl.py b/kitty/develop_gl.py new file mode 100644 index 000000000..181c40f4f --- /dev/null +++ b/kitty/develop_gl.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2016, Kovid Goyal + +import glfw +import OpenGL.GL as gl +import sys +from PIL import Image +import numpy +import ctypes + +from kitty.shaders import ShaderProgram, VertexArrayObject + +vertex_shader = """ +# version 410 +in vec2 vertex; + +void main() { + gl_Position = vec4(vertex, 0, 1); +} +""" + + +fragment = """ +# version 410 +out vec4 final_color; + +void main(void) +{ + final_color = vec4(1.0); +} +""" + + +def key_callback(key, action): + """ Sample keyboard callback function """ + print('Key: %s Action: %s pressed' % (key, action)) + + +def gl_get_unicode(k): + ans = gl.glGetString(k) + if isinstance(ans, bytes): + try: + ans = ans.decode('utf-8') + except Exception: + ans = repr(ans) + return ans + + +def on_resize(window, w, h): + gl.glViewport(0, 0, w, h) + + +def _main(): + # These Window hints are used to specify + # which opengl version to use and other details + # for the opengl context that will be created + glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MAJOR, 4) + glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MINOR, 1) + glfw.glfwWindowHint(glfw.GLFW_OPENGL_PROFILE, + glfw.GLFW_OPENGL_CORE_PROFILE) + glfw.glfwWindowHint(glfw.GLFW_OPENGL_FORWARD_COMPAT, True) + + window = glfw.glfwCreateWindow( + 1024, 1024, "Trying this crap".encode('utf-8'), None, None) + if not window: + raise SystemExit("glfwCreateWindow failed") + glfw.glfwMakeContextCurrent(window) + glfw.glfwSwapInterval(1) + glfw.glfwSetFramebufferSizeCallback(window, on_resize) + + # If everything went well the following calls + # will display the version of opengl being used + print('Vendor: %s' % (gl_get_unicode(gl.GL_VENDOR))) + print('Opengl version: %s' % (gl_get_unicode(gl.GL_VERSION))) + print('GLSL Version: %s' % (gl_get_unicode(gl.GL_SHADING_LANGUAGE_VERSION))) + print('Renderer: %s' % (gl_get_unicode(gl.GL_RENDERER))) + + try: + gl.glClearColor(0.5, 0.5, 0.5, 0) + triangle_texture(window) + finally: + glfw.glfwDestroyWindow(window) + + +def rectangle(window): + program = ShaderProgram(vertex_shader, fragment) + vao = VertexArrayObject() + + with program, vao: + vao.make_rectangle() + vertex = program.attribute_location('vertex') + gl.glEnableVertexAttribArray(vertex) + gl.glVertexAttribPointer( + vertex, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, None) + + while not glfw.glfwWindowShouldClose(window): + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + with program, vao: + # Activate the texture + # glActiveTexture(GL_TEXTURE0) + # glBindTexture(GL_TEXTURE_2D, texture_id) + # sampler_loc = program.attribute_location('texture_sampler') + # glUniform1i(sampler_loc, 0) + + # Modern GL makes the draw call really simple + # All the complexity has been pushed elsewhere + gl.glDrawElements(gl.GL_TRIANGLE_STRIP, 4, gl.GL_UNSIGNED_BYTE, None) + + # Now lets show our master piece on the screen + glfw.glfwSwapBuffers(window) + glfw.glfwWaitEvents() + + +def triangle(window): + program = ShaderProgram(vertex_shader, fragment) + vao = VertexArrayObject() + with program, vao: + vao.make_triangle() + vertex = program.attribute_location('vertex') + gl.glEnableVertexAttribArray(vertex) + gl.glVertexAttribPointer( + vertex, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, None) + + while not glfw.glfwWindowShouldClose(window): + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + with program, vao: + gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) + + glfw.glfwSwapBuffers(window) + glfw.glfwWaitEvents() + + +def triangle_texture(window): + program = ShaderProgram( + ''' +#version 410 +in vec2 vertex; +in vec2 texture_position; +out vec2 texture_position_out; + +void main() { + gl_Position = vec4(vertex, 0, 1); + texture_position_out = texture_position; +} + ''', + + ''' +#version 410 +uniform sampler2D tex; +in vec2 texture_position_out; +out vec4 final_color; + +void main() { + final_color = texture(tex, texture_position_out); + // final_color = vec4(texture_position_out, 0, 1.0); +} + ''') + + vao = VertexArrayObject() + img = Image.open('/home/kovid/work/calibre/resources/images/library.png') + img_data = numpy.array(list(img.getdata()), numpy.int8) + with program, vao: + program.set_2d_texture('tex', img_data, img.size[0], img.size[1]) + vao.make_triangle(add_texture=True) + vertex = program.attribute_location('vertex') + gl.glEnableVertexAttribArray(vertex) + gl.glVertexAttribPointer( + vertex, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, None) + texture_position = program.attribute_location('texture_position') + gl.glEnableVertexAttribArray(texture_position) + gl.glVertexAttribPointer( + texture_position, 2, gl.GL_FLOAT, gl.GL_TRUE, 0, ctypes.c_void_p(6 * gl.sizeof(gl.GLfloat))) + + while not glfw.glfwWindowShouldClose(window): + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + with program, vao: + gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) + + glfw.glfwSwapBuffers(window) + glfw.glfwWaitEvents() + + +def on_error(code, msg): + if isinstance(msg, bytes): + try: + msg = msg.decode('utf-8') + except Exception: + msg = repr(msg) + print(msg, file=sys.stderr) + + +def main(): + if not glfw.glfwInit(): + raise SystemExit('GLFW initialization failed') + glfw.glfwSetErrorCallback(on_error) + try: + _main() + finally: + glfw.glfwTerminate() diff --git a/kitty/shaders.py b/kitty/shaders.py index 886d15f76..872ce7865 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -4,13 +4,8 @@ from functools import lru_cache -from OpenGL.GL import ( - glCreateProgram, GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, glAttachShader, - glLinkProgram, glGetProgramiv, glGetProgramInfoLog, GL_LINK_STATUS, - GL_TRUE, glDeleteProgram, glDeleteShader, glCreateShader, glCompileShader, - glGetShaderiv, GL_COMPILE_STATUS, glShaderSource, glGetShaderInfoLog, - glGetUniformLocation, glGetAttribLocation -) +from OpenGL.arrays import ArrayDatatype +import OpenGL.GL as gl class ShaderProgram: @@ -24,22 +19,23 @@ class ShaderProgram: :param fragment: The fragment shader """ - self.program_id = glCreateProgram() - vs_id = self.add_shader(vertex, GL_VERTEX_SHADER) - frag_id = self.add_shader(fragment, GL_FRAGMENT_SHADER) + self.program_id = gl.glCreateProgram() + self.texture_id = None + vs_id = self.add_shader(vertex, gl.GL_VERTEX_SHADER) + frag_id = self.add_shader(fragment, gl.GL_FRAGMENT_SHADER) - glAttachShader(self.program_id, vs_id) - glAttachShader(self.program_id, frag_id) - glLinkProgram(self.program_id) + gl.glAttachShader(self.program_id, vs_id) + gl.glAttachShader(self.program_id, frag_id) + gl.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) + 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) raise ValueError('Error linking shader program: %s' % info) - glDeleteShader(vs_id) - glDeleteShader(frag_id) + gl.glDeleteShader(vs_id) + gl.glDeleteShader(frag_id) def __hash__(self) -> int: return self.program_id @@ -52,24 +48,114 @@ 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 = glCreateShader(shader_type) + shader_id = gl.glCreateShader(shader_type) try: - glShaderSource(shader_id, source) - glCompileShader(shader_id) - if glGetShaderiv(shader_id, GL_COMPILE_STATUS) != GL_TRUE: - info = glGetShaderInfoLog(shader_id) + 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) raise ValueError('GLSL Shader compilation failed: %s' % info) return shader_id except Exception: - glDeleteShader(shader_id) + gl.glDeleteShader(shader_id) raise - @lru_cache(maxlen=None) + @lru_cache(maxsize=None) def uniform_location(self, name: str) -> int: ' Return the id for the uniform variable `name` or -1 if not found. ' - return glGetUniformLocation(self.program_id, name) + return gl.glGetUniformLocation(self.program_id, name) - @lru_cache(maxlen=None) + @lru_cache(maxsize=None) def attribute_location(self, name: str) -> int: ' Return the id for the attribute variable `name` or -1 if not found. ' - return glGetAttribLocation(self.program_id, name) + return gl.glGetAttribLocation(self.program_id, name) + + def __enter__(self): + gl.glUseProgram(self.program_id) + if self.texture_id is not None: + gl.glActiveTexture(gl.GL_TEXTURE0) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_id) + gl.glUniform1i(self.texture_var, 0) # 0 because using GL_TEXTURE0 + + def __exit__(self, *args): + gl.glUseProgram(0) + + def set_2d_texture(self, var_name, data, width, height, data_type='rgba', + min_filter=gl.GL_LINEAR, mag_filter=gl.GL_LINEAR, + swrap=gl.GL_CLAMP_TO_EDGE, twrap=gl.GL_CLAMP_TO_EDGE): + texture_id = self.texture_id = gl.glGenTextures(1) + self.texture_var = self.uniform_location(var_name) + gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1 if data_type == 'red' else 4) + gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, min_filter) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, mag_filter) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, swrap) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, twrap) + internal_format, external_format = { + 'rgba': (gl.GL_RGBA8, gl.GL_RGBA), + 'rgb': (gl.GL_RGB8, gl.GL_RGB), + 'red': (gl.GL_RED, gl.GL_RED), + }[data_type] + gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, internal_format, width, height, + 0, external_format, gl.GL_UNSIGNED_BYTE, data) + return texture_id + + +def array(*args, dtype=gl.GLfloat): + return (dtype * len(args))(*args) + + +def make_buffer(data, target=gl.GL_ARRAY_BUFFER, usage=gl.GL_STREAM_DRAW): + buf_id = gl.glGenBuffers(1) + gl.glBindBuffer(target, buf_id) + gl.glBufferData(target, ArrayDatatype.arrayByteCount(data), data, usage) + return buf_id + + +class VertexArrayObject: + + def __init__(self): + self.vao_id = gl.glGenVertexArrays(1) + self.is_active = False + self.texture_id = None + + def __enter__(self): + gl.glBindVertexArray(self.vao_id) + self.is_active = True + + def __exit__(self, *a): + gl.glBindVertexArray(0) + self.is_active = False + + def make_triangle(self, width=0.8, height=0.8, add_texture=False, usage=gl.GL_STATIC_DRAW): + if not self.is_active: + raise RuntimeError('This VertexArrayObject is not active') + if add_texture: + vertices = array( + 0.0, height, + -width, -height, + width, -height, + 0.5, 1.0, + 0.0, 0.0, + 1.0, 0.0 + ) + else: + vertices = array( + 0.0, height, + -width, -height, + width, -height, + ) + make_buffer(vertices, usage=usage) + + def make_rectangle(self, left=-0.8, top=0.8, right=0.8, bottom=-0.8, usage=gl.GL_STATIC_DRAW): + if not self.is_active: + raise RuntimeError('This VertexArrayObject is not active') + vertices = array( + left, bottom, + right, bottom, + left, top, + right, top + ) + make_buffer(vertices, usage=usage) + elements = array(0, 1, 2, 3, dtype=gl.GLubyte) + make_buffer(elements, gl.GL_ELEMENT_ARRAY_BUFFER, usage=usage)