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