module gl; import glad.gl.all; import core.stdc.stdio; bool checkShaderError(GLuint shader, GLuint flag, bool is_program, in char[] shader_path) nothrow { GLint result; (is_program) ? glGetProgramiv(shader, flag, &result) : glGetShaderiv(shader, flag, &result); if (result == GL_FALSE) { GLchar[256] log; //FIXME this is potentially fatal (is_program) ? glGetProgramInfoLog(shader, log.sizeof, null, log.ptr) : glGetShaderInfoLog(shader, log.sizeof, null, log.ptr); printf("[OpenGL] Error in %s: %s\n", shader_path.ptr, log.ptr); return false; } return true; } // checkShaderError struct PixelBuffer { struct Shader { private { GLuint shader_prog; } @disable this(this); void compile(const (GLchar*)* vs_source, const (GLchar*)* fs_source) { import core.stdc.stdlib : exit; GLuint new_vs_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(new_vs_shader, 1, vs_source, null); glCompileShader(new_vs_shader); if (!checkShaderError(new_vs_shader, GL_COMPILE_STATUS, false, "vertex_shader")) { exit(-1); } GLuint new_fs_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(new_fs_shader, 1, fs_source, null); glCompileShader(new_fs_shader); if (!checkShaderError(new_fs_shader, GL_COMPILE_STATUS, false, "fragment_shader")) { exit(-1); } GLuint new_shader = glCreateProgram(); glAttachShader(new_shader, new_vs_shader); glAttachShader(new_shader, new_fs_shader); // glBindAttribLocation(new_shader, 0, "screen_size"); glLinkProgram(new_shader); if (!checkShaderError(new_shader, GL_LINK_STATUS, true, "shader_program")) { exit(-1); } glValidateProgram(new_shader); if (!checkShaderError(new_shader, GL_VALIDATE_STATUS, true, "shader_program")) { exit(-1); } shader_prog = new_shader; } // compile void setUniforms(int w, int h) { auto attr_loc = glGetUniformLocation(shader_prog, "screen_size"); glUniform2f(attr_loc, cast(float)w, cast(float)h); } // setUniforms void bind() { glUseProgram(shader_prog); } // bind void unbind() { glUseProgram(0); } // unbind } // Shader struct Texture { import derelict.sdl2.sdl; import derelict.sdl2.image; private { GLuint texture_; //OpenGL handle for texture GLenum input_format_, output_format_, data_type_; int width_, height_; } @property @nogc nothrow { int width() const { return width_; } int height() const { return height_; } GLuint handle() { return texture_; } } @disable this(this); nothrow @nogc this(int width, int height, GLenum input_format, GLenum output_format, GLenum unpack_alignment) { width_ = width; height_ = height; input_format_ = input_format; output_format_ = output_format; data_type_ = GL_UNSIGNED_BYTE; glGenTextures(1, &texture_); glBindTexture(GL_TEXTURE_2D, texture_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment); glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, GL_UNSIGNED_BYTE, cast(void*)0); glBindTexture(GL_TEXTURE_2D, 0); } //this nothrow @nogc void create(void* pixels, int width, int height, GLenum input_format = GL_RGB, GLenum output_format = GL_RGB, GLenum data_type = GL_UNSIGNED_BYTE) { width_ = width; height_ = height; input_format_ = input_format; output_format_ = output_format; data_type_ = data_type; //generate single texture, put handle in texture glGenTextures(1, &texture_); //normal 2d texture, bind to our texture handle glBindTexture(GL_TEXTURE_2D, texture_); //set texture parameters in currently bound texture, controls texture wrapping (or GL_CLAMP?) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); //linearly interpolate between pixels, MIN if texture is too small for drawing area, MAG if drawing area is smaller than texture glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //texture type, level, format to store as, width, height, border, format loaded in glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, data_type_, pixels); //UNBIND glBindTexture(GL_TEXTURE_2D, 0); } // this ~this() nothrow @nogc { if (texture_ != 0) { glDeleteTextures(1, &texture_); } } // ~this /** * Binds the texture handle, takes an argument for which texture unit to use. */ nothrow @nogc void bind(int unit) { assert(unit >= 0 && unit <= 31); glActiveTexture(GL_TEXTURE0 + unit); //since this is sequential, this works glBindTexture(GL_TEXTURE_2D, texture_); } // bind nothrow @nogc void unbind() { glBindTexture(GL_TEXTURE_2D, 0); } // unbind /** * Updates the texture in place given the new texture buffer. * Takes an optional offset to update only a part of the texture. **/ nothrow @nogc void update(void[] pixels, size_t offset = 0) { bind(0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, input_format_, data_type_, pixels.ptr); unbind(); } // update } // Texture /** * Generic VertexArray structure, used to upload data of any given vertex type to the GPU. */ struct VertexArray { private { GLuint vao_; GLuint vbo_; GLenum type_; // type of vertex data, GL_TRIANGLES etc uint num_vertices_; } @disable this(this); nothrow @nogc void create(in float[2][] vertices, GLenum draw_type = GL_STATIC_DRAW, GLenum primitive = GL_TRIANGLES) { this.num_vertices_ = cast(uint)vertices.length; this.type_ = primitive; glGenVertexArrays(1, &vao_); glBindVertexArray(vao_); glGenBuffers(1, &vbo_); glBindBuffer(GL_ARRAY_BUFFER, vbo_); glBufferData(GL_ARRAY_BUFFER, vertices.length * vertices[0].sizeof, vertices.ptr, draw_type); // pos glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, float.sizeof * 2, null ); glBindVertexArray(0); } // this ~this() nothrow @nogc { if (vao_ != 0) { glDeleteVertexArrays(1, &vao_); } } // ~this void bind() nothrow @nogc { glBindVertexArray(vao_); } // bind void draw() nothrow @nogc { glDrawArrays(type_, 0, num_vertices_); } // draw void unbind() nothrow @nogc { glBindVertexArray(0); } // unbind } // VertexArray const char* vs_shader = q{ #version 330 core uniform vec2 screen_size; layout(location = 0) in vec2 pos; out vec2 frag_uv; void main() { gl_Position = vec4(pos, 0.0, 1.0); frag_uv = clamp(pos, vec2(0.0, 0.0), vec2(1.0, 1.0)); } }; const char* fs_shader = q{ #version 330 core uniform sampler2D tex; in vec2 frag_uv; out vec4 out_col; void main() { float v = texture(tex, frag_uv.st).r * 255; out_col = vec4(v, v, v, 1.0); } }; Shader shader; VertexArray vao; Texture tex; void create(void* pixels, int w, int h) { float[2][6] rect = [ [-1.0f, -1.0f], // top left [1.0f, -1.0f], // top right [1.0f, 1.0f], // bottom right [-1.0f, -1.0f], // top left [-1.0f, 1.0f], // bottom left [1.0f, 1.0f], // bottom right ]; vao.create(rect[]); tex.create(pixels, w, h, GL_RED, GL_RED); shader.compile(&vs_shader, &fs_shader); } // create void draw(int screen_w, int screen_h) { vao.bind(); shader.bind(); shader.setUniforms(screen_w, screen_h); tex.bind(0); vao.draw(); } // draw nothrow @nogc void update(void[] pixels, size_t offset = 0) { tex.update(pixels, offset); } // update } // PixelBuffer