really compiles! ;-)
[dd2d.git] / glutils.d
blobaf5f3627c1a59fa6b6e7edee1895091169bcf28f
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module glutils is aliced;
19 private:
20 import iv.glbinds;
21 import iv.vfs;
22 import arsd.color;
23 import arsd.png;
24 import iv.jpegd;
26 import wadarc;
29 // ////////////////////////////////////////////////////////////////////////// //
30 __gshared bool glutilsShowShaderWarnings = false; // shut up!
33 __gshared GLuint glLastUsedTexture = 0;
35 public void useTexture (GLuint tid) {
36 pragma(inline, true);
37 if (glLastUsedTexture != tid) {
38 glLastUsedTexture = tid;
39 glBindTexture(GL_TEXTURE_2D, tid);
43 public void useTexture (Texture tex) { pragma(inline, true); useTexture(tex !is null ? tex.tid : 0); }
47 public TrueColorImage loadPngFile (string fname) {
48 auto fl = VFile(fname);
49 auto sz = fl.size;
50 if (sz < 4 || sz > 32*1024*1024) throw new Exception("invalid png file size: '"~fname~"'");
51 if (sz == 0) return null;
52 auto res = new ubyte[](cast(uint)sz);
53 if (fl.rawRead(res[]).length != res.length) throw new Exception("error reading png file '"~fname~"'");
54 return imageFromPng(readPng(res)).getAsTrueColorImage;
58 // ////////////////////////////////////////////////////////////////////////// //
59 class OpenGLObject {
60 abstract @property uint id () const pure nothrow @nogc;
62 final void activate () nothrow @nogc {
63 if (gloSP >= gloStack.length) assert(0, "glo stack overflow");
64 gloStack.ptr[gloSP++] = this;
65 activateObj();
68 final void deactivate () nothrow @nogc {
69 foreach_reverse (usize idx; 0..gloStack.length) {
70 if (gloStack.ptr[idx] is this) {
71 // find previous object of this type
72 foreach_reverse (usize pidx; 0..idx) {
73 if (typeid(gloStack.ptr[pidx]) is typeid(gloStack.ptr[idx])) {
74 gloStack.ptr[pidx].activateObj();
75 removeFromStack(pidx);
76 return;
79 deactivateObj();
80 removeFromStack(idx);
81 return;
84 assert(0, "trying to deactivate inactive object");
87 protected:
88 abstract void activateObj () nothrow @nogc;
89 abstract void deactivateObj () nothrow @nogc;
93 __gshared OpenGLObject[1024] gloStack;
94 __gshared uint gloSP = 0;
97 public void gloStackClear () nothrow @nogc {
98 bindTexture(0);
99 //glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
100 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
101 glUseProgram(0);
105 void removeFromStack (usize idx) nothrow @nogc {
106 if (idx >= gloSP) return;
107 if (idx != gloSP-1) {
108 import core.stdc.string : memmove;
109 memmove(gloStack.ptr+idx, gloStack.ptr+idx+1, (gloSP-idx-1)*gloStack[0].sizeof);
111 --gloSP;
115 // ////////////////////////////////////////////////////////////////////////// //
116 public final class Texture : OpenGLObject {
117 GLuint tid;
118 int width, height;
120 override @property uint id () const pure nothrow @nogc => tid;
122 // default: repeat, linear
123 enum Option : int {
124 Repeat,
125 Clamp,
126 ClampBorder,
127 Linear,
128 Nearest,
129 UByte,
130 Float, // create floating point texture
131 Depth, // FBO: attach depth buffer
132 FBO2, // create two FBO attaches
135 this (string fname, in Option[] opts...) { loadImage(fname, opts); }
136 this (int w, int h, in Option[] opts...) { createIntr(w, h, null, opts); }
137 this (TrueColorImage aimg, Option[] opts...) { createIntr(aimg.width, aimg.height, aimg, opts); }
138 ~this () { clear(); }
141 void clear () {
142 if (tid) {
143 //useTexture(tid);
144 bindTexture(tid);
145 glDeleteTextures(1, &tid);
146 //useTexture(0);
147 bindTexture(0);
148 tid = 0;
149 width = 0;
150 height = 0;
154 private static void processOpt (GLuint* wrapOpt, GLuint* filterOpt, GLuint* ttype, in Option[] opts...) {
155 foreach (immutable opt; opts) {
156 switch (opt) with (Option) {
157 case Repeat: *wrapOpt = GL_REPEAT; break;
158 case Clamp: *wrapOpt = GL_CLAMP_TO_EDGE; break;
159 case ClampBorder: *wrapOpt = GL_CLAMP_TO_BORDER; break;
160 case Linear: *filterOpt = GL_LINEAR; break;
161 case Nearest: *filterOpt = GL_NEAREST; break;
162 case UByte: *ttype = GL_UNSIGNED_BYTE; break;
163 case Float: *ttype = GL_FLOAT; break;
164 default:
169 void createIntr (int w, int h, TrueColorImage img, in Option[] opts...) {
170 import core.stdc.stdlib : malloc, free;
171 assert(w > 0);
172 assert(h > 0);
173 clear();
175 GLuint wrapOpt = GL_REPEAT;
176 GLuint filterOpt = GL_LINEAR;
177 GLuint ttype = GL_UNSIGNED_BYTE;
178 processOpt(&wrapOpt, &filterOpt, &ttype, opts);
180 glGenTextures(1, &tid);
181 //useTexture(tid);
182 auto oldtid = boundTexture;
183 bindTexture(tid);
184 scope(exit) bindTexture(oldtid);
185 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapOpt);
186 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapOpt);
187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterOpt);
188 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterOpt);
189 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
190 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
191 GLfloat[4] bclr = 0.0;
192 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
193 if (img !is null && img.width == w && img.height == h) {
194 // create straight from image
195 glTexImage2D(GL_TEXTURE_2D, 0, (ttype == GL_FLOAT ? GL_RGBA16F : GL_RGBA), w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
196 } else {
197 // create empty texture
198 ubyte* ptr = null;
199 scope(exit) if (ptr !is null) free(ptr);
201 import core.stdc.string : memset;
202 ptr = cast(ubyte*)malloc(w*h*4);
203 if (ptr !is null) memset(ptr, 0, w*h*4);
205 glTexImage2D(GL_TEXTURE_2D, 0, (ttype == GL_FLOAT ? GL_RGBA16F : GL_RGBA), w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
206 if (img !is null && img.width > 0 && img.height > 0) {
207 // setup from image
208 //TODO: dunno if it's ok to use images bigger than texture here
209 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
210 // the following is ok too
211 //bindTexture(0);
212 //glTextureSubImage2D(tid, 0, 0, 0, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
215 width = w;
216 height = h;
219 void setFromImage (TrueColorImage img, int x=0, int y=0) {
220 if (img is null || !tid || img.height < 1 || img.width < 1) return;
221 if (x >= width || y >= height) return;
222 if (x+img.width <= 0 || y+img.height <= 0) return; //TODO: overflow
223 if (x >= 0 && y >= 0 && x+img.width <= width && y+img.height <= height) {
224 // easy case, just copy it
225 glTextureSubImage2D(tid, 0, x, y, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
226 } else {
227 import core.stdc.stdlib : malloc, free;
228 import core.stdc.string : memset, memcpy;
229 // hard case, have to build the temp region
230 uint* src = cast(uint*)img.imageData.bytes.ptr;
231 // calc x skip and effective width
232 int rwdt = img.width;
233 if (x < 0) {
234 rwdt += x;
235 src -= x; // as `x` is negative here
236 x = 0;
238 if (x+rwdt > width) rwdt = width-x;
239 // calc y skip and effective height
240 int rhgt = img.height;
241 if (y < 0) {
242 rhgt += y;
243 src -= y*img.width; // as `y` is negative here
244 y = 0;
246 if (y+rhgt > height) rhgt = height-y;
247 assert(rwdt > 0 && rhgt > 0);
248 uint* ptr = null;
249 scope(exit) if (ptr !is null) free(ptr);
250 ptr = cast(uint*)malloc(rwdt*rhgt*4);
251 if (ptr is null) assert(0, "out of memory in `Texture.setFromImage()`");
252 // now copy
253 auto d = ptr;
254 foreach (immutable _; 0..rhgt) {
255 memcpy(d, src, rwdt*4);
256 src += img.width;
257 d += rwdt;
259 glTextureSubImage2D(tid, 0, x, y, rwdt, rhgt, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
263 void loadPng (VFile fl, in Option[] opts...) {
264 auto flsize = fl.size-fl.tell;
265 if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big");
266 auto data = new ubyte[](cast(uint)flsize);
267 fl.rawReadExact(data);
268 auto png = readPng(data);
269 auto ximg = imageFromPng(png).getAsTrueColorImage;
270 if (ximg is null) throw new Exception("png: wtf?!");
271 if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small");
272 createIntr(ximg.width, ximg.height, ximg, opts);
275 void loadJpeg (VFile fl, in Option[] opts...) {
276 auto jpg = readJpeg(fl).getAsTrueColorImage;
277 if (jpg.width < 1 || jpg.width > 32760) throw new Exception("invalid image width");
278 if (jpg.height < 1 || jpg.height > 32760) throw new Exception("invalid image height");
279 createIntr(jpg.width, jpg.height, jpg, opts);
282 void loadImage (string fname, in Option[] opts...) {
283 scope(failure) clear;
284 auto fl = VFile(fname);
285 scope(failure) fl.seek(0);
286 char[8] sign;
287 fl.seek(0);
288 fl.rawReadExact(sign[]);
289 fl.seek(0);
290 // png?
291 if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
292 loadPng(fl, opts);
293 return;
295 // jpeg?
296 int width, height, actual_comps;
297 if (sign[0..2] == "\xff\xd8" && detectJpeg(fl, width, height, actual_comps)) {
299 fl.seek(-2, Seek.End);
300 fl.rawReadExact(sign[0..2]);
301 fl.seek(0);
302 if (sign[0..2] == "\xff\xd9") {
303 loadJpeg(fl, opts);
304 return;
307 loadJpeg(fl, opts);
308 return;
310 throw new Exception("invalid texture image format");
313 protected:
314 override void activateObj () nothrow @nogc { /*if (tid)*/ bindTexture(tid); }
315 override void deactivateObj () nothrow @nogc { /*if (tid)*/ bindTexture(0); }
319 // ////////////////////////////////////////////////////////////////////////// //
320 public final class FBO : OpenGLObject {
321 int width;
322 int height;
323 GLuint fbo;
324 Texture tex, tex1;
325 Texture texdepth;
326 //Texture.Option[] xopts;
328 override @property uint id () const pure nothrow @nogc => fbo;
330 this (Texture atex) { createWithTexture(atex); }
332 this (int wdt, int hgt, Texture.Option[] opts...) {
333 //xopts = opts.dup;
334 createWithTexture(new Texture(wdt, hgt, opts), opts);
337 ~this () {
338 //FIXME: this may be wrong, as texture may be already destroyed (and it's wrong too); we need refcount for textures
339 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
340 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0);
341 glDeleteFramebuffersEXT(1, &fbo);
342 fbo = 0;
345 void clear () {
346 if (fbo) {
347 // detach texture
348 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
349 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0);
350 //glDeleteFramebuffersEXT(1, &fbo);
351 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
352 glDeleteFramebuffersEXT(1, &fbo);
353 fbo = 0;
355 if (tex !is null) tex.clear();
356 tex = null;
357 if (tex1 !is null) tex1.clear();
358 tex1 = null;
359 if (texdepth !is null) texdepth.clear();
360 texdepth = null;
363 protected:
364 override void activateObj () nothrow @nogc { /*if (fbo)*/ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); }
365 override void deactivateObj () nothrow @nogc { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); }
367 // this will deactivate current FBO!
369 void replaceTexture (Texture ntex) {
370 if (tex !is null) {
371 if (ntex !is null && ntex.tid == tex.tid) return;
372 } else {
373 if (ntex is null) return;
375 glGenFramebuffersEXT(1, &fbo);
376 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
377 scope(exit) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
378 // detach texture
379 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
380 glDeleteFramebuffersEXT(1, &fbo);
381 fbo = 0;
382 tex = ntex;
383 // attach texture
384 if (tex !is null) {
385 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex.tid, 0);
387 GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
388 if (status != GL_FRAMEBUFFER_COMPLETE_EXT) assert(0, "framebuffer fucked!");
394 private:
395 void createWithTexture (Texture atex, Texture.Option[] opts...) {
396 assert(atex !is null && atex.tid);
398 bool need2 = false;
399 foreach (immutable opt; opts) if (opt == Texture.Option.FBO2) { need2 = true; break; }
401 tex = atex;
402 glGenFramebuffersEXT(1, &fbo);
403 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
404 scope(exit) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
405 // attach 2D texture to this FBO
406 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex.tid, 0);
407 // GL_COLOR_ATTACHMENT0_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_STENCIL_ATTACHMENT_EXT
409 if (need2) {
410 tex1 = new Texture(tex.width, tex.height, opts);
411 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, tex1.tid, 0);
415 void createDepth () {
416 uint fboDepthId;
417 glGenRenderbuffersEXT(1, &fboDepthId);
418 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboDepthId);
419 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT/*24*/, atex.width, atex.height);
420 // attach depth buffer to FBO
421 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fboDepthId);
422 // kill it (we don't need it anymore)
423 glDeleteRenderbuffersEXT(1, &fboDepthId);
428 foreach (Texture.Option opt; opts) {
429 if (opt == Texture.Option.Depth) {
430 createDepth();
431 break;
435 //createDepth();
438 GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
439 if (status != GL_FRAMEBUFFER_COMPLETE_EXT) assert(0, "framebuffer fucked!");
441 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
443 width = tex.width;
444 height = tex.height;
449 // ////////////////////////////////////////////////////////////////////////// //
450 public struct SVec2I { int x, y; }
451 public struct SVec3I { int x, y, z; alias r = x; alias g = y; alias b = z; }
452 public struct SVec4I { int x, y, z, w; alias r = x; alias g = y; alias b = z; alias a = w; }
454 public struct SVec2F { float x, y; }
455 public struct SVec3F { float x, y, z; alias r = x; alias g = y; alias b = z; }
456 public struct SVec4F { float x, y, z, w; alias r = x; alias g = y; alias b = z; alias a = w; }
459 public final class Shader : OpenGLObject {
460 string shaderName;
461 GLuint prg = 0;
462 GLint[string] vars;
464 override @property uint id () const pure nothrow @nogc => prg;
466 this (string ashaderName, const(char)[] src) {
467 shaderName = ashaderName;
468 if (src.length > int.max) {
469 import core.stdc.stdio : printf;
470 printf("shader '%.*s' code too long!", cast(uint)ashaderName.length, ashaderName.ptr);
471 assert(0);
473 auto shaderId = glCreateShader(GL_FRAGMENT_SHADER);
474 auto sptr = src.ptr;
475 GLint slen = cast(int)src.length;
476 glShaderSource(shaderId, 1, &sptr, &slen);
477 glCompileShader(shaderId);
478 GLint success = 0;
479 glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
480 if (!success || glutilsShowShaderWarnings) {
481 import core.stdc.stdio : printf;
482 import core.stdc.stdlib : malloc, free;
483 GLint logSize = 0;
484 glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);
485 if (logSize > 0) {
486 auto logStrZ = cast(GLchar*)malloc(logSize);
487 glGetShaderInfoLog(shaderId, logSize, null, logStrZ);
488 printf("shader '%.*s' compilation messages:\n%s\n", cast(uint)ashaderName.length, ashaderName.ptr, logStrZ);
489 free(logStrZ);
492 if (!success) assert(0);
493 prg = glCreateProgram();
494 glAttachShader(prg, shaderId);
495 glLinkProgram(prg);
498 GLint varId(NT) (NT vname) if (is(NT == char[]) || is(NT == const(char)[]) || is(NT == immutable(char)[])) {
499 GLint id = -1;
500 if (vname.length > 0 && vname.length <= 128) {
501 if (auto vi = vname in vars) {
502 id = *vi;
503 } else {
504 char[129] buf = void;
505 buf[0..vname.length] = vname[];
506 buf[vname.length] = 0;
507 id = glGetUniformLocation(prg, buf.ptr);
508 //{ import core.stdc.stdio; printf("[%.*s.%s]=%i\n", cast(uint)shaderName.length, shaderName.ptr, buf.ptr, id); }
509 static if (is(NT == immutable(char)[])) {
510 vars[vname.idup] = id;
511 } else {
512 vars[vname.idup] = id;
514 if (id < 0) {
515 import core.stdc.stdio : printf;
516 printf("shader '%.*s': unknown variable '%.*s'\n", cast(uint)shaderName.length, shaderName.ptr, cast(uint)vname.length, vname.ptr);
520 return id;
523 // get unified var id
524 GLint opIndex(NT) (NT vname) if (is(NT == char[]) || is(NT == const(char)[]) || is(NT == immutable(char)[])) {
525 auto id = varId(vname);
526 if (id < 0) {
527 import core.stdc.stdio : printf;
528 printf("shader '%.*s': unknown variable '%.*s'\n", cast(uint)shaderName.length, shaderName.ptr, cast(uint)vname.length, vname.ptr);
529 assert(0);
531 return id;
534 private import std.traits;
535 void opIndexAssign(T, NT) (in auto ref T v, NT vname)
536 if (((isIntegral!T && T.sizeof <= 4) || (isFloatingPoint!T && T.sizeof == float.sizeof) || isBoolean!T ||
537 is(T : SVec2I) || is(T : SVec3I) || is(T : SVec4I) ||
538 is(T : SVec2F) || is(T : SVec3F) || is(T : SVec4F)) &&
539 (is(NT == char[]) || is(NT == const(char)[]) || is(NT == immutable(char)[])))
541 auto id = varId(vname);
542 if (id < 0) return;
543 //{ import core.stdc.stdio; printf("setting '%.*s' (%d)\n", cast(uint)vname.length, vname.ptr, id); }
544 static if (isIntegral!T || isBoolean!T) glUniform1i(id, cast(int)v);
545 else static if (isFloatingPoint!T) glUniform1f(id, cast(float)v);
546 else static if (is(SVec2I : T)) glUniform2i(id, cast(int)v.x, cast(int)v.y);
547 else static if (is(SVec3I : T)) glUniform3i(id, cast(int)v.x, cast(int)v.y, cast(int)v.z);
548 else static if (is(SVec4I : T)) glUniform4i(id, cast(int)v.x, cast(int)v.y, cast(int)v.z, cast(int)v.w);
549 else static if (is(SVec2F : T)) glUniform2f(id, cast(float)v.x, cast(float)v.y);
550 else static if (is(SVec3F : T)) glUniform3f(id, cast(float)v.x, cast(float)v.y, cast(float)v.z);
551 else static if (is(SVec4F : T)) glUniform4f(id, cast(float)v.x, cast(float)v.y, cast(float)v.z, cast(float)v.w);
552 else static assert(0, "wtf?!");
555 protected:
556 override void activateObj () nothrow @nogc { /*if (prg)*/ glUseProgram(prg); }
557 override void deactivateObj () nothrow @nogc { glUseProgram(0); }
561 // ////////////////////////////////////////////////////////////////////////// //
562 //private import std.traits;
564 public:
565 void exec(TO) (TO obj, scope void delegate () dg) if (is(typeof(() { obj.activate(); obj.deactivate(); }))) {
566 obj.activate();
567 scope(exit) obj.deactivate();
568 dg();
571 void exec(TO, TG) (TO obj, scope TG dg) if (is(typeof((TO obj) { dg(obj); })) && is(typeof(() { obj.activate(); obj.deactivate(); }))) {
572 obj.activate();
573 scope(exit) obj.deactivate();
574 dg(obj);
578 // ////////////////////////////////////////////////////////////////////////// //
579 void orthoCamera (int wdt, int hgt) {
580 glMatrixMode(GL_PROJECTION); // for ortho camera
581 glLoadIdentity();
582 // left, right, bottom, top, near, far
583 //glOrtho(0, wdt, 0, hgt, -1, 1); // bottom-to-top
584 glOrtho(0, wdt, hgt, 0, -1, 1); // top-to-bottom
585 glViewport(0, 0, wdt, hgt);
587 //glTranslatef(-cx, -cy, 0.0f);
590 // origin is texture left top
591 void drawAtXY (GLuint tid, int x, int y, int w, int h, bool mirrorX=false, bool mirrorY=false) {
592 if (!tid || w < 1 || h < 1) return;
593 w += x;
594 h += y;
595 if (mirrorX) { int tmp = x; x = w; w = tmp; }
596 if (mirrorY) { int tmp = y; y = h; h = tmp; }
597 bindTexture(tid);
598 glBegin(GL_QUADS);
599 glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); // top-left
600 glTexCoord2f(1.0f, 0.0f); glVertex2i(w, y); // top-right
601 glTexCoord2f(1.0f, 1.0f); glVertex2i(w, h); // bottom-right
602 glTexCoord2f(0.0f, 1.0f); glVertex2i(x, h); // bottom-left
603 glEnd();
607 // origin is texture center
608 void drawAtXYC (Texture tex, int x, int y, bool mirrorX=false, bool mirrorY=false) {
609 if (tex is null || !tex.tid) return;
610 x -= tex.width/2;
611 y -= tex.height/2;
612 drawAtXY(tex.tid, x, y, tex.width, tex.height, mirrorX, mirrorY);
616 // origin is texture left top
617 void drawAtXY (Texture tex, int x, int y, bool mirrorX=false, bool mirrorY=false) {
618 if (tex is null || !tex.tid) return;
619 drawAtXY(tex.tid, x, y, tex.width, tex.height, mirrorX, mirrorY);
623 private __gshared GLuint boundTexture = 0;
625 // make sure that texture unit 0 is active!
626 void bindTexture (GLuint tid) nothrow @trusted @nogc {
627 if (tid != boundTexture) {
628 boundTexture = tid;
629 glBindTexture(GL_TEXTURE_2D, tid);