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.Graphic.Texture; 25 26 private: 27 28 import derelict.opengl3.gl; 29 30 import Dgame.Math.Rect; 31 32 import Dgame.Graphic.Surface; 33 import Dgame.Graphic.Color; 34 35 import Dgame.Internal.m3; 36 37 public: 38 39 /** 40 * A Texture is a 2 dimensional pixel reprasentation. 41 * It is a wrapper of an OpenGL Texture. 42 * 43 * Author: Randy Schuett (rswhite4@googlemail.com) 44 */ 45 struct Texture { 46 /** 47 * Supported Texture Format 48 */ 49 enum Format { 50 None = 0, /// Take this if you want to declare that you give no Format. 51 RGB = GL_RGB, /// Alias for GL_RGB 52 RGBA = GL_RGBA, /// Alias for GL_RGBA 53 BGR = GL_BGR, /// Alias for GL_BGR 54 BGRA = GL_BGRA, /// Alias for GL_BGRA 55 RGB8 = GL_RGB8, /// 8 Bit RGB Format 56 RGB16 = GL_RGB16, /// 16 Bit RGB Format 57 RGBA8 = GL_RGBA8, /// 8 Bit RGBA Format 58 RGBA16 = GL_RGBA16, /// 16 Bit RGBA Format 59 Alpha = GL_ALPHA, /// Alias for GL_ALPHA 60 Luminance = GL_LUMINANCE, /// Alias for GL_LUMINANCE 61 LuminanceAlpha = GL_LUMINANCE_ALPHA /// Alias for GL_LUMINANCE_ALPHA 62 } 63 64 private: 65 uint _texId; 66 67 uint _width; 68 uint _height; 69 ubyte _depth; 70 71 bool _isSmooth; 72 bool _isRepeated; 73 74 Format _format; 75 76 @nogc 77 void _init() nothrow { 78 if (_texId == 0) { 79 glGenTextures(1, &_texId); 80 } 81 } 82 83 public: 84 /** 85 * CTor 86 */ 87 @nogc 88 this(void* memory, uint width, uint height, Format fmt) nothrow { 89 this.loadFromMemory(memory, width, height, fmt); 90 } 91 92 /** 93 * CTor 94 */ 95 @nogc 96 this(const Surface srfc, Format fmt = Format.None) nothrow { 97 this.loadFrom(srfc, fmt); 98 } 99 100 /** 101 * Postblit is disabled 102 */ 103 @disable 104 this(this); 105 106 /** 107 * DTor 108 */ 109 @nogc 110 ~this() nothrow { 111 if (_texId != 0) 112 glDeleteTextures(1, &_texId); 113 } 114 115 /** 116 * Returns the currently bound texture id. 117 */ 118 @nogc 119 static int currentlyBound() nothrow { 120 int current; 121 glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t); 122 123 return current; 124 } 125 126 /** 127 * Returns the Texture Id. 128 */ 129 @property 130 @nogc 131 uint id() const pure nothrow { 132 return _texId; 133 } 134 135 /** 136 * Returns if the texture is used. 137 */ 138 @nogc 139 bool isValid() const pure nothrow { 140 return _texId != 0; 141 } 142 143 /** 144 * Returns the width of this Texture 145 */ 146 @property 147 @nogc 148 uint width() const pure nothrow { 149 return _width; 150 } 151 152 /** 153 * Returns the height of this Texture. 154 */ 155 @property 156 @nogc 157 uint height() const pure nothrow { 158 return _height; 159 } 160 161 /** 162 * Returns the depth. May often 24 or 32. 163 */ 164 @property 165 @nogc 166 ubyte depth() const pure nothrow { 167 return _depth; 168 } 169 170 /** 171 * Returns the Format. 172 * 173 * See: Format enum. 174 */ 175 @property 176 @nogc 177 Format format() const pure nothrow { 178 return _format; 179 } 180 181 /** 182 * Binds this Texture. 183 * Means this Texture is now activated. 184 */ 185 @nogc 186 void bind() const nothrow { 187 glBindTexture(GL_TEXTURE_2D, _texId); 188 } 189 190 /** 191 * Binds this Texture. 192 * Means this Texture is now deactivated. 193 */ 194 @nogc 195 void unbind() const nothrow { 196 glBindTexture(GL_TEXTURE_2D, 0); 197 } 198 199 /** 200 * Returns true, if this Texture is currently activated. 201 */ 202 @nogc 203 bool isCurrentlyBound() const nothrow { 204 return Texture.currentlyBound() == _texId; 205 } 206 207 /** 208 * Set smooth filter. 209 */ 210 @nogc 211 void setSmooth(bool smooth) nothrow { 212 if (smooth != _isSmooth) { 213 _isSmooth = smooth; 214 215 if (_texId != 0) { 216 this.bind(); 217 218 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST); 219 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST); 220 221 this.unbind(); 222 } 223 } 224 } 225 226 /** 227 * Returns if smooth filter are activated. 228 */ 229 @nogc 230 bool isSmooth() const pure nothrow { 231 return _isSmooth; 232 } 233 234 /** 235 * Set repeating. 236 **/ 237 @nogc 238 void setRepeat(bool repeat) nothrow { 239 if (repeat != _isRepeated) { 240 _isRepeated = repeat; 241 242 if (_texId != 0) { 243 this.bind(); 244 245 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 246 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 247 248 this.unbind(); 249 } 250 } 251 } 252 253 /** 254 * Returns if repeating is enabled. 255 */ 256 @nogc 257 bool isRepeated() const pure nothrow { 258 return _isRepeated; 259 } 260 261 /** 262 * Load from Surface 263 */ 264 @nogc 265 void loadFrom(const Surface srfc, Format fmt = Format.None) nothrow { 266 assert(srfc.isValid(), "Cannot load invalid Surface"); 267 268 if (fmt == Format.None) { 269 fmt = bitsToFormat(srfc.bits); 270 version (BigEndian) fmt = switchFormat(fmt); 271 } 272 273 this.loadFromMemory(srfc.pixels, srfc.width, srfc.height, fmt); 274 } 275 276 /** 277 * Load from memory. 278 */ 279 @nogc 280 void loadFromMemory(const void* memory, uint width, uint height, Format fmt) nothrow { 281 _init(); 282 283 assert(width != 0 && height != 0, "Width and height cannot be 0."); 284 assert(fmt != Format.None, "Need a valid format."); 285 286 _format = fmt == Format.None ? bitsToFormat(depth) : fmt; 287 assert(_format != Format.None, "Need Texture.Format or depth > 24"); 288 289 if (width == _width && height == _height) 290 this.update(memory); 291 else { 292 _depth = formatToBits(_format); 293 294 this.bind(); 295 296 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 297 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE); 298 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST); 299 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST); 300 glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); 301 glTexImage2D(GL_TEXTURE_2D, 0, depth / 8, width, height, 0, _format, GL_UNSIGNED_BYTE, memory); 302 303 _width = width; 304 _height = height; 305 306 this.unbind(); 307 } 308 } 309 310 /** 311 * Set a colorkey. 312 */ 313 @nogc 314 void setColorkey(const Color4b colorkey) nothrow { 315 if (_texId == 0) 316 return; 317 318 // Get the pixel memory 319 immutable size_t msize = this.getByteSize(); 320 void[] mem = make!(void[])(msize); 321 scope(exit) unmake(mem); 322 323 void[] memory = this.getPixels(mem[0 .. msize]); 324 325 // Go through pixels 326 for (uint i = 0; i < memory.length; ++i) { 327 // Get pixel colors 328 uint* color = cast(uint*) &memory[i]; 329 // Color matches 330 if (color[0] == colorkey.red 331 && color[1] == colorkey.green 332 && color[2] == colorkey.blue 333 && (0 == colorkey.alpha || color[3] == colorkey.alpha)) 334 { 335 // Make transparent 336 color[0] = 255; 337 color[1] = 255; 338 color[2] = 255; 339 color[3] = 0; 340 } 341 } 342 343 this.update(memory.ptr); 344 } 345 346 /** 347 * Returns the byte size of the Texture 348 */ 349 @nogc 350 size_t getByteSize() const pure nothrow { 351 if (_depth > 0) 352 return _width * _height * (_depth / 8); 353 return 0; 354 } 355 356 /** 357 * Returns the pixel data of this Texture or null if this Texture isn't valid. 358 * pixels is used to store the pixel data. 359 */ 360 @nogc 361 void[] getPixels(void[] pixels) const nothrow { 362 immutable size_t msize = this.getByteSize(); 363 if (msize == 0) 364 return null; 365 366 assert(pixels.length >= msize, "Your given memory is too short."); 367 368 this.bind(); 369 370 glGetTexImage(GL_TEXTURE_2D, 0, _format, GL_UNSIGNED_BYTE, pixels.ptr); 371 372 this.unbind(); 373 374 return pixels; 375 } 376 377 /** 378 * Returns the pixel of this Texture or null if this Texture isn't valid. 379 * 380 * Note: this method <b>allocates</b> GC memory. 381 */ 382 void[] getPixels() const nothrow { 383 immutable size_t msize = this.getByteSize(); 384 if (msize == 0) 385 return null; 386 387 void[] pixels = new void[msize]; 388 389 return this.getPixels(pixels); 390 } 391 392 /** 393 * Update the pixel data of this Texture. 394 * The second parameter is a pointer to the area which is updated. 395 * If it is null (default) the whole Texture will be updated. 396 * The third parameter is the format of the pixels. 397 */ 398 @nogc 399 void update(const void* memory, const Rect* rect = null, Format fmt = Format.None) const nothrow { 400 if (_texId == 0) 401 return; 402 403 assert(memory, "Invalid memory"); 404 assert(_width != 0 && _height != 0, "width or height is 0."); 405 406 fmt = fmt == Format.None ? _format : fmt; 407 408 uint width = _width, height = _height; 409 int x = 0, y = 0; 410 411 if (rect) { 412 assert(rect.width <= _width && rect.height <= _height, "Rect is greater as the Texture."); 413 assert(rect.x < _width && rect.y < _height, "x or y of the Rect is greater as the Texture."); 414 415 width = rect.width; 416 height = rect.height; 417 418 x = rect.x; 419 y = rect.y; 420 } 421 422 this.bind(); 423 424 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, fmt, GL_UNSIGNED_BYTE, memory); 425 426 this.unbind(); 427 } 428 } 429 430 /** 431 * Format a Texture.Format into the related bit count. 432 * If the format is not supported, it returns 0. 433 * 434 * Examples: 435 * --- 436 * assert(formatToBits(Texture.Format.RGBA) == 32); 437 * assert(formatToBits(Texture.Format.RGB) == 24); 438 * assert(formatToBits(Texture.Format.BGRA) == 32); 439 * assert(formatToBits(Texture.Format.BGR) == 24); 440 * --- 441 */ 442 @nogc 443 ubyte formatToBits(Texture.Format fmt) pure nothrow { 444 switch (fmt) { 445 case Texture.Format.RGB: 446 case Texture.Format.BGR: 447 return 24; 448 case Texture.Format.RGBA: 449 case Texture.Format.BGRA: 450 return 32; 451 case Texture.Format.RGBA16: return 16; 452 case Texture.Format.RGBA8: return 8; 453 default: return 0; 454 } 455 } 456 457 unittest { 458 assert(Texture.Format.RGB.formatToBits() == 24); 459 assert(Texture.Format.BGR.formatToBits() == 24); 460 assert(Texture.Format.RGBA.formatToBits() == 32); 461 assert(Texture.Format.BGRA.formatToBits() == 32); 462 } 463 464 /** 465 * Format a bit count into the related Texture.Format. 466 * If no bit count is supported, it returns Texture.Format.None. 467 * 468 * Examples: 469 * --- 470 * assert(bitsToFormat(32) == Texture.Format.RGBA); 471 * assert(bitsToFormat(24) == Texture.Format.RGB); 472 * assert(bitsToFormat(32, true) == Texture.Format.BGRA); 473 * assert(bitsToFormat(24, true) == Texture.Format.BGR); 474 * --- 475 */ 476 @nogc 477 Texture.Format bitsToFormat(ubyte bits, bool reverse = false) pure nothrow { 478 switch (bits) { 479 case 32: return !reverse ? Texture.Format.RGBA : Texture.Format.BGRA; 480 case 24: return !reverse ? Texture.Format.RGB : Texture.Format.BGR; 481 case 16: return Texture.Format.RGBA16; 482 case 8: return Texture.Format.RGBA8; 483 default: return Texture.Format.None; 484 } 485 } 486 487 unittest { 488 assert(bitsToFormat(8) == Texture.Format.RGBA8); 489 assert(bitsToFormat(16) == Texture.Format.RGBA16); 490 assert(bitsToFormat(24) == Texture.Format.RGB); 491 assert(bitsToFormat(32) == Texture.Format.RGBA); 492 assert(bitsToFormat(24, true) == Texture.Format.BGR); 493 assert(bitsToFormat(32, true) == Texture.Format.BGRA); 494 } 495 496 /** 497 * Switch/Reverse Texture.Format. 498 * 499 * Examples: 500 * --- 501 * assert(switchFormat(Texture.Format.RGB) == Texture.Format.BGR); 502 * assert(switchFormat(Texture.Format.RGB, true) == Texture.Format.BGRA); 503 * assert(switchFormat(Texture.Format.RGBA) == Texture.Format.BGRA); 504 * assert(switchFormat(Texture.Format.RGBA, true) == Texture.Format.BGRA); 505 * --- 506 */ 507 @nogc 508 Texture.Format switchFormat(Texture.Format fmt, bool alpha = false) pure nothrow { 509 switch (fmt) { 510 case Texture.Format.RGB: 511 if (alpha) goto case Texture.Format.RGBA; 512 return Texture.Format.BGR; 513 case Texture.Format.BGR: 514 if (alpha) goto case Texture.Format.BGRA; 515 return Texture.Format.RGB; 516 case Texture.Format.RGBA: return Texture.Format.BGRA; 517 case Texture.Format.BGRA: return Texture.Format.RGBA; 518 default: return fmt; 519 } 520 }