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, &current);
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 }