Refactor the shaders module to present a nicer interface

This commit is contained in:
Kovid Goyal 2016-10-25 18:08:38 +05:30
parent f7db790061
commit 135db0d834
2 changed files with 101 additions and 104 deletions

View File

@ -7,9 +7,8 @@ import OpenGL.GL as gl
import sys import sys
from PIL import Image from PIL import Image
import numpy import numpy
import ctypes
from kitty.shaders import ShaderProgram, VertexArrayObject from kitty.shaders import ShaderProgram, array
vertex_shader = """ vertex_shader = """
# version 410 # version 410
@ -78,62 +77,69 @@ def _main():
try: try:
gl.glClearColor(0.5, 0.5, 0.5, 0) gl.glClearColor(0.5, 0.5, 0.5, 0)
triangle_texture(window) rectangle_texture(window)
finally: finally:
glfw.glfwDestroyWindow(window) 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): def rectangle(window):
program = ShaderProgram(vertex_shader, fragment) program = ShaderProgram(vertex_shader, fragment)
vao = VertexArrayObject()
with program, vao: with program:
vao.make_rectangle() program.set_attribute_data('vertex', rectangle_vertices()[0])
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): while not glfw.glfwWindowShouldClose(window):
gl.glClear(gl.GL_COLOR_BUFFER_BIT) gl.glClear(gl.GL_COLOR_BUFFER_BIT)
with program, vao: with program:
# Activate the texture gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
# 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.glfwSwapBuffers(window)
glfw.glfwWaitEvents() glfw.glfwWaitEvents()
def triangle(window): def triangle(window):
program = ShaderProgram(vertex_shader, fragment) program = ShaderProgram(vertex_shader, fragment)
vao = VertexArrayObject() with program:
with program, vao: program.set_attribute_data('vertex', triangle_vertices())
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): while not glfw.glfwWindowShouldClose(window):
gl.glClear(gl.GL_COLOR_BUFFER_BIT) gl.glClear(gl.GL_COLOR_BUFFER_BIT)
with program, vao: with program:
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
glfw.glfwSwapBuffers(window) glfw.glfwSwapBuffers(window)
glfw.glfwWaitEvents() glfw.glfwWaitEvents()
textured_shaders = (
def triangle_texture(window): '''
program = ShaderProgram(
'''
#version 410 #version 410
in vec2 vertex; in vec2 vertex;
in vec2 texture_position; in vec2 texture_position;
@ -145,7 +151,7 @@ void main() {
} }
''', ''',
''' '''
#version 410 #version 410
uniform sampler2D tex; uniform sampler2D tex;
in vec2 texture_position_out; in vec2 texture_position_out;
@ -153,34 +159,55 @@ out vec4 final_color;
void main() { void main() {
final_color = texture(tex, texture_position_out); 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 = Image.open('/home/kovid/work/calibre/resources/images/library.png')
img_data = numpy.array(list(img.getdata()), numpy.int8) img_data = numpy.array(list(img.getdata()), numpy.int8)
with program, vao: return img_data, img.size[0], img.size[1]
program.set_2d_texture('tex', img_data, img.size[0], img.size[1])
vao.make_triangle(add_texture=True)
vertex = program.attribute_location('vertex') def triangle_texture(window):
gl.glEnableVertexAttribArray(vertex) program = ShaderProgram(*textured_shaders)
gl.glVertexAttribPointer( img_data, w, h = texture_data()
vertex, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, None) with program:
texture_position = program.attribute_location('texture_position') program.set_2d_texture('tex', img_data, w, h)
gl.glEnableVertexAttribArray(texture_position) program.set_attribute_data('vertex', triangle_vertices())
gl.glVertexAttribPointer( program.set_attribute_data('texture_position', array(
texture_position, 2, gl.GL_FLOAT, gl.GL_TRUE, 0, ctypes.c_void_p(6 * gl.sizeof(gl.GLfloat))) 0.5, 1.0,
0.0, 0.0,
1.0, 0.0
))
while not glfw.glfwWindowShouldClose(window): while not glfw.glfwWindowShouldClose(window):
gl.glClear(gl.GL_COLOR_BUFFER_BIT) gl.glClear(gl.GL_COLOR_BUFFER_BIT)
with program, vao: with program:
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
glfw.glfwSwapBuffers(window) glfw.glfwSwapBuffers(window)
glfw.glfwWaitEvents() 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): def on_error(code, msg):
if isinstance(msg, bytes): if isinstance(msg, bytes):
try: try:

View File

@ -21,6 +21,7 @@ class ShaderProgram:
""" """
self.program_id = gl.glCreateProgram() self.program_id = gl.glCreateProgram()
self.texture_id = None self.texture_id = None
self.is_active = False
vs_id = self.add_shader(vertex, gl.GL_VERTEX_SHADER) vs_id = self.add_shader(vertex, gl.GL_VERTEX_SHADER)
frag_id = self.add_shader(fragment, gl.GL_FRAGMENT_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) raise ValueError('Error linking shader program: %s' % info)
gl.glDeleteShader(vs_id) gl.glDeleteShader(vs_id)
gl.glDeleteShader(frag_id) gl.glDeleteShader(frag_id)
self.vao_id = gl.glGenVertexArrays(1)
self.attribute_buffers = {}
def __hash__(self) -> int: def __hash__(self) -> int:
return self.program_id return self.program_id
@ -72,13 +75,17 @@ class ShaderProgram:
def __enter__(self): def __enter__(self):
gl.glUseProgram(self.program_id) gl.glUseProgram(self.program_id)
gl.glBindVertexArray(self.vao_id)
self.is_active = True
if self.texture_id is not None: if self.texture_id is not None:
gl.glActiveTexture(gl.GL_TEXTURE0) gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_id) gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_id)
gl.glUniform1i(self.texture_var, 0) # 0 because using GL_TEXTURE0 gl.glUniform1i(self.texture_var, 0) # 0 because using GL_TEXTURE0
def __exit__(self, *args): def __exit__(self, *args):
gl.glBindVertexArray(0)
gl.glUseProgram(0) gl.glUseProgram(0)
self.is_active = False
def set_2d_texture(self, var_name, data, width, height, data_type='rgba', def set_2d_texture(self, var_name, data, width, height, data_type='rgba',
min_filter=gl.GL_LINEAR, mag_filter=gl.GL_LINEAR, min_filter=gl.GL_LINEAR, mag_filter=gl.GL_LINEAR,
@ -100,62 +107,25 @@ class ShaderProgram:
0, external_format, gl.GL_UNSIGNED_BYTE, data) 0, external_format, gl.GL_UNSIGNED_BYTE, data)
return texture_id 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): def array(*args, dtype=gl.GLfloat):
return (dtype * len(args))(*args) 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)