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
;
29 // ////////////////////////////////////////////////////////////////////////// //
30 __gshared
bool glutilsShowShaderWarnings
= false; // shut up!
33 __gshared GLuint glLastUsedTexture = 0;
35 public void useTexture (GLuint tid) {
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
);
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 // ////////////////////////////////////////////////////////////////////////// //
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;
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
);
84 assert(0, "trying to deactivate inactive object");
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 {
99 //glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
100 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 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
);
115 // ////////////////////////////////////////////////////////////////////////// //
116 public final class Texture
: OpenGLObject
{
120 override @property uint id () const pure nothrow @nogc => tid
;
122 // default: repeat, linear
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(); }
145 glDeleteTextures(1, &tid
);
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;
169 void createIntr (int w
, int h
, TrueColorImage img
, in Option
[] opts
...) {
170 import core
.stdc
.stdlib
: malloc
, free
;
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
);
182 auto oldtid
= boundTexture
;
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
);
197 // create empty texture
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) {
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
212 //glTextureSubImage2D(tid, 0, 0, 0, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
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
);
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
;
235 src
-= x
; // as `x` is negative here
238 if (x
+rwdt
> width
) rwdt
= width
-x
;
239 // calc y skip and effective height
240 int rhgt
= img
.height
;
243 src
-= y
*img
.width
; // as `y` is negative here
246 if (y
+rhgt
> height
) rhgt
= height
-y
;
247 assert(rwdt
> 0 && rhgt
> 0);
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()`");
254 foreach (immutable _
; 0..rhgt
) {
255 memcpy(d
, src
, rwdt
*4);
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);
288 fl
.rawReadExact(sign
[]);
291 if (sign
== "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
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]);
302 if (sign[0..2] == "\xff\xd9") {
310 throw new Exception("invalid texture image format");
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
{
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
...) {
334 createWithTexture(new Texture(wdt
, hgt
, opts
), opts
);
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
);
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
);
355 if (tex
!is null) tex
.clear();
357 if (tex1
!is null) tex1
.clear();
359 if (texdepth
!is null) texdepth
.clear();
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) {
371 if (ntex !is null && ntex.tid == tex.tid) return;
373 if (ntex is null) return;
375 glGenFramebuffersEXT(1, &fbo);
376 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
377 scope(exit) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
379 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
380 glDeleteFramebuffersEXT(1, &fbo);
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!");
395 void createWithTexture (Texture atex
, Texture
.Option
[] opts
...) {
396 assert(atex
!is null && atex
.tid
);
399 foreach (immutable opt
; opts
) if (opt
== Texture
.Option
.FBO2
) { need2
= true; break; }
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
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 () {
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) {
438 GLenum status
= glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT
);
439 if (status
!= GL_FRAMEBUFFER_COMPLETE_EXT
) assert(0, "framebuffer fucked!");
441 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
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
{
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
);
473 auto shaderId
= glCreateShader(GL_FRAGMENT_SHADER
);
475 GLint slen
= cast(int)src
.length
;
476 glShaderSource(shaderId
, 1, &sptr
, &slen
);
477 glCompileShader(shaderId
);
479 glGetShaderiv(shaderId
, GL_COMPILE_STATUS
, &success
);
480 if (!success || glutilsShowShaderWarnings
) {
481 import core
.stdc
.stdio
: printf
;
482 import core
.stdc
.stdlib
: malloc
, free
;
484 glGetShaderiv(shaderId
, GL_INFO_LOG_LENGTH
, &logSize
);
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
);
492 if (!success
) assert(0);
493 prg
= glCreateProgram();
494 glAttachShader(prg
, shaderId
);
498 GLint
varId(NT
) (NT vname
) if (is(NT
== char[]) ||
is(NT
== const(char)[]) ||
is(NT
== immutable(char)[])) {
500 if (vname
.length
> 0 && vname
.length
<= 128) {
501 if (auto vi
= vname
in vars
) {
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
;
512 vars
[vname
.idup
] = id
;
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
);
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
);
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
);
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
);
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?!");
556 override void activateObj () nothrow @nogc { /*if (prg)*/ glUseProgram(prg
); }
557 override void deactivateObj () nothrow @nogc { glUseProgram(0); }
561 // ////////////////////////////////////////////////////////////////////////// //
562 //private import std.traits;
565 void exec(TO
) (TO obj
, scope void delegate () dg
) if (is(typeof(() { obj
.activate(); obj
.deactivate(); }))) {
567 scope(exit
) obj
.deactivate();
571 void exec(TO
, TG
) (TO obj
, scope TG dg
) if (is(typeof((TO obj
) { dg(obj
); })) && is(typeof(() { obj
.activate(); obj
.deactivate(); }))) {
573 scope(exit
) obj
.deactivate();
578 // ////////////////////////////////////////////////////////////////////////// //
579 void orthoCamera (int wdt
, int hgt
) {
580 glMatrixMode(GL_PROJECTION
); // for ortho camera
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;
595 if (mirrorX
) { int tmp
= x
; x
= w
; w
= tmp
; }
596 if (mirrorY
) { int tmp
= y
; y
= h
; h
= tmp
; }
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
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;
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
) {
629 glBindTexture(GL_TEXTURE_2D
, tid
);