10 // ////////////////////////////////////////////////////////////////////////// //
11 __gshared
bool glutilsShowShaderWarnings
= false; // shut up!
14 __gshared GLuint glLastUsedTexture = 0;
16 public void useTexture (GLuint tid) {
18 if (glLastUsedTexture != tid) {
19 glLastUsedTexture = tid;
20 glBindTexture(GL_TEXTURE_2D, tid);
24 public void useTexture (Texture tex) { pragma(inline, true); useTexture(tex !is null ? tex.tid : 0); }
28 public TrueColorImage
loadPngFile (string fname
) {
29 auto fl
= openFile(fname
);
31 if (sz
< 4 || sz
> 32*1024*1024) throw new Exception("invalid png file size: '"~fname
~"'");
32 if (sz
== 0) return null;
33 auto res
= new ubyte[](cast(uint)sz
);
34 if (fl
.rawRead(res
[]).length
!= res
.length
) throw new Exception("error reading png file '"~fname
~"'");
35 return imageFromPng(readPng(res
)).getAsTrueColorImage
;
39 // ////////////////////////////////////////////////////////////////////////// //
40 public final class Texture
{
44 // default: repeat, linear
52 Float
, // create floating point texture
55 this (string fname
, in Option
[] opts
...) { loadPng(fname
, opts
); }
56 this (int w
, int h
, in Option
[] opts
...) { createIntr(w
, h
, null, opts
); }
57 this (TrueColorImage aimg
, Option
[] opts
...) { createIntr(aimg
.width
, aimg
.height
, aimg
, opts
); }
64 glBindTexture(GL_TEXTURE_2D
, tid
);
65 glDeleteTextures(1, &tid
);
67 glBindTexture(GL_TEXTURE_2D
, 0);
74 private static void processOpt (GLuint
* wrapOpt
, GLuint
* filterOpt
, GLuint
* ttype
, in Option
[] opts
...) {
75 foreach (immutable opt
; opts
) {
76 switch (opt
) with (Option
) {
77 case Repeat
: *wrapOpt
= GL_REPEAT
; break;
78 case Clamp
: *wrapOpt
= GL_CLAMP_TO_EDGE
; break;
79 case ClampBorder
: *wrapOpt
= GL_CLAMP_TO_BORDER
; break;
80 case Linear
: *filterOpt
= GL_LINEAR
; break;
81 case Nearest
: *filterOpt
= GL_NEAREST
; break;
82 case UByte
: *ttype
= GL_UNSIGNED_BYTE
; break;
83 case Float
: *ttype
= GL_FLOAT
; break;
89 void createIntr (int w
, int h
, TrueColorImage img
, in Option
[] opts
...) {
90 import core
.stdc
.stdlib
: malloc
, free
;
95 GLuint wrapOpt
= GL_REPEAT
;
96 GLuint filterOpt
= GL_LINEAR
;
97 GLuint ttype
= GL_UNSIGNED_BYTE
;
98 processOpt(&wrapOpt
, &filterOpt
, &ttype
, opts
);
100 glGenTextures(1, &tid
);
102 glBindTexture(GL_TEXTURE_2D
, tid
);
103 scope(exit
) glBindTexture(GL_TEXTURE_2D
, 0);
104 glTexParameterf(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, wrapOpt
);
105 glTexParameterf(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, wrapOpt
);
106 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, filterOpt
);
107 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, filterOpt
);
108 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
109 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
110 GLfloat
[4] bclr
= 0.0;
111 glTexParameterfv(GL_TEXTURE_2D
, GL_TEXTURE_BORDER_COLOR
, bclr
.ptr
);
112 if (img
!is null && img
.width
== w
&& img
.height
== h
) {
113 // create straight from image
114 glTexImage2D(GL_TEXTURE_2D
, 0, (ttype
== GL_FLOAT ? GL_RGBA16F
: GL_RGBA
), w
, h
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.imageData
.bytes
.ptr
);
116 // create empty texture
118 scope(exit
) if (ptr
!is null) free(ptr
);
120 import core
.stdc
.string
: memset
;
121 ptr
= cast(ubyte*)malloc(w
*h
*4);
122 if (ptr
!is null) memset(ptr
, 0, w
*h
*4);
124 glTexImage2D(GL_TEXTURE_2D
, 0, (ttype
== GL_FLOAT ? GL_RGBA16F
: GL_RGBA
), w
, h
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, ptr
);
125 if (img
!is null && img
.width
> 0 && img
.height
> 0) {
127 //TODO: dunno if it's ok to use images bigger than texture here
128 glTexSubImage2D(GL_TEXTURE_2D
, 0, 0, 0, img
.width
, img
.height
, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.imageData
.bytes
.ptr
);
129 // the following is ok too
130 //glBindTexture(GL_TEXTURE_2D, 0);
131 //glTextureSubImage2D(tid, 0, 0, 0, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, img.imageData.bytes.ptr);
138 void setFromImage (TrueColorImage img
, int x
, int y
) {
139 if (img
is null ||
!tid || img
.height
< 1 || img
.width
< 1) return;
140 if (x
>= width || y
>= height
) return;
141 if (x
+img
.width
<= 0 || y
+img
.height
<= 0) return; //TODO: overflow
142 if (x
>= 0 && y
>= 0 && x
+img
.width
<= width
&& y
+img
.height
<= height
) {
143 // easy case, just copy it
144 glTextureSubImage2D(tid
, 0, x
, y
, img
.width
, img
.height
, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.imageData
.bytes
.ptr
);
146 import core
.stdc
.stdlib
: malloc
, free
;
147 import core
.stdc
.string
: memset
, memcpy
;
148 // hard case, have to build the temp region
149 uint* src
= cast(uint*)img
.imageData
.bytes
.ptr
;
150 // calc x skip and effective width
151 int rwdt
= img
.width
;
154 src
-= x
; // as `x` is negative here
157 if (x
+rwdt
> width
) rwdt
= width
-x
;
158 // calc y skip and effective height
159 int rhgt
= img
.height
;
162 src
-= y
*img
.width
; // as `y` is negative here
165 if (y
+rhgt
> height
) rhgt
= height
-y
;
166 assert(rwdt
> 0 && rhgt
> 0);
168 scope(exit
) if (ptr
!is null) free(ptr
);
169 ptr
= cast(uint*)malloc(rwdt
*rhgt
*4);
170 if (ptr
is null) assert(0, "out of memory in `Texture.setFromImage()`");
173 foreach (immutable _
; 0..rhgt
) {
174 memcpy(d
, src
, rwdt
*4);
178 glTextureSubImage2D(tid
, 0, x
, y
, rwdt
, rhgt
, GL_RGBA
, GL_UNSIGNED_BYTE
, ptr
);
182 void loadPng (string fname
, in Option
[] opts
...) {
183 scope(failure
) clear
;
184 auto img
= loadPngFile(fname
);
185 createIntr(img
.width
, img
.height
, img
, opts
);
188 void activate () { if (tid
) glBindTexture(GL_TEXTURE_2D
, tid
); }
189 void deactivate () { if (tid
) glBindTexture(GL_TEXTURE_2D
, 0); }
193 // ////////////////////////////////////////////////////////////////////////// //
195 public final class TextureCube {
199 this (string fname, in Texture.Option[] opts...) { loadPng(fname, opts); }
200 this (int w, int h, in Texture.Option[] opts...) { createIntr(w, h, opts); }
201 ~this () { clear(); }
206 glBindTexture(GL_TEXTURE_CUBE_MAP, tid);
207 glDeleteTextures(1, &tid);
208 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
215 void createIntr (int w, int h, in Texture.Option[] opts...) {
216 import core.stdc.stdlib : malloc, free;
220 GLuint wrapOpt = GL_CLAMP_TO_EDGE;
221 GLuint filterOpt = GL_LINEAR;
222 GLuint ttype = GL_FLOAT;
223 Texture.processOpt(&wrapOpt, &filterOpt, &ttype, opts);
225 glGenTextures(1, &tid);
226 glBindTexture(GL_TEXTURE_CUBE_MAP, tid);
227 scope(exit) glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
228 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapOpt);
229 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapOpt);
230 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, wrapOpt);
231 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, filterOpt);
232 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, filterOpt);
233 GLfloat[4] bclr = 0.0;
234 glTexParameterfv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
237 scope(exit) if (ptr !is null) free(ptr);
239 import core.stdc.string : memset;
240 ptr = cast(ubyte*)malloc(w*h*4*4);
241 memset(ptr, 0, w*h*4);
243 foreach (int idx; 0..6) {
244 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+idx, 0, (ttype == GL_FLOAT ? GL_RGBA16F : GL_RGBA), w, h, 0, GL_BGRA, GL_FLOAT, ptr);
250 void loadPng (string fname, in Texture.Option[] opts...) {
252 TrueColorImage[6] img;
253 foreach (int idx; 0..6) {
254 import std.string : format;
255 img[idx] = loadPngFile(fname.format(idx));
257 if (img[idx].width != img[idx-1].width || img[idx].height != img[idx-1].height) {
258 assert(0, "cubemap fucked: "~fname);
262 createIntr(img[0].width, img[0].height, opts);
263 glBindTexture(GL_TEXTURE_CUBE_MAP, tid);
264 scope(exit) glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
265 foreach (int idx; 0..6) {
266 glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+idx, 0, 0, 0, img[idx].width, img[idx].height, GL_RGBA, GL_UNSIGNED_BYTE, img[idx].imageData.bytes.ptr);
270 void activate () { if (tid) glBindTexture(GL_TEXTURE_CUBE_MAP, tid); }
271 void deactivate () { if (tid) glBindTexture(GL_TEXTURE_CUBE_MAP, 0); }
276 // ////////////////////////////////////////////////////////////////////////// //
277 public final class FBO
{
283 this (Texture atex
) { createWithTexture(atex
); }
285 this (int wdt
, int hgt
, Texture
.Option
[] opts
...) {
286 createWithTexture(new Texture(wdt
, hgt
, opts
));
290 //FIXME: this may be wrong, as texture may be already destroyed (and it's wrong too); we need refcount for textures
291 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
, GL_TEXTURE_2D
, 0, 0);
292 glDeleteFramebuffersEXT(1, &fbo
);
299 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
, GL_TEXTURE_2D
, 0, 0);
300 glDeleteFramebuffersEXT(1, &fbo
);
303 if (tex
!is null) tex
.clear();
307 void activate () { if (fbo
) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, fbo
); }
308 void deactivate () { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0); }
311 void createWithTexture (Texture atex
) {
312 assert(atex
!is null && atex
.tid
);
315 glGenFramebuffersEXT(1, &fbo
);
316 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, fbo
);
317 scope(exit
) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
318 // attach 2D texture to this FBO
319 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
, GL_TEXTURE_2D
, tex
.tid
, 0);
320 // GL_COLOR_ATTACHMENT0_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_STENCIL_ATTACHMENT_EXT
323 glGenRenderbuffersEXT(1, &fboDepthId);
324 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fboDepthId);
325 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, LightSize, LightSize);
326 // attach depth buffer to FBO
327 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fboDepthId);
329 glDeleteRenderbuffersEXT(1, &fboDepthId);
333 GLenum status
= glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT
);
334 if (status
!= GL_FRAMEBUFFER_COMPLETE_EXT
) assert(0, "framebuffer fucked!");
336 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
344 // ////////////////////////////////////////////////////////////////////////// //
345 public struct SVec2I
{ int x
, y
; }
346 public struct SVec3I
{ int x
, y
, z
; }
347 public struct SVec4I
{ int x
, y
, z
, w
; }
349 public struct SVec2F
{ float x
, y
; }
350 public struct SVec3F
{ float x
, y
, z
; }
351 public struct SVec4F
{ float x
, y
, z
, w
; }
354 public final class Shader
{
359 this (string ashaderName
, const(char)[] src
) {
360 shaderName
= ashaderName
;
361 if (src
.length
> int.max
) {
362 import core
.stdc
.stdio
: printf
;
363 printf("shader '%.*s' code too long!", cast(uint)ashaderName
.length
, ashaderName
.ptr
);
366 auto shaderId
= glCreateShader(GL_FRAGMENT_SHADER
);
368 GLint slen
= cast(int)src
.length
;
369 glShaderSource(shaderId
, 1, &sptr
, &slen
);
370 glCompileShader(shaderId
);
372 glGetShaderiv(shaderId
, GL_COMPILE_STATUS
, &success
);
373 if (!success || glutilsShowShaderWarnings
) {
374 import core
.stdc
.stdio
: printf
;
375 import core
.stdc
.stdlib
: malloc
, free
;
377 glGetShaderiv(shaderId
, GL_INFO_LOG_LENGTH
, &logSize
);
379 auto logStrZ
= cast(GLchar
*)malloc(logSize
);
380 glGetShaderInfoLog(shaderId
, logSize
, null, logStrZ
);
381 printf("shader '%.*s' compilation messages:\n%s\n", cast(uint)ashaderName
.length
, ashaderName
.ptr
, logStrZ
);
385 if (!success
) assert(0);
386 prg
= glCreateProgram();
387 glAttachShader(prg
, shaderId
);
391 GLint
varId(NT
) (NT vname
) if (is(NT
== char[]) ||
is(NT
== const(char)[]) ||
is(NT
== immutable(char)[])) {
393 if (vname
.length
> 0 && vname
.length
<= 128) {
394 if (auto vi
= vname
in vars
) {
397 char[129] buf
= void;
398 buf
[0..vname
.length
] = vname
[];
399 buf
[vname
.length
] = 0;
400 id
= glGetUniformLocation(prg
, buf
.ptr
);
401 //{ import core.stdc.stdio; printf("[%.*s.%s]=%i\n", cast(uint)shaderName.length, shaderName.ptr, buf.ptr, id); }
402 static if (is(NT
== immutable(char)[])) {
403 vars
[vname
.idup
] = id
;
405 vars
[vname
.idup
] = id
;
408 import core
.stdc
.stdio
: printf
;
409 printf("shader '%.*s': unknown variable '%.*s'\n", cast(uint)shaderName
.length
, shaderName
.ptr
, cast(uint)vname
.length
, vname
.ptr
);
416 // get unified var id
417 GLint
opIndex(NT
) (NT vname
) if (is(NT
== char[]) ||
is(NT
== const(char)[]) ||
is(NT
== immutable(char)[])) {
418 auto id
= varId(vname
);
420 import core
.stdc
.stdio
: printf
;
421 printf("shader '%.*s': unknown variable '%.*s'\n", cast(uint)shaderName
.length
, shaderName
.ptr
, cast(uint)vname
.length
, vname
.ptr
);
427 private import std
.traits
;
428 void opIndexAssign(T
, NT
) (in auto ref T v
, NT vname
)
429 if (((isIntegral
!T
&& T
.sizeof
<= 4) ||
(isFloatingPoint
!T
&& T
.sizeof
== float.sizeof
) || isBoolean
!T ||
430 is(T
: SVec2I
) ||
is(T
: SVec3I
) ||
is(T
: SVec4I
) ||
431 is(T
: SVec2F
) ||
is(T
: SVec3F
) ||
is(T
: SVec4F
)) &&
432 (is(NT
== char[]) ||
is(NT
== const(char)[]) ||
is(NT
== immutable(char)[])))
434 auto id
= varId(vname
);
436 //{ import core.stdc.stdio; printf("setting '%.*s' (%d)\n", cast(uint)vname.length, vname.ptr, id); }
437 static if (isIntegral
!T || isBoolean
!T
) glUniform1i(id
, cast(int)v
);
438 else static if (isFloatingPoint
!T
) glUniform1f(id
, cast(float)v
);
439 else static if (is(SVec2I
: T
)) glUniform2i(id
, cast(int)v
.x
, cast(int)v
.y
);
440 else static if (is(SVec3I
: T
)) glUniform3i(id
, cast(int)v
.x
, cast(int)v
.y
, cast(int)v
.z
);
441 else static if (is(SVec4I
: T
)) glUniform4i(id
, cast(int)v
.x
, cast(int)v
.y
, cast(int)v
.z
, cast(int)v
.w
);
442 else static if (is(SVec2F
: T
)) glUniform2f(id
, cast(float)v
.x
, cast(float)v
.y
);
443 else static if (is(SVec3F
: T
)) glUniform3f(id
, cast(float)v
.x
, cast(float)v
.y
, cast(float)v
.z
);
444 else static if (is(SVec4F
: T
)) glUniform4f(id
, cast(float)v
.x
, cast(float)v
.y
, cast(float)v
.z
, cast(float)v
.w
);
445 else static assert(0, "wtf?!");
448 void activate () { if (prg
) glUseProgram(prg
); }
449 void deactivate () { glUseProgram(0); }
453 // ////////////////////////////////////////////////////////////////////////// //
454 //private import std.traits;
457 void exec(TO
) (TO obj
, scope void delegate () dg
) if (is(typeof(() { obj
.activate(); obj
.deactivate(); }))) {
459 scope(exit
) obj
.deactivate();
463 void exec(TO
, TG
) (TO obj
, scope TG dg
) if (is(typeof((TO obj
) { dg(obj
); })) && is(typeof(() { obj
.activate(); obj
.deactivate(); }))) {
465 scope(exit
) obj
.deactivate();
470 // ////////////////////////////////////////////////////////////////////////// //
471 void orthoCamera (int wdt
, int hgt
) {
472 glMatrixMode(GL_PROJECTION
); // for ortho camera
474 // left, right, bottom, top, near, far
475 //glOrtho(0, wdt, 0, hgt, -1, 1); // bottom-to-top
476 glOrtho(0, wdt
, hgt
, 0, -1, 1); // top-to-bottom
477 glViewport(0, 0, wdt
, hgt
);
479 //glTranslatef(-cx, -cy, 0.0f);
482 // origin is texture left top
483 void drawAtXY (GLuint tid
, int x
, int y
, int w
, int h
, bool mirrorX
=false, bool mirrorY
=false) {
484 if (!tid || w
< 1 || h
< 1) return;
487 if (mirrorX
) { int tmp
= x
; x
= w
; w
= tmp
; }
488 if (mirrorY
) { int tmp
= y
; y
= h
; h
= tmp
; }
489 glBindTexture(GL_TEXTURE_2D
, tid
);
491 glTexCoord2f(0.0f, 0.0f); glVertex2i(x
, y
); // top-left
492 glTexCoord2f(1.0f, 0.0f); glVertex2i(w
, y
); // top-right
493 glTexCoord2f(1.0f, 1.0f); glVertex2i(w
, h
); // bottom-right
494 glTexCoord2f(0.0f, 1.0f); glVertex2i(x
, h
); // bottom-left
499 // origin is texture center
500 void drawAtXYC (Texture tex
, int x
, int y
, bool mirrorX
=false, bool mirrorY
=false) {
501 if (tex
is null ||
!tex
.tid
) return;
504 drawAtXY(tex
.tid
, x
, y
, tex
.width
, tex
.height
, mirrorX
, mirrorY
);
508 // origin is texture left top
509 void drawAtXY (Texture tex
, int x
, int y
, bool mirrorX
=false, bool mirrorY
=false) {
510 if (tex
is null ||
!tex
.tid
) return;
511 drawAtXY(tex
.tid
, x
, y
, tex
.width
, tex
.height
, mirrorX
, mirrorY
);