Skip to content

Instantly share code, notes, and snippets.

@bhelyer
Created August 3, 2018 10:47
Show Gist options
  • Select an option

  • Save bhelyer/950258aa8a8297ef2bd5ac0a1b285404 to your computer and use it in GitHub Desktop.

Select an option

Save bhelyer/950258aa8a8297ef2bd5ac0a1b285404 to your computer and use it in GitHub Desktop.
module rr.ui.gl;
import core = core.exception;
import io = watt.io;
import file = watt.io.file;
import conv = watt.conv;
import amp.sdl2;
import amp.gl;
import amp.gl.loader;
import specs = rr.specs;
// This should be called with an active context.
fn initialise() {
gladLoadGL(SDL_GL_GetProcAddress);
glGetError();
loadFragmentShader("simple.f.glsl");
gCoord2d = gShaderProgram.bindAttribute("coord2d");
gTexCoord = gShaderProgram.bindAttribute("texcoord");
gMyTexture = gShaderProgram.bindUniform("mytexture");
vertices: GLfloat[] = [
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
-1.0f, 1.0f,
];
texcoords: GLfloat[] = [
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
];
gVertexBuffer = Buffer.createVBO(vertices);
gTexCoordsBuffer = Buffer.createVBO(texcoords);
gTexture = Texture.create();
glEnable(GL_BLEND);
checkGlError("glEnable");
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
checkGlError("glBlendFunc");
}
fn loadFragmentShader(name: string) {
gFragmentShaderFilename = name;
gShaderProgram.cleanup();
vshader := Shader.create(GL_VERTEX_SHADER, cast(string)file.read("res/simple.v.glsl"));
fpath := new "res/${name}";
fshader := Shader.create(GL_FRAGMENT_SHADER, cast(string)file.read(fpath));
gShaderProgram = Program.create(vshader, fshader);
}
fn nextFragmentShader() {
shaders := fragmentShaders();
assert(shaders.length > 0);
currenti: size_t;
foreach (i, shader; shaders) {
if (shader == gFragmentShaderFilename) {
currenti = i;
}
}
currenti++;
if (currenti >= shaders.length) {
currenti = 0;
}
loadFragmentShader(shaders[currenti]);
}
fn cleanup() {
gTexture.cleanup();
gShaderProgram.cleanup();
gVertexBuffer.cleanup();
}
fn loadPixels(pixels: u8*) {
gShaderProgram.use();
gTexture.bind2D();
gTexture.texImage(pixels);
gVertexBuffer.enable(gCoord2d);
gTexCoordsBuffer.enable(gTexCoord);
glDrawArrays(GL_QUADS, 0, 4);
checkGlError("glDrawArrays");
gTexCoordsBuffer.disable(gTexCoord);
gVertexBuffer.disable(gCoord2d);
}
private:
global gFragmentShaderFilename: string;
global gShaderProgram: Program;
global gCoord2d, gTexCoord: GLint;
global gMyTexture: GLint;
global gTexture: Texture;
global gVertexBuffer: Buffer;
global gTexCoordsBuffer: Buffer;
// Return the filenames (no leading directory information etc -- just the filename).
fn fragmentShaders() string[] {
sourceFiles: string[];
fn addFile(s: string) file.SearchStatus { sourceFiles ~= s; return file.SearchStatus.Continue; }
file.searchDir("res", "*.f.glsl", addFile);
return sourceFiles;
}
struct Texture {
id: GLuint;
global fn create() Texture {
tex: Texture;
glGenTextures(1, &tex.id);
checkGlError("glGenTextures");
glBindTexture(GL_TEXTURE_2D, tex.id);
checkGlError("glBindTexture");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
checkGlError("glTexParameteri");
return tex;
}
fn bind2D() {
glActiveTexture(GL_TEXTURE0);
checkGlError("glActiveTexture");
glUniform1i(gMyTexture, 0);
checkGlError("glUniform1i");
glBindTexture(GL_TEXTURE_2D, id);
checkGlError("glBindTexture");
}
fn texImage(pixels: u8*) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, specs.ScreenWidth, specs.ScreenHeight, 0,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, cast(GLvoid*)pixels);
checkGlError("glTexImage2D");
}
fn cleanup() {
glDeleteTextures(1, &id);
checkGlError("glDeleteTextures");
}
}
struct Shader {
id: GLuint;
global fn create(type: GLenum, source: string) Shader {
s: Shader;
s.id = glCreateShader(type);
checkGlError("glCreateShader");
src := conv.toStringz(source);
glShaderSource(s.id, 1, &src, null);
checkGlError("glShaderSource");
glCompileShader(s.id);
checkGlError("glCompileShader");
status: GLint;
glGetShaderiv(s.id, GL_COMPILE_STATUS, &status);
checkGlError("glGetShaderiv");
infoLog := s.getInfoLog();
if (infoLog !is null) {
io.writeln(infoLog);
}
if (status != GL_TRUE) {
throw new core.Exception("Error compiling shader.");
}
return s;
}
fn cleanup() {
glDeleteShader(id);
checkGlError("glDeleteShader");
}
fn getInfoLog() string {
ilength: GLint;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &ilength);
checkGlError("glGetShaderiv");
if (ilength <= 0) {
return null;
}
length := cast(GLsizei)ilength;
buf := new GLchar[](length);
buflen: GLsizei;
glGetShaderInfoLog(id, length, &buflen, buf.ptr);
checkGlError("glGetShaderInfoLog");
return cast(string)buf[0 .. buflen];
}
}
struct Program {
id: GLuint;
shaders: Shader[];
global fn create(shaders: Shader[]...) Program {
p: Program;
p.id = glCreateProgram();
checkGlError("glCreateProgram");
p.shaders = shaders;
foreach (s; shaders) {
glAttachShader(p.id, s.id);
checkGlError("glAttachShader");
}
glLinkProgram(p.id);
checkGlError("glLinkProgram");
linkOk: GLint;
glGetProgramiv(p.id, GL_LINK_STATUS, &linkOk);
checkGlError("glGetProgramiv");
if (linkOk != GL_TRUE) {
throw new core.Exception("Error linking program.");
}
return p;
}
fn cleanup() {
foreach (shader; shaders) {
glDetachShader(id, shader.id);
checkGlError("glDetachShader");
shader.cleanup();
}
glUseProgram(0);
checkGlError("glUseProgram");
glDeleteProgram(id);
checkGlError("glDeleteProgram");
}
fn bindAttribute(name: string) GLint {
val := glGetAttribLocation(id, conv.toStringz(name));
checkGlError("glGetAttribLocation");
if (val == -1) {
throw new core.Exception(new "could not bind attribute '${name}'");
}
return val;
}
fn bindUniform(name: string) GLint {
val := glGetUniformLocation(id, conv.toStringz(name));
checkGlError("glGetUniformLocation");
if (val == -1) {
throw new core.Exception(new "could not bind uniform '${name}'");
}
return val;
}
fn use() {
glUseProgram(id);
checkGlError("glUseProgram");
}
}
struct Buffer {
id: GLuint;
global fn createVBO(vertices: GLfloat[]) Buffer {
b: Buffer;
glGenBuffers(1, &b.id);
checkGlError("glGenBuffers");
b.bind();
b.bufferData(vertices);
return b;
}
fn cleanup() {
glDeleteBuffers(1, &id);
checkGlError("glDeleteBuffers");
}
fn bind() {
glBindBuffer(GL_ARRAY_BUFFER, id);
checkGlError("glBindBuffer");
}
fn enable(attr: GLint) {
glEnableVertexAttribArray(cast(GLuint)attr);
checkGlError("glEnableVertexAttribArray");
bind();
glVertexAttribPointer(cast(GLuint)attr, 2, GL_FLOAT, GL_FALSE, 0, null);
checkGlError("glVertexAttribPointer");
}
fn disable(attr: GLint) {
glDisableVertexAttribArray(cast(GLuint)attr);
checkGlError("glDisableVertexAttribArray");
}
fn bufferData(vertices: GLfloat[]) {
glBufferData(GL_ARRAY_BUFFER, cast(GLsizeiptr)(vertices.length * typeid(GLfloat).size),
cast(void*)vertices.ptr, GL_STATIC_DRAW);
checkGlError("glBufferData");
}
}
fn checkGlError(functionName: string = __FUNCTION__, location: string = __LOCATION__) {
err := glGetError();
if (err != GL_NO_ERROR) {
throw new core.Exception(new "${functionName}@${location} glError: ${errorToString(err)}");
}
}
fn errorToString(err: GLenum) string {
switch (err) {
case GL_NO_ERROR: return "GL_NO_ERROR";
case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION";
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
default: return "unknown OpenGL error";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment