1 /* 2 ******************************************************************************************* 3 * Dgame (a D game framework) - Copyright (c) Randy Schütt 4 * 5 * This software is provided 'as-is', without any express or implied warranty. 6 * In no event will the authors be held liable for any damages arising from 7 * the use of this software. 8 * 9 * Permission is granted to anyone to use this software for any purpose, 10 * including commercial applications, and to alter it and redistribute it 11 * freely, subject to the following restrictions: 12 * 13 * 1. The origin of this software must not be misrepresented; you must not claim 14 * that you wrote the original software. If you use this software in a product, 15 * an acknowledgment in the product documentation would be appreciated but is 16 * not required. 17 * 18 * 2. Altered source versions must be plainly marked as such, and must not be 19 * misrepresented as being the original software. 20 * 21 * 3. This notice may not be removed or altered from any source distribution. 22 ******************************************************************************************* 23 */ 24 module Dgame.Graphics.Texture; 25 26 private { 27 import std.exception : enforce; 28 29 import derelict.opengl3.gl; 30 31 import Dgame.Internal.Log; 32 import Dgame.Internal.Unique; 33 import Dgame.Internal.Unique; 34 35 import Dgame.Math.Rect; 36 import Dgame.Graphics.Color; 37 } 38 39 /** 40 * Format a Texture.Format into the related bit count. 41 * If the format is not supported, it returns 0. 42 * 43 * Examples: 44 * --- 45 * assert(formatToBits(Texture.Format.RGBA) == 32); 46 * assert(formatToBits(Texture.Format.RGB) == 24); 47 * assert(formatToBits(Texture.Format.BGRA) == 32); 48 * assert(formatToBits(Texture.Format.BGR) == 24); 49 * --- 50 */ 51 ubyte formatToBits(Texture.Format fmt) pure nothrow { 52 switch (fmt) { 53 case Texture.Format.RGB: 54 case Texture.Format.BGR: 55 return 24; 56 case Texture.Format.RGBA: 57 case Texture.Format.BGRA: 58 return 32; 59 default: return 0; 60 } 61 } unittest { 62 assert(Texture.Format.RGB.formatToBits() == 24); 63 assert(Texture.Format.BGR.formatToBits() == 24); 64 assert(Texture.Format.RGBA.formatToBits() == 32); 65 assert(Texture.Format.BGRA.formatToBits() == 32); 66 } 67 68 /** 69 * Format a bit count into the related Texture.Format. 70 * If no bit count is supported, it returns Texture.Format.None. 71 * 72 * Examples: 73 * --- 74 * assert(bitsToFormat(32) == Texture.Format.RGBA); 75 * assert(bitsToFormat(24) == Texture.Format.RGB); 76 * assert(bitsToFormat(32, true) == Texture.Format.BGRA); 77 * assert(bitsToFormat(24, true) == Texture.Format.BGR); 78 * --- 79 */ 80 Texture.Format bitsToFormat(ubyte bits, bool reverse = false) pure nothrow { 81 switch (bits) { 82 case 32: return !reverse ? Texture.Format.RGBA : Texture.Format.BGRA; 83 case 24: return !reverse ? Texture.Format.RGB : Texture.Format.BGR; 84 case 16: return Texture.Format.RGBA16; 85 case 8: return Texture.Format.RGBA8; 86 default: return Texture.Format.None; 87 } 88 } unittest { 89 assert(bitsToFormat(8) == Texture.Format.RGBA8); 90 assert(bitsToFormat(16) == Texture.Format.RGBA16); 91 assert(bitsToFormat(24) == Texture.Format.RGB); 92 assert(bitsToFormat(32) == Texture.Format.RGBA); 93 assert(bitsToFormat(24, true) == Texture.Format.BGR); 94 assert(bitsToFormat(32, true) == Texture.Format.BGRA); 95 } 96 97 /** 98 * Switch/Reverse Texture.Format. 99 * 100 * Examples: 101 * --- 102 * assert(switchFormat(Texture.Format.RGB) == Texture.Format.BGR); 103 * assert(switchFormat(Texture.Format.RGB, true) == Texture.Format.BGRA); 104 * assert(switchFormat(Texture.Format.RGBA) == Texture.Format.BGRA); 105 * assert(switchFormat(Texture.Format.RGBA, true) == Texture.Format.BGRA); 106 * --- 107 */ 108 Texture.Format switchFormat(Texture.Format fmt, bool alpha = false) pure nothrow { 109 switch (fmt) { 110 case Texture.Format.RGB: 111 if (alpha) goto case Texture.Format.RGBA; 112 return Texture.Format.BGR; 113 case Texture.Format.BGR: 114 if (alpha) goto case Texture.Format.BGRA; 115 return Texture.Format.RGB; 116 case Texture.Format.RGBA: return Texture.Format.BGRA; 117 case Texture.Format.BGRA: return Texture.Format.RGBA; 118 default: return fmt; 119 } 120 } 121 122 /** 123 * Choose the right compress format for the given Texture.Format. 124 * 125 * See: Texture.Format enum 126 */ 127 Texture.Format compressFormat(Texture.Format fmt) pure nothrow { 128 switch (fmt) { 129 case Texture.Format.RGB: return Texture.Format.CompressedRGB; 130 case Texture.Format.RGBA: return Texture.Format.CompressedRGBA; 131 case Texture.Format.CompressedRGB: 132 case Texture.Format.CompressedRGBA: 133 return fmt; 134 default: return Texture.Format.None; 135 } 136 } 137 138 private GLuint*[] _TexFinalizer; 139 140 static ~this() { 141 debug Log.info("Finalize Texture (%d)", _TexFinalizer.length); 142 143 for (size_t i = 0; i < _TexFinalizer.length; i++) { 144 if (_TexFinalizer[i] && *_TexFinalizer[i] != 0) { 145 debug Log.info(" -> Texture finalized: %d", i); 146 147 glDeleteTextures(1, _TexFinalizer[i]); 148 } 149 } 150 151 _TexFinalizer = null; 152 153 debug Log.info(" >> Texture Finalized"); 154 } 155 156 /** 157 * A Texture is a 2 dimensional pixel reprasentation. 158 * It is a wrapper of an OpenGL Texture. 159 * 160 * Author: rschuett 161 */ 162 class Texture { 163 /** 164 * Supported Texture Format 165 */ 166 enum Format { 167 None = 0, /// Take this if you want to declare that you give no Format. 168 RGB = GL_RGB, /// Alias for GL_RGB 169 RGBA = GL_RGBA, /// Alias for GL_RGBA 170 BGR = GL_BGR, /// Alias for GL_BGR 171 BGRA = GL_BGRA, /// Alias for GL_BGRA 172 RGBA16 = GL_RGBA16, /// 16 Bit RGBA Format 173 RGBA8 = GL_RGBA8, /// 8 Bit RGBA Format 174 Alpha = GL_ALPHA, /// Alias for GL_ALPHA 175 Luminance = GL_LUMINANCE, /// Alias for GL_LUMINANCE 176 LuminanceAlpha = GL_LUMINANCE_ALPHA, /// Alias for GL_LUMINANCE_ALPHA 177 CompressedRGB = GL_COMPRESSED_RGB, /// Compressed RGB 178 CompressedRGBA = GL_COMPRESSED_RGBA /// Compressed RGBA 179 } 180 181 /** 182 * Compression modes 183 */ 184 enum Compression { 185 None, /// No compression 186 DontCare = GL_DONT_CARE, /// The OpenGL implementation decide on their own 187 Fastest = GL_FASTEST, /// Fastest compression 188 Nicest = GL_NICEST /// Nicest but slowest mode of compression 189 } 190 191 private: 192 GLuint _texId; 193 194 ushort _width; 195 ushort _height; 196 ubyte _depth; 197 198 bool _isSmooth; 199 bool _isRepeated; 200 201 Format _format; 202 Compression _comp; 203 204 public: 205 final: 206 /** 207 * CTor 208 */ 209 this() { 210 glGenTextures(1, &this._texId); 211 212 _TexFinalizer ~= &this._texId; 213 214 this.bind(); 215 } 216 217 /** 218 * Postblit 219 */ 220 this(const Texture tex, Format t_fmt = Format.None) { 221 const uint msize = tex.width * tex.height * (tex.depth / 8); 222 unique_ptr!(void) mem = allocate_unique!(void)(msize); 223 224 void[] memory = tex.getMemory(mem[0 .. msize]); 225 enforce(memory !is null, "Cannot a texture with no memory."); 226 this.loadFromMemory(&memory[0], tex.width, tex.height, tex.depth, t_fmt ? t_fmt : tex.getFormat()); 227 } 228 229 /** 230 * DTor 231 */ 232 ~this() { 233 this.free(); 234 } 235 236 /** 237 * Free / Delete the Texture & Memory 238 * After this call, the Pixel data is invalid. 239 */ 240 void free() { 241 if (this._texId != 0) { 242 debug Log.info("Destroy texture"); 243 glDeleteTextures(1, &this._texId); 244 245 this._texId = 0; 246 this._format = Format.None; 247 } 248 } 249 250 /** 251 * Returns the currently bound texture id. 252 */ 253 static GLint currentlyBound() { 254 GLint current; 255 glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t); 256 257 return current; 258 } 259 260 /** 261 * Returns the Texture Id. 262 */ 263 @property 264 GLuint Id() const pure nothrow { 265 return this._texId; 266 } 267 268 /** 269 * Returns if the texture is used. 270 */ 271 bool isValid() const pure nothrow { 272 return this._texId != 0; 273 } 274 275 /** 276 * Returns the width of this Texture 277 */ 278 @property 279 ushort width() const pure nothrow { 280 return this._width; 281 } 282 283 /** 284 * Returns the height of this Texture. 285 */ 286 @property 287 ushort height() const pure nothrow { 288 return this._height; 289 } 290 291 /** 292 * Returns the depth. May often 24 or 32. 293 */ 294 @property 295 ubyte depth() const pure nothrow { 296 return this._depth; 297 } 298 299 /** 300 * Returns the Format. 301 * 302 * See: Format enum. 303 */ 304 Format getFormat() const pure nothrow { 305 return this._format; 306 } 307 308 /** 309 * Binds this Texture. 310 * Means this Texture is now activated. 311 */ 312 void bind() const { 313 glBindTexture(GL_TEXTURE_2D, this._texId); 314 } 315 316 /** 317 * Binds this Texture. 318 * Means this Texture is now deactivated. 319 */ 320 void unbind() const { 321 glBindTexture(GL_TEXTURE_2D, 0); 322 } 323 324 /** 325 * Returns true, if this Texture is currently activated. 326 */ 327 bool isCurrentlyBound() const { 328 return Texture.currentlyBound() == this._texId; 329 } 330 331 /** 332 * Set smooth filter for the (next) load. 333 */ 334 void setSmooth(bool enable) pure nothrow { 335 this._isSmooth = enable; 336 } 337 338 /** 339 * Returns if smooth filter are activated. 340 */ 341 bool isSmooth() const pure nothrow { 342 return this._isSmooth; 343 } 344 345 /** 346 * Set repeating for the (next) load. 347 **/ 348 void setRepeat(bool repeat) { 349 if (repeat != this._isRepeated) { 350 this._isRepeated = repeat; 351 352 if (this._texId != 0) { 353 this.bind(); 354 355 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 356 this._isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 357 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 358 this._isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 359 } 360 } 361 } 362 363 /** 364 * Returns if repeating is enabled. 365 */ 366 bool isRepeated() const pure nothrow { 367 return this._isRepeated; 368 } 369 370 /** 371 * (Re)Set the compression mode. 372 * 373 * See: Compression enum 374 */ 375 void setCompression(Compression comp) pure nothrow { 376 this._comp = comp; 377 } 378 379 /** 380 * Returns the current Compression mode. 381 * 382 * See: Compression enum 383 */ 384 Compression getCompression() const pure nothrow { 385 return this._comp; 386 } 387 388 /** 389 * Checks whether the current Texture is compressed or not. 390 */ 391 bool isCompressed() const { 392 this.bind(); 393 394 GLint compressed; 395 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed); 396 397 return compressed != 1; 398 } 399 400 /** 401 * Load from memory. 402 */ 403 void loadFromMemory(void* memory, ushort width, ushort height, ubyte depth, Format fmt = Format.None) in { 404 assert(width != 0 && height != 0, "Width and height cannot be 0."); 405 assert(depth >= 8 || fmt != Format.None, "Need a depth or a format."); 406 } body { 407 /// Possible speedup because 'glTexSubImage2D' 408 /// is often faster than 'glTexImage2D'. 409 if (width == this.width 410 && height == this.height 411 && (fmt == Format.None || fmt == this._format)) 412 { 413 this.update(memory, null); 414 return; 415 } 416 417 this._format = fmt == Format.None ? bitsToFormat(depth) : fmt; 418 enforce(this._format != Format.None, "Need Texture.Format or depth > 24"); 419 depth = depth < 8 ? formatToBits(this._format) : depth; 420 421 this.bind(); 422 423 Format format = Format.None; 424 // Compression 425 if (this._comp != Compression.None) { 426 glHint(GL_TEXTURE_COMPRESSION_HINT, this._comp); 427 format = compressFormat(this._format); 428 } 429 430 glTexImage2D(GL_TEXTURE_2D, 0, format == Format.None ? depth / 8 : format, 431 width, height, 0, this._format, GL_UNSIGNED_BYTE, memory); 432 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 433 this._isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 434 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 435 this._isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 436 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 437 this._isSmooth ? GL_LINEAR : GL_NEAREST); 438 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 439 this._isSmooth ? GL_LINEAR : GL_NEAREST); 440 glGenerateMipmap(GL_TEXTURE_2D); // We want MipMaps 441 442 debug { 443 if (format != Format.None) { 444 if (!this.isCompressed()) 445 Log.info("\tTexture wurde nicht komprimiert : %s : %s", cast(Format) format, this._format); 446 } 447 } 448 449 this._width = width; 450 this._height = height; 451 this._depth = depth; 452 } 453 454 /** 455 * Set a colorkey. 456 */ 457 void setColorkey(ref const Color colorkey) { 458 // Get the pixel memory 459 const uint msize = this._width * this._height * (this._depth / 8); 460 unique_ptr!(void) mem = allocate_unique!(void)(msize); 461 void[] memory = this.getMemory(mem[0 .. msize]); 462 enforce(memory !is null, "Cannot set a colorkey for an empty Texture."); 463 // Go through pixels 464 for (uint i = 0; i < memory.length; ++i) { 465 // Get pixel colors 466 uint* color = cast(uint*) &memory[i]; 467 // Color matches 468 if (color[0] == colorkey.red 469 && color[1] == colorkey.green 470 && color[2] == colorkey.blue 471 && (0 == colorkey.alpha || color[3] == colorkey.alpha)) 472 { 473 // Make transparent 474 color[0] = 255; 475 color[1] = 255; 476 color[2] = 255; 477 color[3] = 0; 478 } 479 } 480 481 this.update(&memory[0]); 482 } 483 484 /** 485 * Rvalue version 486 */ 487 void setColorkey(const Color colorkey) { 488 this.setColorkey(colorkey); 489 } 490 491 /** 492 * Returns the pixel of this Texture or null if this Texture isn't valid. 493 * If memory is not null and has the same width and height as the Texture, 494 * it is used to store the pixel data. 495 * Otherwise it <b>allocates</b> GC memory. 496 */ 497 void[] getMemory(void[] memory = null) const { 498 const uint msize = this._width * this._height * (this._depth / 8); 499 if (msize == 0) { 500 debug Log.info("@Texture.GetPixels: Null Pixel"); 501 502 return null; 503 } 504 505 this.bind(); 506 507 if (memory is null) 508 memory = new void[msize]; 509 else if (memory.length < msize) 510 throw new Exception("Your given memory is to short."); 511 512 glGetTexImage(GL_TEXTURE_2D, 0, this._format, GL_UNSIGNED_BYTE, memory.ptr); 513 514 return memory; 515 } 516 517 /** 518 * Rvalue version 519 */ 520 Texture subTexture(const ShortRect rect) const { 521 return this.subTexture(rect); 522 } 523 524 /** 525 * Returns a subTexture of this Texture. 526 */ 527 Texture subTexture(ref const ShortRect rect) const in { 528 assert(rect.x <= this._width, "rect.x is out of range."); 529 assert(rect.y <= this._height, "rect.y is out of range."); 530 assert(rect.width <= this._width, "rect.width is out of range."); 531 assert(rect.height <= this._height, "rect.height is out of range."); 532 assert(rect.x >= 0, "rect.x is negative"); 533 assert(rect.y >= 0, "rect.y is negative."); 534 assert(rect.width >= 0, "rect.width is negative."); 535 assert(rect.height >= 0, "rect.height is negative."); 536 } body { 537 if (this._format == Format.None) 538 return null; 539 540 const ubyte bits = this._depth / 8; 541 const uint msize = this._width * this._height * bits; 542 unique_ptr!(void) mem = allocate_unique!(void)(msize); 543 void[] memory = this.getMemory(mem[0 .. msize]); 544 545 const uint[2] pitch = [this._width * bits, rect.width * bits]; 546 const uint diff = pitch[0] - pitch[1]; 547 548 uint from = pitch[0] * rect.y + rect.x * bits; 549 uint too = from + (rect.height * pitch[0]); 550 551 unique_ptr!(ubyte) buffer = allocate_unique!(ubyte)(msize); 552 // ubyte[] buffer = new ubyte[msize]; 553 for (uint i = from, j = 0; i < too - pitch[1]; i += pitch[1], j += pitch[1]) { 554 buffer[j .. j + pitch[1]] = cast(ubyte[]) memory[i .. i + pitch[1]]; 555 i += diff; 556 } 557 558 Texture tex = new Texture(); 559 tex.loadFromMemory(buffer.ptr, rect.width, rect.height, 0, this._format); 560 561 return tex; 562 } 563 564 /** 565 * Copy another Texture to this. 566 * The second parameter is a pointer to the destination rect. 567 * Is it is null this means the whole tex is copied. 568 */ 569 void copy(const Texture tex, const ShortRect* rect = null) const in { 570 assert(tex !is null, "Cannot copy null Texture."); 571 assert(this._width != 0 && this._height != 0, "width or height is 0."); 572 } body { 573 const ubyte bits = tex.depth / 8; 574 const uint msize = tex.width * tex.height * bits; 575 576 unique_ptr!(void) mem = allocate_unique!(void)(msize); 577 void[] memory = tex.getMemory(mem[0 .. msize]); 578 579 this.update(memory.ptr, rect, tex.getFormat()); 580 } 581 582 /** 583 * Update the pixel data of this Texture. 584 * The second parameter is a pointer to the area which is updated. 585 * If it is null (default) the whole Texture will be updated. 586 * The third parameter is the format of the pixels. 587 */ 588 void update(const void* memory, const ShortRect* rect = null, Format fmt = Format.None) const in { 589 assert(memory !is null, "Pixels is null."); 590 assert(this._width != 0 && this._height != 0, "width or height is 0."); 591 } body { 592 ushort width, height; 593 short x, y; 594 595 if (rect !is null) { 596 enforce(rect.width <= this._width && rect.height <= this._height, 597 "Rect is greater as the Texture."); 598 enforce(rect.x < this._width && rect.y < this._height, 599 "x or y of the Rect is greater as the Texture."); 600 601 width = rect.width; 602 height = rect.height; 603 604 x = rect.x; 605 y = rect.y; 606 } else { 607 width = this._width; 608 height = this._height; 609 610 x = y = 0; 611 } 612 613 this.bind(); 614 615 glTexSubImage2D(GL_TEXTURE_2D, 0, 616 x, y, width, height, 617 (fmt == Format.None ? this._format : fmt), 618 GL_UNSIGNED_BYTE, memory); 619 } 620 }