1 /** 2 * <div class="header"> 3 * High-level OpenGL Wrapper/Helpers: Core Package 4 * Authors: S.Percentage 5 * </div> 6 */ 7 /** 8 Macros: 9 COPYRIGHT = Copyright 2016 S.Percentage 10 DDOC = <!DOCTYPE html> 11 <html><head> 12 <meta charset="utf-8"> 13 <title>$(TITLE)</title> 14 <link rel="stylesheet" href="style.css" type="text/css" /> 15 </head><body> 16 <div class="box"> 17 <h1>$(TITLE)</h1> 18 $(BODY) 19 <hr>$(SMALL Page generated by $(LINK2 https://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT)) 20 </div> 21 </body></html> 22 */ 23 module objectivegl.core; 24 25 // Version Have_dglsl: Enabled dglsl support if required 26 27 public import derelict.opengl3.gl3; 28 import std..string, std.algorithm, std.range, std.meta, std.traits, std.typecons; 29 version(Have_dglsl) 30 { 31 import dglsl; 32 } 33 34 /// Pixel Format for Textures 35 enum PixelFormat 36 { 37 /// 32bpp full color 38 RGBA = GL_RGBA, 39 /// 8bpp single color 40 Grayscale = GL_RED 41 } 42 43 /// OpenGL Texture Interfacing 44 abstract class Texture(GLenum TextureType) 45 { 46 protected GLuint id; 47 48 protected this() { glGenTextures(1, &this.id); } 49 ~this() { glDeleteTextures(1, &this.id); } 50 51 protected void bind() { glBindTexture(TextureType, this.id); } 52 static class Parameter 53 { 54 @disable this(); 55 56 static auto opIndexAssign(GLint value, GLenum param) { glTexParameteri(TextureType, param, value); } 57 } 58 } 59 60 /// OpenGL Texture2D Representation 61 final class Texture2D : Texture!GL_TEXTURE_2D 62 { 63 /// Makes empty texture 64 public static auto newEmpty(int width, int height, PixelFormat format) in { assert(width >= 1 && height >= 1); } body 65 { 66 auto obj = new Texture2D(); 67 obj.bind(); 68 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, null); 69 Texture2D.Parameter[GL_TEXTURE_MIN_FILTER] = GL_LINEAR; 70 Texture2D.Parameter[GL_TEXTURE_MAG_FILTER] = GL_LINEAR; 71 return obj; 72 } 73 74 /// Updates texture 75 public void update(int x, int y, int width, int height, const(ubyte)* pixels, PixelFormat format) 76 { 77 this.bind(); 78 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, format, GL_UNSIGNED_BYTE, pixels); 79 } 80 } 81 82 /// OpenGL Buffer Object Interfacing 83 abstract class Buffer(GLenum BufferType) 84 { 85 protected GLuint id; 86 protected this(GLuint buffer) { this.id = buffer; } 87 ~this() { glDeleteBuffers(1, &this.id); } 88 89 protected static auto newOne(BufferDataT)(in BufferDataT* ptr, GLenum usage) 90 { 91 GLuint b; 92 93 glGenBuffers(1, &b); 94 glBindBuffer(BufferType, b); scope(exit) glBindBuffer(BufferType, 0); 95 glBufferData(BufferType, BufferDataT.sizeof, ptr, usage); 96 return b; 97 } 98 } 99 100 /// OpenGL VertexArrayObject Representation 101 final class VertexArray 102 { 103 private GLuint aid, bid; 104 private GLuint vcount; 105 106 private this(GLuint array, GLuint buffer, GLuint vcount) 107 { 108 this.aid = array; 109 this.bid = buffer; 110 this.vcount = vcount; 111 } 112 ~this() { glDeleteBuffers(1, &this.bid); glDeleteVertexArrays(1, &this.aid); } 113 114 /// Makes new Vertex Array Object from slice to be rendered with program 115 public static auto fromSlice(T)(const T[] slice, const ShaderProgram program) 116 { 117 GLuint aid, bid; 118 119 glGenVertexArrays(1, &aid); 120 glGenBuffers(1, &bid); 121 122 glBindVertexArray(aid); scope(exit) glBindVertexArray(0); 123 glBindBuffer(GL_ARRAY_BUFFER, bid); scope(exit) glBindBuffer(GL_ARRAY_BUFFER, 0); 124 glBufferData(GL_ARRAY_BUFFER, slice.length * T.sizeof, slice.ptr, GL_STATIC_DRAW); 125 program.applyInputLayouts(); 126 127 return new VertexArray(aid, bid, cast(GLuint)slice.length); 128 } 129 130 /// Instanced drawing shorthand 131 public void drawInstanced(GLenum primitiveType)(GLint count) 132 { 133 GLDevice.Vertices = this; 134 glDrawArraysInstanced(primitiveType, 0, this.vcount, count); 135 } 136 137 /// Updates buffer data 138 public void update(T)(const T[] slice) 139 { 140 glBindBuffer(GL_ARRAY_BUFFER, this.bid); scope(exit) glBindBuffer(GL_ARRAY_BUFFER, 0); 141 glBufferData(GL_ARRAY_BUFFER, slice.length * T.sizeof, slice.ptr, GL_STATIC_DRAW); 142 this.vcount = cast(GLuint)slice.length; 143 } 144 } 145 146 /// Type inferred Uniform Buffer factory 147 final class UniformBufferFactory 148 { 149 @disable this(); 150 151 /// Makes new Static(Modified once, used many times) Uniform Buffer with Data 152 public static auto newStatic(BufferStructureT)(BufferStructureT buffer) 153 { 154 return new UniformBuffer!BufferStructureT(Buffer!GL_UNIFORM_BUFFER.newOne(&buffer, GL_STATIC_DRAW)); 155 } 156 } 157 158 /// OpenGL Uniform Buffer Representation 159 final class UniformBuffer(BufferStructureT) : Buffer!GL_UNIFORM_BUFFER 160 { 161 private this(GLuint id) { super(id); } 162 /// Makes new Static(Modified once, used many times) Uniform Buffer with Data 163 public static auto newStatic(BufferStructureT buffer) 164 { 165 return new UniformBuffer(newOne(&buffer, GL_STATIC_DRAW)); 166 } 167 /// Makes new Static(Modified once, used many times) Uniform Buffer 168 public static auto newStatic() 169 { 170 return new UniformBuffer(newOne!BufferStructureT(null, GL_STATIC_DRAW)); 171 } 172 173 /// Updates buffer data 174 public void update(BufferStructureT buffer) 175 { 176 glBindBuffer(GL_UNIFORM_BUFFER, this.id); scope(exit) glBindBuffer(GL_UNIFORM_BUFFER, 0); 177 glBufferSubData(GL_UNIFORM_BUFFER, 0, BufferStructureT.sizeof, &buffer); 178 } 179 } 180 181 /// UDA: Mark field as Input Element 182 struct element 183 { 184 /// Attribute Name in shader 185 string attrName; 186 /// Is value normalized?(default is false) 187 bool normalized = false; 188 } 189 /// Type to OpenGL Type Enum 190 private template TypeEnum(T) 191 { 192 static if(is(T : float)) alias TypeEnum = GL_FLOAT; 193 else static assert(false, "Unsupported Type"); 194 } 195 // Map fields to input element descriptor 196 private template InputElementGen(alias Symbol) 197 { 198 immutable Attr = getUDAs!(Symbol, element)[0]; 199 immutable InputElementGen = InputElement(Attr.attrName, Symbol.length, TypeEnum!(typeof(Symbol[0])), 200 Attr.normalized ? GL_TRUE : GL_FALSE, __traits(parent, Symbol).sizeof, cast(const GLvoid*)Symbol.offsetof); 201 } 202 /// Auto generated input element descriptor list from vertex data structure 203 private alias IEDescList(VertexDataT) = staticMap!(InputElementGen, getSymbolsByUDA!(VertexDataT, element)); 204 205 // Vertex(Shader Input) Elements 206 private struct InputElement 207 { 208 string attrName; 209 GLint size; 210 GLenum type; 211 GLboolean normalized; 212 GLsizei stride; 213 const GLvoid* offset; 214 } 215 216 /// Shader Source Types 217 enum ShaderType : GLenum 218 { 219 /// Vertex Shader 220 Vertex = GL_VERTEX_SHADER, 221 /// Fragment(Pixel) Shader 222 Fragment = GL_FRAGMENT_SHADER, 223 /// Geometry Shader 224 Geometry = GL_GEOMETRY_SHADER 225 } 226 227 /// OpenGL ShaderProgram Representation 228 final class ShaderProgram 229 { 230 private struct ResolvedInputElement 231 { 232 GLuint index; 233 GLint size; 234 GLenum type; 235 GLboolean normalized; 236 GLsizei stride; 237 const GLvoid* offset; 238 } 239 private ResolvedInputElement[] elements; 240 private GLuint pid; 241 242 private this(GLuint p, const InputElement[] elements) 243 { 244 this.pid = p; 245 this.elements = elements.map!(x => ResolvedInputElement(glGetAttribLocation(p, x.attrName.toStringz), 246 x.size, x.type, x.normalized, x.stride, x.offset)).array; 247 248 this.uniforms = new UniformLocations(); 249 this.uniformBlocks = new UniformBlockIndices(); 250 } 251 ~this() { glDeleteProgram(this.pid); } 252 253 version(Have_dglsl) 254 { 255 /// Make shader program from dglsl shader classes 256 public static auto fromDSLClasses(VertexDataT, ShaderT...)() 257 { 258 ShaderT shaders; 259 foreach(i, ST; ShaderT) 260 { 261 shaders[i] = new ST(); 262 shaders[i].compile(); 263 } 264 auto p = new dglsl.Program!ShaderT(shaders); 265 return new ShaderProgram(p.id, [IEDescList!VertexDataT]); 266 } 267 } 268 /// Make shader program from source codes 269 public static auto fromSources(VertexDataT, ShaderSources...)() 270 { 271 auto compileShader(ShaderType T, string Source)() 272 { 273 auto sh = glCreateShader(T); 274 auto src = Source.toStringz; 275 auto srcLength = Source.length; 276 glShaderSource(sh, 1, &src, cast(GLint*)&srcLength); 277 glCompileShader(sh); 278 279 GLint status; 280 glGetShaderiv(sh, GL_COMPILE_STATUS, &status); 281 if(status == GL_FALSE) 282 { 283 GLint errlen; 284 GLchar[] errbuf; 285 glGetShaderiv(sh, GL_INFO_LOG_LENGTH, &errlen); 286 errbuf.length = errlen; 287 glGetShaderInfoLog(sh, cast(GLint)errbuf.length, null, errbuf.ptr); 288 throw new Exception(errbuf.idup); 289 } 290 return sh; 291 } 292 template shaderCompilationList(ShaderSources...) 293 { 294 static if(ShaderSources.length < 2) alias shaderCompilationList = AliasSeq!(); 295 else alias shaderCompilationList = AliasSeq!(compileShader!(ShaderSources[0], ShaderSources[1]), shaderCompilationList!(ShaderSources[2 .. $])); 296 } 297 auto shaders = [shaderCompilationList!ShaderSources]; 298 scope(exit) shaders.each!glDeleteShader; 299 300 auto p = glCreateProgram(); 301 shaders.each!(x => glAttachShader(p, x)); 302 glLinkProgram(p); 303 304 GLint status; 305 glGetProgramiv(p, GL_LINK_STATUS, &status); 306 if(status == GL_FALSE) 307 { 308 GLint errlen; 309 GLchar[] errbuf; 310 glGetProgramiv(p, GL_INFO_LOG_LENGTH, &errlen); 311 errbuf.length = errlen; 312 glGetProgramInfoLog(p, cast(GLint)errbuf.length, null, errbuf.ptr); 313 throw new Exception(errbuf.idup); 314 } 315 return new ShaderProgram(p, [IEDescList!VertexDataT]); 316 } 317 /// Build shader from imported source 318 unittest 319 { 320 auto shader = ShaderProgram.fromSources!(VertexData, 321 ShaderType.Vertex, import("vsh.glsl"), 322 ShaderType.Fragment, import("fsh.glsl")); 323 } 324 325 private void applyInputLayouts() const 326 { 327 glUseProgram(this.pid); 328 foreach(ref e; this.elements) 329 { 330 glEnableVertexAttribArray(e.index); 331 glVertexAttribPointer(e.index, e.size, e.type, e.normalized, e.stride, e.offset); 332 } 333 glUseProgram(0); 334 } 335 336 /// Activates(Uses) shader program 337 public void activate() const 338 { 339 glUseProgram(this.pid); 340 } 341 342 private class UniformLocations 343 { 344 GLuint[string] cache; 345 346 public auto opDispatch(string name)(int v) { this[name] = v; } 347 public auto opDispatch(string name)(float v) { this[name] = v; } 348 public auto opDispatch(string name)(float[2] v) { this[name] = v; } 349 public auto opDispatch(string name)(in float[4] vf) { this[name] = vf; } 350 public auto opDispatch(string name)(in float[4][4] matr) { this[name] = matr; } 351 public auto opIndexAssign(int v, string name) { glUniform1i(this.getLocation(name), v); } 352 public auto opIndexAssign(float v, string name) { glUniform1f(this.getLocation(name), v); } 353 public auto opIndexAssign(float[2] v, string name) { glUniform2fv(this.getLocation(name), 1, v.ptr); } 354 public auto opIndexAssign(in float[4] vf, string name) { glUniform4fv(this.getLocation(name), 1, vf.ptr); } 355 public auto opIndexAssign(in float[4][4] matr, string name) { glUniformMatrix4fv(this.getLocation(name), 1, GL_FALSE, &matr[0][0]); } 356 version(Have_gl3n) 357 { 358 import gl3n.linalg; 359 public auto opDispatch(string name)(in vec2 vf) { this[name] = vf; } 360 public auto opDispatch(string name)(in vec4 vf) { this[name] = vf; } 361 public auto opIndexAssign(in vec2 vf, string name) { glUniform2fv(this.getLocation(name), 1, vf.value_ptr); } 362 public auto opIndexAssign(in vec4 vf, string name) { glUniform4fv(this.getLocation(name), 1, vf.value_ptr); } 363 } 364 365 private auto getLocation(string name) 366 { 367 if(name !in cache) cache[name] = glGetUniformLocation(this.outer.pid, name.toStringz); 368 return cache[name]; 369 } 370 } 371 /// Field like Uniform Accessors 372 UniformLocations uniforms; 373 374 private class UniformBlockIndices 375 { 376 GLuint[string] cache; 377 378 public auto opDispatch(string name)(int idx) { this[name] = idx; } 379 public auto opIndexAssign(int idx, string name) { glUniformBlockBinding(this.outer.pid, this.getIndex(name), idx); } 380 381 private auto getIndex(string name) 382 { 383 if(name !in cache) cache[name] = glGetUniformBlockIndex(this.outer.pid, name.toStringz); 384 return cache[name]; 385 } 386 } 387 /// Field like Uniform Block Binding 388 UniformBlockIndices uniformBlocks; 389 } 390 391 /// Blend Function 392 alias BlendFunc = Tuple!(GLenum, "srcBlend", GLenum, "destBlend"); 393 /// Predefined Blend Functions 394 final class BlendFunctions 395 { 396 @disable this(); 397 398 static immutable Alpha = BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 399 } 400 401 /// OpenGL Device Representation 402 final class GLDevice 403 { 404 @disable this(); 405 406 /// Texture Units 407 static class TextureUnits 408 { 409 @disable this(); 410 411 static void opIndexAssign(GLenum TextureType)(in Texture!TextureType tex, int index) 412 { 413 glActiveTexture(GL_TEXTURE0 + index); 414 glBindTexture(TextureType, tex.id); 415 } 416 } 417 /// Accessing texture unit with index 418 unittest 419 { 420 GLDevice.TextureUnits[0] = texture; 421 } 422 423 /// Binding Point Table 424 static class BindingPoint 425 { 426 @disable this(); 427 428 static void opIndexAssign(T)(in UniformBuffer!T buffer, int index) 429 { 430 glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer.id); 431 } 432 } 433 434 /// Input Assembler: Vertex Buffer and Input Layout 435 static class Vertices 436 { 437 @disable this(); 438 439 static void opAssign(in VertexArray varray) 440 { 441 glBindVertexArray(varray.aid); 442 } 443 } 444 445 /// Rasterizer State 446 static class RasterizerState 447 { 448 @disable this(); 449 450 private static class DeviceCaps(GLenum CapEnum) 451 { 452 @disable this(); 453 454 static void opAssign(bool flag) 455 { 456 (flag ? glEnable : glDisable)(CapEnum); 457 } 458 } 459 public alias Blending = DeviceCaps!GL_BLEND; 460 public alias ScissorTest = DeviceCaps!GL_SCISSOR_TEST; 461 public alias BackCulling = DeviceCaps!GL_CULL_FACE; 462 public alias DepthTest = DeviceCaps!GL_DEPTH_TEST; 463 public alias DepthClamp = DeviceCaps!GL_DEPTH_CLAMP; 464 static void opDispatch(string name)(BlendFunc blend) if(name == "BlendFunc") 465 { 466 glBlendFunc(blend.srcBlend, blend.destBlend); 467 } 468 } 469 470 /// Pixel Store 471 static class PixelStore 472 { 473 @disable this(); 474 475 static auto opIndexAssign(GLint value, GLenum param) { glPixelStorei(param, value); } 476 } 477 }