1 module dgt.gl_backend; 2 import derelict.opengl; 3 import derelict.sdl2.sdl; 4 5 import dgt.array : Array; 6 import dgt.color : Color; 7 import dgt.geom; 8 import dgt.io; 9 10 ///The default vertex shader 11 string DEFAULT_VERTEX_SHADER = "#version 150 12 in vec2 position; 13 in vec2 tex_coord; 14 in vec4 color; 15 uniform mat3 transform; 16 out vec4 Color; 17 out vec2 Tex_coord; 18 void main() { 19 Color = color; 20 Tex_coord = tex_coord; 21 vec3 transformed = vec3(position, 1.0) * transform; 22 transformed.z = 0; 23 gl_Position = vec4(transformed, 1.0); 24 }"; 25 ///The default fragment shader 26 string DEFAULT_FRAGMENT_SHADER = "#version 150 27 in vec4 Color; 28 in vec2 Tex_coord; 29 out vec4 outColor; 30 uniform sampler2D tex; 31 void main() { 32 vec4 tex_color = texture(tex, Tex_coord); 33 outColor = Color * tex_color; 34 }"; 35 36 ///Represents a vertex to pass to OpenGL 37 struct Vertex 38 { 39 Vector pos, texPos; 40 Color col; 41 42 @nogc nothrow: 43 44 void print() const 45 { 46 dgt.io.print("Vertex(", pos, ", ", texPos, ", ", col, ")"); 47 } 48 } 49 50 /** 51 Handles opengl contexts and passing data to OpenGL such as shaders and vertices 52 */ 53 struct GLBackend 54 { 55 //The draw data 56 private GLuint texture = 0; 57 public Array!float vertices; 58 private Array!GLuint indices; 59 60 private SDL_GLContext ctx; 61 //OpenGL objects 62 private GLuint shader, fragment, vertex, vbo, ebo, vao, texture_location; 63 private string transformAttribute, 64 positionAttribute, 65 texPositionAttribute, 66 colorAttribute, textureAttribute; 67 private SDL_Window* window; 68 private Transform transform; 69 70 //The amount of floats per vertex 71 private static immutable size_t vertex_size = 8; 72 73 @disable this(); 74 @disable this(this); 75 76 @trusted: 77 package this(SDL_Window* window, bool vsync) 78 { 79 DerelictGL3.load(); 80 this.window = window; 81 SDL_GL_SetSwapInterval(vsync ? 1 : 0); 82 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 83 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); 84 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 85 ctx = SDL_GL_CreateContext(window); 86 DerelictGL3.reload(); 87 DerelictGL3.loadExtra(); 88 glGenVertexArrays(1, &vao); 89 glBindVertexArray(vao); 90 glGenBuffers(1, &vbo); 91 glGenBuffers(1, &ebo); 92 setShader(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER); 93 glEnable (GL_BLEND); 94 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 95 vertices = Array!float(1024); 96 indices = Array!GLuint(1024); 97 } 98 99 @nogc nothrow: 100 101 ~this() 102 { 103 vertices.destroy(); 104 indices.destroy(); 105 glDeleteProgram(shader); 106 glDeleteShader(fragment); 107 glDeleteShader(vertex); 108 109 glDeleteBuffers(1, &vbo); 110 glDeleteBuffers(1, &ebo); 111 112 glDeleteVertexArrays(1, &vao); 113 SDL_GL_DeleteContext(ctx); 114 } 115 116 /** 117 Set the current shader and its attributes 118 119 The GL backend passes a uniform 3x3 matrix to a uniform with a name 120 given by 'transformAttributeName.' This matrix is a transformation 121 applied to every vertex. Each vertex receives a vec2 of its position 122 and texture coordinate, given by 'positionAttributeName' and 123 'texPositionAttributeName.' Vertices also are blended with a color, 124 given by 'colorAttributeName.' The texture is passed as a uniform 125 sampler2D to 'textureAttributeName.' The output of the shader is a 126 vec4 for a color, given by 'colorOutputName' 127 */ 128 public void setShader(in string vertexShader, 129 in string fragmentShader, 130 in string transformAttributeName = "transform", 131 in string positionAttributeName = "position", 132 in string texPositionAttributeName = "tex_coord", 133 in string colorAttributeName = "color", 134 in string textureAttributeName = "tex", 135 in string colorOutputName = "outColor") 136 { 137 transformAttribute = transformAttributeName; 138 positionAttribute = positionAttributeName; 139 texPositionAttribute = texPositionAttributeName, 140 colorAttribute = colorAttributeName; 141 textureAttribute = textureAttributeName; 142 if(shader != 0) glDeleteProgram(shader); 143 if(vertex != 0) glDeleteShader(vertex); 144 if(fragment != 0) glDeleteShader(fragment); 145 vertex = glCreateShader(GL_VERTEX_SHADER); 146 auto vertexShaderPtr = vertexShader.ptr; 147 glShaderSource(vertex, 1, cast(GLchar**)(&vertexShaderPtr), null); 148 glCompileShader(vertex); 149 GLint status; 150 glGetShaderiv(vertex, GL_COMPILE_STATUS, &status); 151 if (status != GL_TRUE) 152 { 153 println("Vertex shader compilation failed"); 154 char[512] buffer; 155 GLsizei length; 156 glGetShaderInfoLog(vertex, 512, &length, buffer.ptr); 157 println("Error: ", buffer[0..length]); 158 setShader(DEFAULT_VERTEX_SHADER, fragmentShader); 159 } 160 fragment = glCreateShader(GL_FRAGMENT_SHADER); 161 auto fragmentShaderPtr = fragmentShader.ptr; 162 glShaderSource(fragment, 1, cast(GLchar**)(&fragmentShaderPtr), null); 163 glCompileShader(fragment); 164 glGetShaderiv(fragment, GL_COMPILE_STATUS, &status); 165 if (status != GL_TRUE) 166 { 167 println("Fragment shader compilation failed\n"); 168 char[512] buffer; 169 GLsizei length; 170 glGetShaderInfoLog(vertex, 512, &length, buffer.ptr); 171 println("Error: ", buffer[0..length]); 172 setShader(vertexShader, DEFAULT_FRAGMENT_SHADER); 173 } 174 shader = glCreateProgram(); 175 glAttachShader(shader, vertex); 176 glAttachShader(shader, fragment); 177 glBindFragDataLocation(shader, 0, colorOutputName.ptr); 178 glLinkProgram(shader); 179 glUseProgram(shader); 180 } 181 182 /** 183 Clear the screen and the vertex and index buffers 184 185 Any data that hasn't been drawn will be lost 186 */ 187 public void clear(in Color col) 188 { 189 vertices.clear(); 190 indices.clear(); 191 glClearColor(col.r, col.g, col.b, col.a); 192 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 193 } 194 195 ///Draw the current vertices and indices and clear the buffers 196 public void flush() 197 { 198 GLint transform_attrib = glGetUniformLocation(shader, transformAttribute.ptr); 199 glUniformMatrix3fv(transform_attrib, 1, GL_FALSE, transform.ptr); 200 //Bind the vertex data 201 glBindBuffer(GL_ARRAY_BUFFER, vbo); 202 glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof, vertices.ptr, GL_STREAM_DRAW); 203 //Bind the index data 204 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); 205 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * GLuint.sizeof, indices.ptr, GL_STREAM_DRAW); 206 //Set up the vertex attributes 207 GLint posAttrib = glGetAttribLocation(shader, positionAttribute.ptr); 208 glEnableVertexAttribArray(posAttrib); 209 glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)0); 210 GLint texAttrib = glGetAttribLocation(shader, texPositionAttribute.ptr); 211 glEnableVertexAttribArray(texAttrib); 212 glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)(2 * GLfloat.sizeof)); 213 GLint colAttrib = glGetAttribLocation(shader, colorAttribute.ptr); 214 glEnableVertexAttribArray(colAttrib); 215 glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)(4 * GLfloat.sizeof)); 216 //Upload the texture to the GPU 217 texture_location = glGetUniformLocation(shader, textureAttribute.ptr); 218 glActiveTexture(GL_TEXTURE0); 219 glBindTexture(GL_TEXTURE_2D, texture); 220 glUniform1i(texture_location, 0); 221 //Draw the triangles 222 glDrawElements(GL_TRIANGLES, cast(int)indices.length, GL_UNSIGNED_INT, cast(void*)0); 223 vertices.clear(); 224 indices.clear(); 225 } 226 227 ///Flush the buffers and display the new screen 228 public void flip() 229 { 230 flush(); 231 SDL_GL_SwapWindow(window); 232 } 233 234 235 private void switchTexture(in GLuint texture) 236 { 237 if (this.texture != 0) 238 flush(); 239 this.texture = texture; 240 } 241 242 /** 243 Add some vertices and indices to the backend 244 245 The 0th index is the first vertex in this add, not since the last flush 246 */ 247 public void add(in GLuint texture, 248 in Vertex[] newVertices, in GLuint[] newIndices) 249 { 250 if(this.texture != texture) 251 switchTexture(texture); 252 auto offset = vertices.length / vertex_size; 253 foreach(v; newVertices) 254 vertices.addAll(v.pos.x, v.pos.y, v.texPos.x, v.texPos.y, 255 v.col.r, v.col.g, v.col.b, v.col.a); 256 foreach(i; newIndices) 257 indices.add(cast(uint)(i + offset)); 258 } 259 260 public void setTransform(in Transform transform) 261 { 262 this.transform = transform; 263 if(indices.length != 0) 264 flush(); 265 } 266 } 267 unittest 268 { 269 auto vert = Vertex(Vector(0, 0), Vector(1, 1), Color(1, 1, 1, 1)); 270 println("Should print a white vertex at 0, 0 from 1, 1: ", vert); 271 }