level scrolling
[dd2d.git] / glutils.d
blob5ced573e1e7a3faafe3cc45a78a95ac317e5d85c
1 module glutils;
2 private:
3 import iv.glbinds;
4 import arsd.color;
5 import arsd.png;
7 import wadarc;
10 // ////////////////////////////////////////////////////////////////////////// //
11 __gshared bool glutilsShowShaderWarnings = false; // shut up!
14 __gshared GLuint glLastUsedTexture = 0;
16 public void useTexture (GLuint tid) {
17 pragma(inline, true);
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);
30 auto sz = fl.size;
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 {
41 GLuint tid;
42 int width, height;
44 // default: repeat, linear
45 enum Option : int {
46 Repeat,
47 Clamp,
48 ClampBorder,
49 Linear,
50 Nearest,
51 UByte,
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); }
58 ~this () { clear(); }
61 void clear () {
62 if (tid) {
63 //useTexture(tid);
64 glBindTexture(GL_TEXTURE_2D, tid);
65 glDeleteTextures(1, &tid);
66 //useTexture(0);
67 glBindTexture(GL_TEXTURE_2D, 0);
68 tid = 0;
69 width = 0;
70 height = 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;
84 default:
89 void createIntr (int w, int h, TrueColorImage img, in Option[] opts...) {
90 import core.stdc.stdlib : malloc, free;
91 assert(w > 0);
92 assert(h > 0);
93 clear();
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);
101 //useTexture(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);
115 } else {
116 // create empty texture
117 ubyte* ptr = null;
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) {
126 // setup from image
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);
134 width = w;
135 height = h;
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);
145 } else {
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;
152 if (x < 0) {
153 rwdt += x;
154 src -= x; // as `x` is negative here
155 x = 0;
157 if (x+rwdt > width) rwdt = width-x;
158 // calc y skip and effective height
159 int rhgt = img.height;
160 if (y < 0) {
161 rhgt += y;
162 src -= y*img.width; // as `y` is negative here
163 y = 0;
165 if (y+rhgt > height) rhgt = height-y;
166 assert(rwdt > 0 && rhgt > 0);
167 uint* ptr = null;
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()`");
171 // now copy
172 auto d = ptr;
173 foreach (immutable _; 0..rhgt) {
174 memcpy(d, src, rwdt*4);
175 src += img.width;
176 d += rwdt;
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 {
196 GLuint tid;
197 int width, height;
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(); }
204 void clear () {
205 if (tid) {
206 glBindTexture(GL_TEXTURE_CUBE_MAP, tid);
207 glDeleteTextures(1, &tid);
208 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
209 tid = 0;
210 width = 0;
211 height = 0;
215 void createIntr (int w, int h, in Texture.Option[] opts...) {
216 import core.stdc.stdlib : malloc, free;
217 assert(w > 0);
218 assert(h > 0);
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);
236 ubyte* ptr = null;
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);
246 width = w;
247 height = h;
250 void loadPng (string fname, in Texture.Option[] opts...) {
251 clear();
252 TrueColorImage[6] img;
253 foreach (int idx; 0..6) {
254 import std.string : format;
255 img[idx] = loadPngFile(fname.format(idx));
256 if (idx > 0) {
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 {
278 int width;
279 int height;
280 GLuint fbo;
281 Texture tex;
283 this (Texture atex) { createWithTexture(atex); }
285 this (int wdt, int hgt, Texture.Option[] opts...) {
286 createWithTexture(new Texture(wdt, hgt, opts));
289 ~this () {
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);
293 fbo = 0;
296 void clear () {
297 if (fbo) {
298 // detach texture
299 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
300 glDeleteFramebuffersEXT(1, &fbo);
301 fbo = 0;
303 if (tex !is null) tex.clear();
304 tex = null;
307 void activate () { if (fbo) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); }
308 void deactivate () { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); }
310 private:
311 void createWithTexture (Texture atex) {
312 assert(atex !is null && atex.tid);
314 tex = atex;
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);
328 // kill it
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);
338 width = tex.width;
339 height = tex.height;
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 {
355 string shaderName;
356 GLuint prg = 0;
357 GLint[string] vars;
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);
364 assert(0);
366 auto shaderId = glCreateShader(GL_FRAGMENT_SHADER);
367 auto sptr = src.ptr;
368 GLint slen = cast(int)src.length;
369 glShaderSource(shaderId, 1, &sptr, &slen);
370 glCompileShader(shaderId);
371 GLint success = 0;
372 glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
373 if (!success || glutilsShowShaderWarnings) {
374 import core.stdc.stdio : printf;
375 import core.stdc.stdlib : malloc, free;
376 GLint logSize = 0;
377 glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);
378 if (logSize > 0) {
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);
382 free(logStrZ);
385 if (!success) assert(0);
386 prg = glCreateProgram();
387 glAttachShader(prg, shaderId);
388 glLinkProgram(prg);
391 GLint varId(NT) (NT vname) if (is(NT == char[]) || is(NT == const(char)[]) || is(NT == immutable(char)[])) {
392 GLint id = -1;
393 if (vname.length > 0 && vname.length <= 128) {
394 if (auto vi = vname in vars) {
395 id = *vi;
396 } else {
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;
404 } else {
405 vars[vname.idup] = id;
407 if (id < 0) {
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);
413 return id;
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);
419 if (id < 0) {
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);
422 assert(0);
424 return id;
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);
435 if (id < 0) return;
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;
456 public:
457 void exec(TO) (TO obj, scope void delegate () dg) if (is(typeof(() { obj.activate(); obj.deactivate(); }))) {
458 obj.activate();
459 scope(exit) obj.deactivate();
460 dg();
463 void exec(TO, TG) (TO obj, scope TG dg) if (is(typeof((TO obj) { dg(obj); })) && is(typeof(() { obj.activate(); obj.deactivate(); }))) {
464 obj.activate();
465 scope(exit) obj.deactivate();
466 dg(obj);
470 // ////////////////////////////////////////////////////////////////////////// //
471 void orthoCamera (int wdt, int hgt) {
472 glMatrixMode(GL_PROJECTION); // for ortho camera
473 glLoadIdentity();
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;
485 w += x;
486 h += y;
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);
490 glBegin(GL_QUADS);
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
495 glEnd();
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;
502 x -= tex.width/2;
503 y -= tex.height/2;
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);