diff --git a/kitty/develop_gl.py b/kitty/develop_gl.py index 181c40f4f..188891474 100644 --- a/kitty/develop_gl.py +++ b/kitty/develop_gl.py @@ -7,9 +7,8 @@ import OpenGL.GL as gl import sys from PIL import Image import numpy -import ctypes -from kitty.shaders import ShaderProgram, VertexArrayObject +from kitty.shaders import ShaderProgram, array vertex_shader = """ # version 410 @@ -78,62 +77,69 @@ def _main(): try: gl.glClearColor(0.5, 0.5, 0.5, 0) - triangle_texture(window) + rectangle_texture(window) finally: glfw.glfwDestroyWindow(window) +def triangle_vertices(width=0.8, height=0.8): + return array( + 0.0, height, + -width, -height, + width, -height, + ) + + +def rectangle_vertices(left=-0.8, top=0.8, right=0.8, bottom=-0.8): + vertex_data = array( + right, top, + right, bottom, + left, bottom, + right, top, + left, bottom, + left, top + ) + + texture_coords = array( + 1, 0, # right top + 1, 1, # right bottom + 0, 1, # left bottom + 1, 0, # right top + 0, 1, # left bottom + 0, 0) # left top + return vertex_data, texture_coords + + 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) + with program: + program.set_attribute_data('vertex', rectangle_vertices()[0]) 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) + with program: + gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) - # 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) + with program: + program.set_attribute_data('vertex', triangle_vertices()) while not glfw.glfwWindowShouldClose(window): gl.glClear(gl.GL_COLOR_BUFFER_BIT) - with program, vao: + with program: gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) glfw.glfwSwapBuffers(window) glfw.glfwWaitEvents() - -def triangle_texture(window): - program = ShaderProgram( - ''' +textured_shaders = ( + ''' #version 410 in vec2 vertex; in vec2 texture_position; @@ -145,7 +151,7 @@ void main() { } ''', - ''' + ''' #version 410 uniform sampler2D tex; in vec2 texture_position_out; @@ -153,34 +159,55 @@ out vec4 final_color; void main() { final_color = texture(tex, texture_position_out); - // final_color = vec4(texture_position_out, 0, 1.0); } ''') - vao = VertexArrayObject() + +def texture_data(): 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))) + return img_data, img.size[0], img.size[1] + + +def triangle_texture(window): + program = ShaderProgram(*textured_shaders) + img_data, w, h = texture_data() + with program: + program.set_2d_texture('tex', img_data, w, h) + program.set_attribute_data('vertex', triangle_vertices()) + program.set_attribute_data('texture_position', array( + 0.5, 1.0, + 0.0, 0.0, + 1.0, 0.0 + )) while not glfw.glfwWindowShouldClose(window): gl.glClear(gl.GL_COLOR_BUFFER_BIT) - with program, vao: + with program: gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) glfw.glfwSwapBuffers(window) glfw.glfwWaitEvents() +def rectangle_texture(window): + program = ShaderProgram(*textured_shaders) + img_data, w, h = texture_data() + with program: + program.set_2d_texture('tex', img_data, w, h) + rv, texc = rectangle_vertices() + program.set_attribute_data('vertex', rv) + program.set_attribute_data('texture_position', texc) + + while not glfw.glfwWindowShouldClose(window): + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + with program: + gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) + + glfw.glfwSwapBuffers(window) + glfw.glfwWaitEvents() + + def on_error(code, msg): if isinstance(msg, bytes): try: diff --git a/kitty/shaders.py b/kitty/shaders.py index 872ce7865..8d395f99d 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -21,6 +21,7 @@ class ShaderProgram: """ self.program_id = gl.glCreateProgram() self.texture_id = None + self.is_active = False vs_id = self.add_shader(vertex, gl.GL_VERTEX_SHADER) frag_id = self.add_shader(fragment, gl.GL_FRAGMENT_SHADER) @@ -36,6 +37,8 @@ class ShaderProgram: raise ValueError('Error linking shader program: %s' % info) gl.glDeleteShader(vs_id) gl.glDeleteShader(frag_id) + self.vao_id = gl.glGenVertexArrays(1) + self.attribute_buffers = {} def __hash__(self) -> int: return self.program_id @@ -72,13 +75,17 @@ class ShaderProgram: def __enter__(self): gl.glUseProgram(self.program_id) + gl.glBindVertexArray(self.vao_id) + self.is_active = True 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.glBindVertexArray(0) gl.glUseProgram(0) + self.is_active = False def set_2d_texture(self, var_name, data, width, height, data_type='rgba', min_filter=gl.GL_LINEAR, mag_filter=gl.GL_LINEAR, @@ -100,62 +107,25 @@ class ShaderProgram: 0, external_format, gl.GL_UNSIGNED_BYTE, data) return texture_id + def set_attribute_data(self, attribute_name, data, items_per_attribute_value=2, volatile=False, normalize=False): + if not self.is_active: + raise RuntimeError('The program must be active before you can add buffers') + if len(data) % items_per_attribute_value != 0: + raise ValueError('The length of the data buffer is not a multiple of the items_per_attribute_value') + buf_id = self.attribute_buffers[attribute_name] = gl.glGenBuffers(1) + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buf_id) + gl.glBufferData(gl.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(data), data, gl.GL_STREAM_DRAW if volatile else gl.GL_STATIC_DRAW) + loc = self.attribute_location(attribute_name) + gl.glEnableVertexAttribArray(loc) + typ = { + gl.GLfloat: gl.GL_FLOAT, + gl.GLubyte: gl.GL_UNSIGNED_BYTE, + gl.GLuint: gl.GL_UNSIGNED_INT, + }[data._type_] + gl.glVertexAttribPointer( + loc, items_per_attribute_value, typ, gl.GL_TRUE if normalize else gl.GL_FALSE, 0, None + ) + 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)