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