1 /++ 2 Authors: Miguel Ángel (quevangel), quevangel@protonmail.com 3 +/ 4 5 module daque.graphics.opengl; 6 7 import std.string; 8 import std.file; 9 import std.algorithm; 10 11 import core.stdc.stdlib; 12 13 import derelict.opengl; 14 import derelict.sdl2.sdl; 15 import derelict.sdl2.image; 16 17 import daque.math.geometry; 18 19 /// Renders an Array of vertices already on GPU memory 20 void render(GpuArray vertices) 21 { 22 vertices.bind(); 23 glDrawArrays(GL_TRIANGLES, 0, cast(int) vertices.size()); 24 } 25 26 /++ 27 Initializes required libraries for graphics rendering. 28 29 Initializes SDL2 and OpenGL libraries. 30 +/ 31 static this() 32 { 33 DerelictGL3.load(); 34 } 35 36 /++ 37 +/ 38 static ~this() 39 { 40 } 41 /++ 42 Represents a 'shader' OpenGL object. 43 44 A shader is *part* of a program ought to be executed by the GPU. 45 This class serves as a way to compile and use those program parts. 46 47 The "whole" Program is another OpenGL object constructed by assembling 48 many Shader s. 49 +/ 50 class Shader 51 { 52 public: 53 /// Types of shaders there can be 54 enum Type 55 { 56 Vertex, 57 Fragment 58 } 59 /++ 60 Get the type of the shader 61 Returns: type of the shader 62 +/ 63 @property Type type() 64 { 65 return m_type; 66 } 67 68 /++ 69 Constructs a new shader of the specified type, using as source code the file pointed to by 70 sourcePath 71 72 Params: 73 type = Type of the shader to be constructed 74 sourcePath = String representing a path to a file containing the 75 source code which will be used as source for the constructed shader 76 +/ 77 this(Shader.Type type, string sourcePath) 78 { 79 m_type = type; 80 m_shaderGlName = cast(immutable(GLuint)) compileShader(type, sourcePath); 81 } 82 83 ~this() 84 { 85 glDeleteShader(m_shaderGlName); 86 } 87 88 private: 89 immutable(GLuint) m_shaderGlName; 90 immutable(Type) m_type; 91 /++ 92 Compiles a shader of the specified type, using as source code the file pointed to by sourcePath 93 and returns the name of the opengl object representing the compiled shader. 94 95 Params: 96 type = Type of shader to be compiled 97 sourcePath = Path to the shader's source code 98 99 Returns: 100 Opengl Name of the compiled shader 101 +/ 102 static GLuint compileShader(immutable Type type, string sourcePath) 103 { 104 // Create and compile 105 GLuint shaderName = glCreateShader(typeToGlenum(type)); 106 const char* sourceCodeZ = toStringz(readText(sourcePath)); 107 glShaderSource(shaderName, 1, &sourceCodeZ, null); 108 glCompileShader(shaderName); 109 110 // Error checking 111 GLint compilationSuccess = 0; 112 glGetShaderiv(shaderName, GL_COMPILE_STATUS, &compilationSuccess); 113 // Error case 114 if (compilationSuccess == GL_FALSE) 115 { 116 GLint logSize = 0; 117 GLchar[] errorLog; 118 119 glGetShaderiv(shaderName, GL_INFO_LOG_LENGTH, &logSize); 120 errorLog.length = logSize; 121 glGetShaderInfoLog(shaderName, logSize, &logSize, &errorLog[0]); 122 string info = cast(string) fromStringz(&errorLog[0]); 123 124 import std.stdio : writeln; 125 writeln("COMPILATION ERROR: ", info); 126 127 glDeleteShader(shaderName); 128 shaderName = 0; 129 } 130 else // Success case 131 { 132 } 133 134 return shaderName; 135 } 136 137 /++ 138 Maps Shader.Type to equivalent OpenGL GLenum. 139 140 Params: 141 type = type to be mapped to GLenum 142 Returns: 143 GLenum equivalent of type 144 +/ 145 static pure GLenum typeToGlenum(Shader.Type type) 146 { 147 final switch (type) 148 { 149 case Shader.Type.Vertex: 150 return GL_VERTEX_SHADER; 151 case Shader.Type.Fragment: 152 return GL_FRAGMENT_SHADER; 153 } 154 } 155 } 156 157 /++ 158 Represents and handles a Program Opengl Object. 159 A Program is a group of Opengl Shaders which will be linked together. 160 A Program is a program to be executed by the GPU to each of the Vertices of a model. 161 +/ 162 class Program 163 { 164 public: 165 /++ 166 Creates a new empty program 167 +/ 168 this() 169 { 170 m_programGlName = glCreateProgram(); 171 } 172 /++ 173 Creates a program with the specified shaders already attached 174 +/ 175 this(Shader[] shaders) 176 { 177 this(); 178 shaders.each!(s => this.attach(s)); 179 } 180 181 ~this() 182 { 183 glDeleteProgram(m_programGlName); 184 } 185 /++ 186 Attaches the shader to this program 187 188 Params: 189 shader = Shader to be attached 190 +/ 191 void attach(Shader shader) 192 { 193 glAttachShader(m_programGlName, shader.m_shaderGlName); 194 } 195 /++ 196 Links the currently attached shaders 197 +/ 198 void link() 199 { 200 glLinkProgram(m_programGlName); 201 202 GLint isLinked = 0; 203 glGetProgramiv(m_programGlName, GL_LINK_STATUS, cast(int*)&isLinked); 204 if (isLinked == GL_FALSE) 205 { 206 import std.stdio: writeln; 207 writeln("LINKING ERROR"); 208 } 209 } 210 211 /++ 212 Binds the program to the current opengl context so that it is used to process new render 213 commands 214 +/ 215 void use() 216 { 217 glUseProgram(m_programGlName); 218 } 219 220 221 int getUniformLocation(string name) 222 { 223 return glGetUniformLocation(m_programGlName, name.toStringz()); 224 } 225 226 template strToType(string typeString) 227 { 228 static if(typeString == "i") 229 alias strToType = int; 230 else static if(typeString == "f") 231 alias strToType = float; 232 else 233 static assert(0, "unrecognized string type " ~ typeString); 234 } 235 236 import std.conv; 237 /++ 238 Sets a integer uniform variable inside the program 239 240 Params: 241 uniformName = name of the single-valued uniform integer to be changed 242 val = new value to be assigned 243 +/ 244 void setUniform(uint count, string typeString)(int location, void[] data) 245 { 246 this.use(); 247 mixin("alias glUniform = " ~ "glUniform" ~ to!string(count) ~ typeString ~ "v;"); 248 glUniform(location, cast(int)(data.length / (strToType!typeString.sizeof * count)), cast(strToType!typeString*)data.ptr); 249 } 250 251 // TODO: setUniformMatrix method 252 253 void getUniform(string typeString)(int location, strToType!typeString* output) 254 { 255 mixin("alias glGetUniform = glGetUniform" ~ typeString ~ "v;"); 256 glGetUniform(m_programGlName, location, output); 257 } 258 259 private: 260 // associated Opengl Object Program's name 261 immutable(GLuint) m_programGlName; 262 263 } 264 265 /++ 266 Represents a buffer opengl object. 267 A buffer opengl object is the mechanism through which data can be stored in the GPU, usually 268 vertex data of the models to be rendered. 269 270 This class eases/abstracts the interaction with this kind of opengl objects. 271 +/ 272 class Buffer 273 { 274 public: 275 /++ 276 Constructs a new and empty buffer 277 +/ 278 this() 279 { 280 m_name = Buffer.gen(); 281 } 282 283 ~this() 284 { 285 del(m_name); 286 } 287 288 /++ 289 Generates a new opengl buffer and returns it's name 290 Returns: name of the newly created opengl buffer 291 +/ 292 static GLuint gen() 293 { 294 GLuint buffer; 295 glGenBuffers(1, &buffer); 296 return buffer; 297 } 298 299 /++ 300 Deletes a opengl buffer given it's name 301 Params : 302 buffer = opengl name of the opengl buffer 303 +/ 304 static void del(GLuint buffer) 305 { 306 glDeleteBuffers(1, &buffer); 307 } 308 309 /++ 310 Sends the unformatted data of the specified size to the buffer 311 312 Params: 313 data = Pointer to the data to be sent 314 size = Size in bytes of the data to be sent 315 +/ 316 void bufferData(void* data, size_t size) 317 { 318 bind(); 319 glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW); 320 } 321 322 /++ 323 Binds the buffer to the current opengl context 324 +/ 325 void bind() 326 { 327 glBindBuffer(GL_ARRAY_BUFFER, m_name); 328 } 329 330 private: 331 // opengl name of the buffer managed by @this 332 immutable(GLuint) m_name; 333 } 334 335 /++ 336 Data needed to represent a particular attribute for a Vertex. 337 +/ 338 struct AttributeFormat 339 { 340 /// OpenGL identifies each attribute by an @index 341 GLuint index; 342 /// No. of components of this attribute 343 GLint size; 344 /// Data type of the components of this attribute 345 GLenum type; 346 /// Does it need to be _normalized_(Clipped to a range of 0.0 - 1.0)? 347 GLboolean normalized; 348 /// Space between each appearance of this attribute in an array of Vertices, equivalently, the 349 /// size of each Vertex 350 GLsizei stride; 351 /// Offset to first appearance of this attribute in an array of Vertices, equivalently, the 352 /// offset of this member in the Vertex structure 353 const GLvoid* pointer; 354 } 355 356 /++ 357 Given the Buffer and the VertexArray currently bound to the OpenGL context, this function provides 358 format info about the attribute format.index of the vertices in the VertexArray. 359 360 This associates the Buffer to the VertexArray. 361 362 Params: 363 format = attribute format to be given to the VertexArray currently bound 364 +/ 365 void setup(AttributeFormat format) 366 { 367 glEnableVertexAttribArray(format.index); 368 369 glVertexAttribPointer(format.index, format.size, format.type, 370 format.normalized, format.stride, format.pointer); 371 } 372 373 /++ 374 Represents an opengl Vertex Array Object (VAO). 375 A VAO relates Opengl Buffers and Vertex Formats. 376 +/ 377 class VertexArray 378 { 379 private: 380 // opengl name of the VAO managed by @this 381 immutable(GLuint) m_name; 382 public: 383 /++ 384 Generates and empty VAO and saves it's name 385 +/ 386 this() 387 { 388 m_name = genVertexArray(); 389 } 390 391 static GLuint genVertexArray() 392 { 393 GLuint name; 394 glGenVertexArrays(1, &name); 395 return name; 396 } 397 /++ 398 Deallocates the VAO 399 +/ 400 ~this() 401 { 402 deleteVertexArray(m_name); 403 } 404 405 static void deleteVertexArray(GLuint vertexArrayName) 406 { 407 glDeleteVertexArrays(1, &vertexArrayName); 408 } 409 /++ 410 Associates this VertexArray with the buffer and the format given by the type 411 VertexType. 412 413 Inputs: 414 buffer = Buffer to associae with this VertexArray and this format 415 +/ 416 void use(Buffer buffer, AttributeFormat[] formats) 417 { 418 bind(); 419 buffer.bind(); 420 formats.each!setup; 421 } 422 423 /++ 424 Binds this VertexArray to the opengl context 425 +/ 426 void bind() 427 { 428 glBindVertexArray(m_name); 429 } 430 } 431 432 /++ 433 Represents an array of things to be stored in the GPU 434 Params: 435 DataType = Type of the data to be stored 436 +/ 437 class GpuArray 438 { 439 private: 440 Buffer m_buffer; 441 VertexArray m_vao; 442 uint m_size; 443 444 public: 445 /++ 446 Creates and fills a new gpu array 447 448 Params: 449 data = data to be initialy filled with 450 +/ 451 this(void[] data, uint noElements, AttributeFormat[] attributeFormats) 452 { 453 m_buffer = new Buffer(); 454 m_vao = new VertexArray(); 455 m_size = noElements; 456 457 m_buffer.bufferData(data.ptr, data.length); 458 m_vao.use(m_buffer, attributeFormats); 459 } 460 461 /// Binds the associated VertexArray to the opengl context 462 void bind() 463 { 464 m_vao.bind(); 465 } 466 467 uint size() 468 { 469 return m_size; 470 } 471 } 472 473 /++ 474 Represents a 2D opengl texture 475 +/ 476 class Texture 477 { 478 private: 479 immutable(GLuint) m_name; 480 immutable(GLenum) m_type; 481 482 immutable(SDL_Surface*) m_surface; 483 immutable(uint) m_width, m_height; 484 485 public: 486 /++ 487 Constructs a Texture from an image file got from imagePath 488 489 Params: 490 imagePath = path to the image to be used as a source to construct the texture 491 +/ 492 this(string imagePath) 493 { 494 m_type = GL_TEXTURE_2D; 495 m_surface = cast(immutable(SDL_Surface*)) IMG_Load(imagePath.toStringz()); 496 m_width = m_surface.w; 497 m_height = m_surface.h; 498 499 if (!m_surface) // error reading surface 500 { 501 return; 502 } 503 else if (m_surface.format.format != SDL_PIXELFORMAT_RGBA32) // unsupported pixel format 504 { 505 return; 506 } 507 508 m_name = Texture.gen(); 509 this.bind(); 510 this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); 511 this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); 512 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_surface.w, m_surface.h, 0, 513 GL_RGBA, GL_UNSIGNED_BYTE, m_surface.pixels); 514 } 515 516 /++ 517 Constructs an empty Texture with the specified width and height, and fills it with color 518 clearColor 519 520 Params: 521 width = width of the texture to be constructed 522 height = height of the texture to be constructed 523 clearColor = color to be filled with 524 +/ 525 this(uint width, uint height, uint clearColor = 0xffffffff) 526 { 527 m_width = width; 528 m_height = height; 529 530 m_name = Texture.gen(); 531 m_type = GL_TEXTURE_2D; 532 m_surface = null; 533 534 this.bind(); 535 this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); 536 this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); 537 glTexImage2D(m_type, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 538 } 539 540 ~this() 541 { 542 Texture.del(m_name); 543 } 544 545 /++ 546 Fills the texture with color clearColor 547 +/ 548 void clear(uint clearColor) 549 { 550 //t glClearTexImage(m_name, 0, GL_RGBA, GL_UNSIGNED_BYTE, &clearColor); 551 } 552 553 private template GLType(string name) 554 { 555 static if (name == "f") 556 { 557 alias GLType = GLfloat; 558 } 559 else static if (name == "i") 560 { 561 alias GLType = GLint; 562 } 563 } 564 /// Sets an internal opengl parameter for the texture 565 void setParameter(string typename)(GLenum parameterName, GLType!typename value) 566 { 567 this.bind(); 568 569 mixin("alias glTexParameter = glTexParameter" ~ typename ~ ";"); 570 glTexParameter(m_type, parameterName, value); 571 } 572 573 /// Returns the width of the texture 574 uint width() 575 { 576 return m_width; 577 } 578 /// Returns the height of the texture 579 uint height() 580 { 581 return m_height; 582 } 583 584 /// Binds the texture to the opengl context 585 void bind() 586 { 587 glBindTexture(m_type, m_name); 588 } 589 590 /// Returns the opengl index of the texture ( aka: it's name ) 591 GLuint name() 592 { 593 return m_name; 594 } 595 /++ 596 Sets the pixels of a specified rectangular region 597 598 Params: 599 offsetx = x coordinate ( from low left texture corner ) of the low left corner of 600 the region 601 602 offsety = y coordinate ( from low left texture corner ) of the low left corner of 603 the region 604 605 width = width of the region 606 height = height of the region 607 608 data = data in row major order of the pixels to be set 609 +/ 610 void updateRegion(uint offsetx, uint offsety, uint width, uint height, uint[] data) 611 in 612 { 613 assert(data.length >= width * height); 614 } 615 out 616 { 617 } 618 do 619 { 620 this.bind(); 621 glTexSubImage2D(m_type, 0, offsetx, offsety, width, height, GL_RGBA, 622 GL_UNSIGNED_BYTE, data.ptr); 623 } 624 625 /++ 626 Generates a new opengl texture and returns it's name 627 628 Returns: the name of the newly created texture 629 +/ 630 static GLuint gen() 631 { 632 GLuint name; 633 glGenTextures(1, &name); 634 return name; 635 } 636 637 /++ 638 Deletes an opengl texture using it's name 639 +/ 640 static void del(GLuint texture) 641 { 642 glDeleteTextures(1, &texture); 643 } 644 } 645 646 /++ 647 Assigns texture to textureUnit. 648 649 Params: 650 textureUnit = texture unit index to be set 651 texture = texture to be assigned 652 +/ 653 void setTextureUnit(int textureUnit, Texture texture) 654 { 655 glActiveTexture(GL_TEXTURE0 + textureUnit); 656 glBindTexture(texture.m_type, texture.m_name); 657 }