Code to work on OpenGL

This commit is contained in:
Kovid Goyal
2016-10-25 16:22:53 +05:30
parent ca57eb9d05
commit f7db790061
2 changed files with 316 additions and 30 deletions

200
kitty/develop_gl.py Normal file
View File

@@ -0,0 +1,200 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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()

View File

@@ -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)