- shaders for both lightmapped objects with or without albedo maps
[laserbrain_demo] / src / texture.cc
1 #include <math.h>
2 #include <assert.h>
3 #include "texture.h"
4 #include "image.h"
5 #include "opengl.h"
6 #include "imago2.h"
7 #include "logger.h"
8
9 static int glifmt_from_ifmt(unsigned int ifmt);
10 static int glfmt_from_ifmt(unsigned int ifmt);
11 static int gltype_from_ifmt(unsigned int ifmt);
12
13 static int glifmt_from_imgfmt(Image::Format fmt);
14
15 static unsigned int type_to_target(TextureType type);
16 static TextureType target_to_type(unsigned int targ);
17
18 static unsigned int cur_target[8] = {
19         GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D,
20         GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D
21 };
22
23 static unsigned int cube_faces[] = {
24         GL_TEXTURE_CUBE_MAP_POSITIVE_X,
25         GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
26         GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
27         GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
28         GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
29         GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
30 };
31
32
33 void bind_texture(Texture *tex, int tunit)
34 {
35         if(tex) {
36                 tex->bind(tunit);
37         } else {
38                 glActiveTexture(GL_TEXTURE0 + tunit);
39                 glBindTexture(cur_target[tunit], 0);
40                 assert(glGetError() == GL_NO_ERROR);
41                 glActiveTexture(GL_TEXTURE0);
42         }
43 }
44
45
46 Image *Texture::default_img;
47
48 Texture::Texture()
49 {
50         target = 0;
51         sz[0] = sz[1] = sz[2] = 0;
52         texfmt = 0;
53
54         img = 0;
55         glGenTextures(1, &id);
56 }
57
58 Texture::~Texture()
59 {
60         if(id) {
61                 glDeleteTextures(1, &id);
62         }
63         if(img) {
64                 delete img;
65         }
66 }
67
68 void Texture::set_wrapping(unsigned int wrap)
69 {
70         if(!target) {
71                 return;
72         }
73
74         glBindTexture(target, id);
75         assert(glGetError() == GL_NO_ERROR);
76         glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
77         glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
78         glTexParameteri(target, GL_TEXTURE_WRAP_R, wrap);
79 }
80
81 void Texture::set_filtering(unsigned int filt)
82 {
83         unsigned int mag_filter;
84
85         if(!target) {
86                 return;
87         }
88
89         switch(filt) {
90         case GL_LINEAR_MIPMAP_NEAREST:
91         case GL_LINEAR_MIPMAP_LINEAR:
92                 mag_filter = GL_LINEAR;
93                 break;
94
95         case GL_NEAREST_MIPMAP_NEAREST:
96         case GL_NEAREST_MIPMAP_LINEAR:
97                 mag_filter = GL_NEAREST;
98                 break;
99
100         default:
101                 mag_filter = filt;
102         }
103
104         set_filtering(filt, mag_filter);
105 }
106
107 void Texture::set_filtering(unsigned int min_filt, unsigned int mag_filt)
108 {
109         glBindTexture(target, id);
110         assert(glGetError() == GL_NO_ERROR);
111         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filt);
112         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filt);
113 }
114
115 unsigned int Texture::get_format() const
116 {
117         return texfmt;
118 }
119
120 int Texture::get_size(int dim) const
121 {
122         if(dim < 0 || dim >= 3) {
123                 return 0;
124         }
125         return sz[dim];
126 }
127
128 unsigned int Texture::get_id() const
129 {
130         return id;
131 }
132
133 TextureType Texture::get_type() const
134 {
135         return target_to_type(target);
136 }
137
138 void Texture::bind(int tex_unit) const
139 {
140         glActiveTexture(GL_TEXTURE0 + tex_unit);
141         glBindTexture(target, id);
142         assert(glGetError() == GL_NO_ERROR);
143         glActiveTexture(GL_TEXTURE0);
144
145         cur_target[tex_unit] = target;
146 }
147
148
149 void Texture::create(int xsz, int ysz, TextureType textype, unsigned int ifmt)
150 {
151         if(textype == TEX_CUBE && xsz != ysz) {
152                 error_log("trying to create cubemap with different width and height (%dx%d)\n", xsz, ysz);
153                 return;
154         }
155
156         int fmt = glfmt_from_ifmt(ifmt);
157         int type = gltype_from_ifmt(ifmt);
158
159         target = type_to_target(textype);
160
161         glBindTexture(target, id);
162         assert(glGetError() == GL_NO_ERROR);
163         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
164         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
165
166         switch(type) {
167         case TEX_2D:
168                 glTexImage2D(GL_TEXTURE_2D, 0, glifmt_from_ifmt(ifmt), xsz, ysz, 0, fmt, type, 0);
169                 break;
170
171         case TEX_CUBE:
172                 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
173                 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
174                 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
175                 for(int i=0; i<6; i++) {
176                         glTexImage2D(cube_faces[i], 0, ifmt, xsz, ysz, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
177                 }
178                 break;
179         }
180
181         sz[0] = xsz;
182         sz[1] = ysz;
183         texfmt = ifmt;
184 }
185
186 #define DEF_IMAGE_SIZE  64
187 void Texture::create_default(TextureType type)
188 {
189         if(!default_img) {
190                 default_img = new Image;
191                 default_img->create(DEF_IMAGE_SIZE, DEF_IMAGE_SIZE, Image::FMT_RGBA);
192
193                 unsigned char *pixels = (unsigned char*)default_img->get_pixels();
194                 for(int i=0; i<DEF_IMAGE_SIZE; i++) {
195                         for(int j=0; j<DEF_IMAGE_SIZE; j++) {
196                                 bool chess = ((i >> 3) & 1) == ((j >> 3) & 1);
197                                 pixels[0] = chess ? 255 : 32;
198                                 pixels[1] = 64;
199                                 pixels[2] = chess ? 32 : 255;
200                                 pixels[3] = 255;
201                                 pixels += 4;
202                         }
203                 }
204                 default_img->save("/tmp/foo.png");
205         }
206
207         switch(type) {
208         case TEX_2D:
209                 set_image(*default_img);
210                 break;
211
212         case TEX_CUBE:
213                 for(int i=0; i<6; i++) {
214                         set_image(*default_img, i);
215                 }
216                 break;
217         }
218 }
219
220 void Texture::set_image(const Image &img, int idx)
221 {
222         if(idx >= 0 && idx < 6) {
223                 set_image_cube(img, idx);
224         } else {
225                 if(!set_image_cube(img)) {
226                         set_image_2d(img);
227                 }
228         }
229 }
230
231 void Texture::set_image_2d(const Image &img)
232 {
233         texfmt = glifmt_from_imgfmt(img.get_format());
234         unsigned int fmt = glfmt_from_ifmt(texfmt);
235         unsigned int type = gltype_from_ifmt(texfmt);
236
237         sz[0] = img.get_width();
238         sz[1] = img.get_height();
239
240         target = GL_TEXTURE_2D;
241         glBindTexture(target, id);
242         assert(glGetError() == GL_NO_ERROR);
243         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
244         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
245         glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
246         glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
247
248 #ifdef __GLEW_H__
249         if(GLEW_SGIS_generate_mipmap) {
250                 glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
251 #endif
252                 glTexImage2D(target, 0, texfmt, sz[0], sz[1], 0, fmt, type, img.get_pixels());
253 #ifdef __GLEW_H__
254         } else {
255                 gluBuild2DMipmaps(target, texfmt, sz[0], sz[1], fmt, type, img.get_pixels());
256         }
257 #endif
258
259 #ifdef GL_ES_VERSION_2_0
260         glGenerateMipmap(target);
261 #endif
262 }
263
264 bool Texture::set_image_cube(const Image &img, int idx)
265 {
266         if(idx < 0 || idx >= 6) {
267                 return false;
268         }
269
270         texfmt = glifmt_from_imgfmt(img.get_format());
271         unsigned int fmt = glfmt_from_ifmt(texfmt);
272         unsigned int type = gltype_from_ifmt(texfmt);
273
274         sz[0] = img.get_width();
275         sz[1] = img.get_height();
276
277         target = GL_TEXTURE_CUBE_MAP;
278         glBindTexture(target, id);
279         assert(glGetError() == GL_NO_ERROR);
280         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
281         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
282         glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
283         glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
284         glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
285
286         glTexImage2D(cube_faces[idx], 0, texfmt, sz[0], sz[1], 0, fmt, type, img.get_pixels());
287         return true;
288 }
289
290 bool Texture::set_image_cube(const Image &img)
291 {
292         static const float one_third = 1.0 / 3.0;
293         static const float two_thirds = 2.0 / 3.0;
294         static const float hcross[2][6] = {
295                 {0.5, 0.0, 0.25, 0.25, 0.25, 0.75}, {one_third, one_third, 0.0, two_thirds, one_third, one_third} };
296         static const float vcross[2][6] = {
297                 {two_thirds, 0.0, one_third, one_third, one_third, one_third}, {0.25, 0.25, 0.0, 0.5, 0.25, 0.75} };
298         static const float hsix[2][6] = {
299                 {0.0, 0.0, one_third, one_third, two_thirds, two_thirds}, {0.0, 0.5, 0.0, 0.5, 0.0, 0.5} };
300
301         int xsz = img.get_width();
302         int ysz = img.get_height();
303
304         if(xsz / 4 == ysz / 3) {
305                 // horizontal cross, assume the vertical bit is center-left
306                 return set_cube_multi(img, hcross[0], hcross[1], xsz / 4);
307         }
308         if(xsz / 3 == ysz / 4) {
309                 // vertical cross, assume the horizontal bit is center-top (180-rotated image 5)
310                 return set_cube_multi(img, vcross[0], vcross[1], ysz / 4, (1 << 5));
311         }
312         if(xsz / 3 == ysz / 2) {
313                 // horizontal sixpack
314                 return set_cube_multi(img, hsix[0], hsix[1], ysz / 2);
315         }
316
317         return false;
318 }
319
320
321 bool Texture::load(const char *fname)
322 {
323         Image img;
324         if(!img.load(fname)) {
325                 error_log("failed to load 2D texture: %s\n", fname);
326                 return false;
327         }
328         set_image(img);
329
330         info_log("loaded 2D texture: %s\n", fname);
331         return true;
332 }
333
334 bool Texture::load_cube(const char *fname)
335 {
336         Image img;
337         if(!img.load(fname)) {
338                 return false;
339         }
340         return set_image_cube(img);
341 }
342
343 bool Texture::set_cube_multi(const Image &img, const float *xoffsets, const float *yoffsets, float sz,
344                 unsigned int rotmask)
345 {
346         for(int i=0; i<6; i++) {
347                 Image face;
348
349                 int xoffs = xoffsets[i] * img.get_width();
350                 int yoffs = yoffsets[i] * img.get_height();
351
352                 if(!face.set_pixels(sz, sz, img.get_pixels(), xoffs, yoffs, img.get_width(), img.get_format())) {
353                         return false;
354                 }
355
356                 if(rotmask & (1 << i)) {
357                         face.rotate_180();
358                 }
359                 set_image_cube(face, i);
360         }
361         return true;
362 }
363
364 static int glifmt_from_ifmt(unsigned int ifmt)
365 {
366 #ifdef GL_ES_VERSION_2_0
367         switch(ifmt) {
368         case GL_LUMINANCE16F_ARB:
369         case GL_LUMINANCE32F_ARB:
370                 ifmt = GL_LUMINANCE;
371                 break;
372
373         case GL_RGB16F:
374         case GL_RGB32F:
375                 ifmt = GL_RGB;
376                 break;
377
378         case GL_RGBA16F:
379         case GL_RGBA32F:
380                 ifmt = GL_RGBA;
381                 break;
382
383         default:
384                 break;
385         }
386 #endif
387         return ifmt;    // by default just pass it through...
388 }
389
390 static int glfmt_from_ifmt(unsigned int ifmt)
391 {
392         switch(ifmt) {
393         case GL_LUMINANCE16F_ARB:
394         case GL_LUMINANCE32F_ARB:
395         case GL_SLUMINANCE:
396                 return GL_LUMINANCE;
397
398         case GL_RGB16F:
399         case GL_RGB32F:
400         case GL_SRGB:
401                 return GL_RGB;
402
403         case GL_RGBA16F:
404         case GL_RGBA32F:
405         case GL_SRGB_ALPHA:
406                 return GL_RGBA;
407
408         default:
409                 break;
410         }
411         return ifmt;
412 }
413
414 static int gltype_from_ifmt(unsigned int ifmt)
415 {
416         switch(ifmt) {
417         case GL_RGB16F:
418         case GL_RGBA16F:
419         case GL_LUMINANCE16F_ARB:
420 #ifdef GL_ES_VERSION_2_0
421                 return GL_HALF_FLOAT_OES;
422 #endif
423         case GL_RGB32F:
424         case GL_RGBA32F:
425         case GL_LUMINANCE32F_ARB:
426                 return GL_FLOAT;
427
428         default:
429                 break;
430         }
431         return GL_UNSIGNED_BYTE;
432 }
433
434 static int glifmt_from_imgfmt(Image::Format fmt)
435 {
436         switch(fmt) {
437         case Image::FMT_GREY:
438                 return GL_SLUMINANCE;
439         case Image::FMT_GREY_FLOAT:
440                 return GL_LUMINANCE16F_ARB;
441         case Image::FMT_RGB:
442                 return GL_SRGB;
443         case Image::FMT_RGB_FLOAT:
444                 return GL_RGB16F;
445         case Image::FMT_RGBA:
446                 return GL_SRGB_ALPHA;
447         case Image::FMT_RGBA_FLOAT:
448                 return GL_RGBA16F;
449         default:
450                 break;
451         }
452         return 0;
453 }
454
455 static unsigned int type_to_target(TextureType type)
456 {
457         return type == TEX_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
458 }
459
460 static TextureType target_to_type(unsigned int targ)
461 {
462         return targ == GL_TEXTURE_CUBE_MAP ? TEX_CUBE : TEX_2D;
463 }
464
465 // ---- TextureSet ----
466 TextureSet::TextureSet()
467         : DataSet<Texture*>(create_tex, load_tex, done_tex, free_tex)
468 {
469 }
470
471 Texture *TextureSet::get_texture(const char *name, TextureType type) const
472 {
473         std::map<std::string, Texture*>::const_iterator iter = data.find(name);
474         if(iter != data.end()) {
475                 return iter->second;
476         }
477
478         Texture *res = create();
479         data[name] = res;
480         res->create_default(type);
481         resman_lookup(rman, name, res);
482         return res;
483 }
484
485 // static callbacks
486
487 Texture *TextureSet::create_tex()
488 {
489         return new Texture;
490 }
491
492 bool TextureSet::load_tex(Texture *tex, const char *fname)
493 {
494         Image *img = new Image;
495         if(!img->load(fname)) {
496                 delete img;
497                 return false;
498         }
499
500         delete tex->img;
501         tex->img = img;
502
503         return true;
504 }
505
506 bool TextureSet::done_tex(Texture *tex)
507 {
508         if(!tex->img) {
509                 return false;
510         }
511
512         tex->set_image(*tex->img);
513         return true;
514 }
515
516 void TextureSet::free_tex(Texture *tex)
517 {
518         delete tex;
519 }