1 //********************************************************************************************
2 //*
3 //*    This file is part of the opengl extensions library. This library is
4 //*    distributed with Egoboo.
5 //*
6 //*    Egoboo is free software: you can redistribute it and/or modify it
7 //*    under the terms of the GNU General Public License as published by
8 //*    the Free Software Foundation, either version 3 of the License, or
9 //*    (at your option) any later version.
10 //*
11 //*    Egoboo is distributed in the hope that it will be useful, but
12 //*    WITHOUT ANY WARRANTY; without even the implied warranty of
13 //*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //*    General Public License for more details.
15 //*
16 //*    You should have received a copy of the GNU General Public License
17 //*    along with Egoboo.  If not, see <http://www.gnu.org/licenses/>.
18 //*
19 //********************************************************************************************
20 
21 /// @file extensions/ogl_texture.c
22 /// @ingroup _ogl_extensions_
23 /// @brief Implements OpenGL texture loading using SDL_image
24 /// @details Basic loading and managing OpenGL textures.
25 ///   Uses SDL_image to load .tif, .png, .bmp, .dib, .xpm, and other formats into OpenGL texures
26 
27 #include "ogl_texture.h"
28 #include "ogl_debug.h"
29 
30 #include "SDL_GL_extensions.h"
31 
32 #include "graphic.h"
33 
34 #include "egoboo_setup.h"
35 #include "egoboo_strutil.h"
36 
37 #include <SDL_image.h>
38 
39 //--------------------------------------------------------------------------------------------
40 //--------------------------------------------------------------------------------------------
41 #define VALID_VALUE        0x32B04E67
42 #define ErrorImage_width   2
43 #define ErrorImage_height  2
44 
45 #define VALID_TEXTURE( PTEX ) ( (NULL != (PTEX)) && (VALID_VALUE == (PTEX)->valid) )
46 
47 static GLuint ErrorImage_binding = INVALID_GL_ID;
48 
49 oglx_texture_parameters_t tex_params = {TX_UNFILTERED, 0};
50 
51 static GLboolean ErrorImage_defined = GL_FALSE;
52 
53 typedef GLubyte image_row_t[ErrorImage_width][4];
54 
55 static GLubyte ErrorImage[ErrorImage_height][ErrorImage_width][4];
56 
57 //--------------------------------------------------------------------------------------------
58 //--------------------------------------------------------------------------------------------
ErrorImage_create(void)59 void ErrorImage_create( void )
60 {
61     /// @details BB@> define a default "error texture"
62 
63     int i, j;
64 
65     if ( INVALID_GL_ID != ErrorImage_binding ) return;
66 
67     GL_DEBUG( glGenTextures )( 1, &ErrorImage_binding );
68 
69     for ( i = 0; i < ErrorImage_height; i++ )
70     {
71         for ( j = 0; j < ErrorImage_width; j++ )
72         {
73             if ( 0 == (( i&0x1 ) ^( j&0x1 ) ) )
74             {
75                 ErrorImage[i][j][0] = ( GLubyte ) 255;
76                 ErrorImage[i][j][1] = ( GLubyte ) 0;
77                 ErrorImage[i][j][2] = ( GLubyte ) 0;
78             }
79             else
80             {
81                 ErrorImage[i][j][0] = ( GLubyte ) 0;
82                 ErrorImage[i][j][1] = ( GLubyte ) 255;
83                 ErrorImage[i][j][2] = ( GLubyte ) 255;
84             }
85 
86             ErrorImage[i][j][3] = ( GLubyte ) 255;
87         }
88     }
89 
90     ErrorImage_bind( GL_TEXTURE_2D, ErrorImage_binding );
91 
92     ErrorImage_defined = GL_TRUE;
93 }
94 
95 //--------------------------------------------------------------------------------------------
ErrorImage_bind(GLenum target,GLuint id)96 void ErrorImage_bind( GLenum target, GLuint id )
97 {
98     // make sure the error texture exists
99     if ( !ErrorImage_defined ) ErrorImage_create();
100 
101     GL_DEBUG( glPushClientAttrib )( GL_CLIENT_PIXEL_STORE_BIT ) ;
102     {
103         GL_DEBUG( glPixelStorei )( GL_UNPACK_ALIGNMENT, 1 );
104 
105         GL_DEBUG( glBindTexture )( target, id );
106 
107         GL_DEBUG( glTexParameteri )( target, GL_TEXTURE_WRAP_S, GL_REPEAT );
108         GL_DEBUG( glTexParameteri )( target, GL_TEXTURE_WRAP_T, GL_REPEAT );
109         GL_DEBUG( glTexParameteri )( target, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
110         GL_DEBUG( glTexParameteri )( target, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
111         if ( target == GL_TEXTURE_1D )
112         {
113             GL_DEBUG( glTexImage1D )( GL_TEXTURE_1D, 0, GL_RGBA, ErrorImage_width, 0, GL_RGBA, GL_UNSIGNED_BYTE, ErrorImage );
114         }
115         else
116         {
117             GL_DEBUG( glTexImage2D )( GL_TEXTURE_2D, 0, GL_RGBA, ErrorImage_width, ErrorImage_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ErrorImage );
118         }
119     }
120     GL_DEBUG( glPopClientAttrib )();
121 }
122 
123 //--------------------------------------------------------------------------------------------
ErrorImage_get_binding()124 GLuint ErrorImage_get_binding()
125 {
126     // make sure the error texture exists
127     if ( !ErrorImage_defined ) ErrorImage_create();
128 
129     return ErrorImage_binding;
130 }
131 //--------------------------------------------------------------------------------------------
132 //--------------------------------------------------------------------------------------------
oglx_texture_ctor(oglx_texture_t * ptex)133 oglx_texture_t * oglx_texture_ctor( oglx_texture_t * ptex )
134 {
135     if ( NULL == ptex ) return ptex;
136 
137     memset( ptex, 0, sizeof( *ptex ) );
138 
139     // only need one base.binding per texture
140     // do not need to ask for a new id, even if we change the texture data
141     GL_DEBUG( glGenTextures )( 1, &( ptex->base.binding ) );
142 
143     // set the flag validity flag
144     if ( VALID_BINDING( ptex->base.binding ) && !ERROR_IMAGE_BINDING( ptex->base.binding ) )
145     {
146         ptex->valid = VALID_VALUE;
147     }
148     else
149     {
150         ptex->valid = ( GLuint )( ~VALID_VALUE );
151     }
152 
153     // set to 2d texture by default
154     ptex->base.target = GL_TEXTURE_2D;
155 
156     // set the image to be clamped in s and t
157     ptex->base.wrap_s = GL_CLAMP;
158     ptex->base.wrap_t = GL_CLAMP;
159 
160     return ptex;
161 }
162 
163 //--------------------------------------------------------------------------------------------
oglx_texture_dtor(oglx_texture_t * ptex)164 void oglx_texture_dtor( oglx_texture_t * ptex )
165 {
166     if ( !VALID_TEXTURE( ptex ) )  return;
167 
168     // set a bad value for ptex->valid
169     ptex->valid = ( GLuint )( ~VALID_VALUE );
170 
171     // actually delete the OpenGL texture data
172     if ( VALID_BINDING( ptex->base.binding ) )
173     {
174         GL_DEBUG( glDeleteTextures )( 1, &ptex->base.binding );
175         ptex->base.binding = INVALID_GL_ID;
176     }
177 
178     // set the image to be clamped in s and t
179     ptex->base.wrap_s = GL_CLAMP;
180     ptex->base.wrap_t = GL_CLAMP;
181 
182     // Reset the other data
183     ptex->imgH = ptex->imgW = ptex->base.width = ptex->base.height  = 0;
184     ptex->name[0] = '\0';
185 
186     if ( NULL != ptex->surface )
187     {
188         SDL_FreeSurface( ptex->surface );
189         ptex->surface = NULL;
190     }
191 }
192 
193 //--------------------------------------------------------------------------------------------
oglx_texture_Convert(oglx_texture_t * ptex,SDL_Surface * image,Uint32 key)194 GLuint oglx_texture_Convert( oglx_texture_t *ptex, SDL_Surface * image, Uint32 key )
195 {
196     /// @detalis BB@> an oglx_texture_t wrapper for the SDL_GL_convert_surface() function
197 
198     if ( NULL == ptex ) return INVALID_GL_ID;
199 
200     // make sure the old texture has been freed
201     oglx_texture_Release( ptex );
202 
203     if ( NULL == image ) return INVALID_GL_ID;
204 
205     /* set the color key, if valid */
206     if ( NULL != image->format && NULL != image->format->palette && INVALID_KEY != key )
207     {
208         SDL_SetColorKey( image, SDL_SRCCOLORKEY, key );
209     };
210 
211     // Determine the correct power of two greater than or equal to the original image's size
212     ptex->base.binding = SDL_GL_convert_surface( ptex->base.binding, image, ptex->base.wrap_s, ptex->base.wrap_t );
213 
214     ptex->base.target = (( 1 == image->h ) && ( image->w > 1 ) ) ? GL_TEXTURE_1D : GL_TEXTURE_2D;
215     ptex->base.height = powerOfTwo( image->h );
216     ptex->base.width  = powerOfTwo( image->w );
217 
218     // Set up some parameters for the format of the oglx_texture_t
219     ptex->base_valid = btrue;
220     ptex->surface    = image;
221     ptex->imgW       = image->w;
222     ptex->imgH       = image->h;
223     strncpy( ptex->name, "SDL_Surface()", SDL_arraysize( ptex->name ) );
224 
225     //// use the following command to grab every possible texture attribute in OpenGL v1.4 for
226     //// this texture. Useful for debugging
227     // ptex->base_valid = bfalse;
228     //oglx_grab_texture_state( tx_target, 0, ptex );
229 
230     return ptex->base.binding;
231 }
232 
233 //--------------------------------------------------------------------------------------------
IMG_test_alpha(SDL_Surface * psurf)234 SDL_bool IMG_test_alpha( SDL_Surface * psurf )
235 {
236     // test to see whether an image requires alpha blending
237 
238     // aliases
239     SDL_PixelFormat * pformat;
240 
241     // the color values
242     Uint32 pix_value;
243     Uint8  r, g, b, a;
244 
245     // the info necessary to scan the image
246     int bypp, bpp, bit_mask;
247     int w, h;
248     int pitch;
249 
250     // the location in the pixel data
251     int ix, iy;
252     const char * row_ptr;
253     const char * char_ptr;
254     Uint32     * ui32_ptr;
255 
256     if ( NULL == psurf ) return SDL_FALSE;
257 
258     // save this alias
259     pformat = psurf->format;
260 
261     // if the surface is tagged as having an alpha value,
262     // it is partially transparent
263     if ( 0xff != pformat->alpha )
264     {
265         return SDL_TRUE;
266     }
267 
268     // If it is an image without an alpha channel, then there is no alpha in the image
269     if ( NULL == pformat->palette && 0x00 == pformat->Amask )
270     {
271         return SDL_FALSE;
272     }
273 
274     // grab the info for scanning the surface
275     bpp  = pformat->BitsPerPixel;
276     bit_mask = pformat->Rmask | pformat->Gmask | pformat->Bmask | pformat->Amask;
277     bypp = pformat->BytesPerPixel;
278     w = psurf->w;
279     h = psurf->h;
280     pitch = psurf->pitch;
281 
282     row_ptr = ( const char * )psurf->pixels;
283     for ( iy = 0; iy < h; iy++ )
284     {
285         char_ptr = row_ptr;
286         for ( ix = 0; ix < w; ix++ )
287         {
288             ui32_ptr = ( Uint32 * )char_ptr;
289             pix_value = ( *ui32_ptr ) & bit_mask;
290 
291             SDL_GetRGBA( pix_value, pformat, &r, &g, &b, &a );
292 
293             if ( 0xFF != a )
294             {
295                 return SDL_TRUE;
296             }
297 
298             // advance to the next entry
299             char_ptr += bypp;
300         }
301         row_ptr += pitch;
302     }
303 
304     return SDL_FALSE;
305 }
306 
307 //--------------------------------------------------------------------------------------------
IMG_test_alpha_key(SDL_Surface * psurf,Uint32 key)308 SDL_bool IMG_test_alpha_key( SDL_Surface * psurf, Uint32 key )
309 {
310     // test to see whether an image requires alpha blending
311 
312     // aliases
313     SDL_PixelFormat * pformat;
314 
315     // the color values
316     Uint32 pix_value;
317     Uint8  r, g, b, a;
318 
319     // the info necessary to scan the image
320     int bypp, bpp, bit_mask;
321     int w, h;
322     int pitch;
323 
324     // the location in the pixel data
325     int ix, iy;
326     const char * row_ptr;
327     const char * char_ptr;
328     Uint32     * ui32_ptr;
329 
330     // flags
331     bool_t check_index = SDL_FALSE;
332 
333     if ( NULL == psurf ) return SDL_FALSE;
334 
335     // save this alias
336     pformat = psurf->format;
337 
338     // if there is no key specified (or the image has an alpha channel), use the basic version
339     if ( INVALID_KEY == key || pformat->Aloss < 8 )
340     {
341         return IMG_test_alpha( psurf );
342     }
343 
344     // if it is a colormapped image, there is one more test
345     check_index = SDL_FALSE;
346     if ( NULL != pformat->palette )
347     {
348         check_index = SDL_TRUE;
349     }
350 
351     // if the surface is tagged as having an alpha value,
352     // it is partially transparent
353     if ( 0xff != pformat->alpha ) return SDL_TRUE;
354 
355     // grab the info for scanning the surface
356     bpp  = pformat->BitsPerPixel;
357     bit_mask = pformat->Rmask | pformat->Gmask | pformat->Bmask | pformat->Amask;
358     bypp = pformat->BytesPerPixel;
359     w = psurf->w;
360     h = psurf->h;
361     pitch = psurf->pitch;
362 
363     row_ptr = ( const char * )psurf->pixels;
364     for ( iy = 0; iy < h; iy++ )
365     {
366         char_ptr = row_ptr;
367         for ( ix = 0; ix < w; ix++ )
368         {
369             ui32_ptr = ( Uint32 * )char_ptr;
370             pix_value = ( *ui32_ptr ) & bit_mask;
371 
372             if ( pix_value == key )
373             {
374                 return SDL_TRUE;
375             }
376             else
377             {
378                 SDL_GetRGBA( pix_value, pformat, &r, &g, &b, &a );
379                 if ( 0xFF != a )
380                 {
381                     return SDL_TRUE;
382                 }
383             }
384 
385             // advance to the next entry
386             char_ptr += bypp;
387         }
388         row_ptr += pitch;
389     }
390 
391     return SDL_FALSE;
392 }
393 
394 //--------------------------------------------------------------------------------------------
oglx_texture_Load(oglx_texture_t * ptex,const char * filename,Uint32 key)395 GLuint oglx_texture_Load( oglx_texture_t *ptex, const char *filename, Uint32 key )
396 {
397     GLuint        retval;
398     SDL_Surface * image;
399 
400     if ( VALID_TEXTURE( ptex ) )
401     {
402         // release any old texture
403         oglx_texture_Release( ptex );
404     }
405     else
406     {
407         // clean out any uninitialied data
408         ptex = oglx_texture_ctor( ptex );
409         if ( NULL == ptex ) return INVALID_GL_ID;
410     }
411 
412     image = IMG_Load( filename );
413     if ( NULL == image ) return INVALID_GL_ID;
414 
415     // test to see if the image requires alpha blanding
416     ptex->has_alpha = SDL_FALSE;
417     if ( INVALID_KEY == key )
418     {
419         ptex->has_alpha = IMG_test_alpha( image );
420     }
421     else
422     {
423         ptex->has_alpha = IMG_test_alpha_key( image, key );
424     }
425 
426     // upload the SDL_surface to OpenGL
427     retval = oglx_texture_Convert( ptex, image, key );
428 
429     if ( !VALID_BINDING( retval ) )
430     {
431         oglx_texture_dtor( ptex );
432     }
433     else
434     {
435         strncpy( ptex->name, filename, SDL_arraysize( ptex->name ) );
436 
437         ptex->base.wrap_s = GL_REPEAT;
438         ptex->base.wrap_t = GL_REPEAT;
439     }
440 
441     return retval;
442 }
443 
444 //--------------------------------------------------------------------------------------------
oglx_texture_GetTextureID(oglx_texture_t * texture)445 GLuint  oglx_texture_GetTextureID( oglx_texture_t *texture )
446 {
447     return ( NULL == texture ) ? INVALID_GL_ID : texture->base.binding;
448 }
449 
450 //--------------------------------------------------------------------------------------------
oglx_texture_GetImageHeight(oglx_texture_t * texture)451 GLsizei  oglx_texture_GetImageHeight( oglx_texture_t *texture )
452 {
453     return ( NULL == texture ) ? 0 : texture->imgH;
454 }
455 
456 //--------------------------------------------------------------------------------------------
oglx_texture_GetImageWidth(oglx_texture_t * texture)457 GLsizei  oglx_texture_GetImageWidth( oglx_texture_t *texture )
458 {
459     return ( NULL == texture ) ? 0 : texture->imgW;
460 }
461 
462 //--------------------------------------------------------------------------------------------
oglx_texture_GetTextureWidth(oglx_texture_t * texture)463 GLsizei  oglx_texture_GetTextureWidth( oglx_texture_t *texture )
464 {
465     return ( NULL == texture ) ? 0 : texture->base.width;
466 }
467 
468 //--------------------------------------------------------------------------------------------
oglx_texture_GetTextureHeight(oglx_texture_t * texture)469 GLsizei  oglx_texture_GetTextureHeight( oglx_texture_t *texture )
470 {
471     return ( NULL == texture ) ? 0 : texture->base.height;
472 }
473 
474 //--------------------------------------------------------------------------------------------
oglx_texture_Release(oglx_texture_t * texture)475 void  oglx_texture_Release( oglx_texture_t *texture )
476 {
477     if ( !VALID_TEXTURE( texture ) ) return;
478 
479     // delete any existing SDL surface
480     if ( NULL != texture->surface )
481     {
482         SDL_FreeSurface( texture->surface );
483         texture->surface = NULL;
484     }
485 
486     // try to get rid of any stored texture data for this texture
487     GL_DEBUG( glDeleteTextures )( 1, &( texture->base.binding ) );
488 
489     if ( !ErrorImage_defined ) ErrorImage_create();
490 
491     // generate a new texture binding
492     GL_DEBUG( glGenTextures )( 1, &( texture->base.binding ) );
493 
494     // Bind the error texture instead of the old texture
495     ErrorImage_bind( texture->base.target, texture->base.binding );
496 
497     // Reset the other data
498     texture->imgW = texture->base.width = ErrorImage_width;
499     texture->imgH = texture->base.height = ErrorImage_height;
500     strncpy( texture->name, "ErrorImage", sizeof( texture->name ) );
501 
502     // set the image to be repeat in s and t
503     texture->base.wrap_s = GL_REPEAT;
504     texture->base.wrap_t = GL_REPEAT;
505 
506     //// use the following command to grab every possible texture attribute in OpenGL v1.4 for
507     //// this texture. Useful for debugging
508     //oglx_grab_texture_state( GL_TEXTURE_2D, 0, texture );
509 }
510 
511 //--------------------------------------------------------------------------------------------
oglx_texture_Bind(oglx_texture_t * texture)512 void oglx_texture_Bind( oglx_texture_t *texture )
513 {
514     /// @details BB@> a oglx_texture_t wrapper for oglx_bind_to_tex_params() function
515 
516     GLenum target;
517     GLuint id;
518     GLint wrap_s, wrap_t;
519 
520     // assume the texture is going to be the error texture
521     target = GL_TEXTURE_2D;
522     wrap_s = wrap_t = GL_REPEAT;
523     id     = ErrorImage_binding;
524 
525     if ( NULL == texture )
526     {
527         // NULL texture means white blob
528         id = INVALID_GL_ID;
529     }
530     else if ( VALID_TEXTURE( texture ) && VALID_BINDING( texture->base.binding ) )
531     {
532         // grab the info from the texture
533         target = texture->base.target;
534         id     = texture->base.binding;
535         wrap_s = texture->base.wrap_s;
536         wrap_t = texture->base.wrap_t;
537     }
538 
539     // upload the texture
540     id = oglx_bind_to_tex_params( id, target, wrap_s, wrap_t );
541 
542     // if the texture binding changed, upload the change.
543     if ( VALID_TEXTURE( texture ) )
544     {
545         texture->base.binding = id;
546     }
547 
548     // use the following command to grab every possible texture attribute in OpenGL v1.4 for
549     // this texture. Useful for debugging
550     //if ( NULL != texture && !texture->base_valid )
551     //{
552     //    oglx_grab_texture_state( target, 0, texture );
553     //}
554 }
555 
556 //--------------------------------------------------------------------------------------------
oglx_texture_Valid(oglx_texture_t * ptex)557 GLboolean oglx_texture_Valid( oglx_texture_t *ptex )
558 {
559     return VALID_TEXTURE( ptex );
560 }
561 
562 //--------------------------------------------------------------------------------------------
oglx_grab_texture_state(GLenum target,GLint level,oglx_texture_t * texture)563 void oglx_grab_texture_state( GLenum target, GLint level, oglx_texture_t * texture )
564 {
565     if ( NULL == texture ) return;
566 
567     gl_grab_texture_state( target, level, &( texture->base ) );
568 
569     texture->base_valid = GL_TRUE;
570 }
571 
572 //--------------------------------------------------------------------------------------------
573 //--------------------------------------------------------------------------------------------
oglx_bind_to_tex_params(GLuint binding,GLenum target,GLint wrap_s,GLint wrap_t)574 GLuint oglx_bind_to_tex_params( GLuint binding, GLenum target, GLint wrap_s, GLint wrap_t )
575 {
576     int    filt_type, anisotropy;
577 
578     GLuint local_binding;
579 
580     // make sure the error texture exists
581     if ( !ErrorImage_defined ) ErrorImage_create();
582 
583     // handle default parameters
584     if ( wrap_s < 0 ) wrap_s = GL_REPEAT;
585     if ( wrap_t < 0 ) wrap_t = GL_REPEAT;
586 
587     local_binding = VALID_BINDING( binding ) ? binding : ErrorImage_binding;
588 
589     filt_type  = tex_params.texturefilter;
590     anisotropy = tex_params.userAnisotropy;
591 
592     if ( !GL_DEBUG( glIsEnabled )( target ) )
593     {
594         GL_DEBUG( glEnable )( target );
595     };
596 
597     if ( filt_type >= TX_ANISOTROPIC )
598     {
599         // Anisotropic filtered!
600         oglx_bind( target, local_binding, wrap_s, wrap_t, GL_LINEAR, GL_LINEAR, anisotropy );
601     }
602     else
603     {
604         switch ( filt_type )
605         {
606                 // Unfiltered
607             case TX_UNFILTERED:
608                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_NEAREST, GL_LINEAR, 0 );
609                 break;
610 
611                 // Linear filtered
612             case TX_LINEAR:
613                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_LINEAR, GL_LINEAR, 0 );
614                 break;
615 
616                 // Bilinear interpolation
617             case TX_MIPMAP:
618                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR, 0 );
619                 break;
620 
621                 // Bilinear interpolation
622             case TX_BILINEAR:
623                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, 0 );
624                 break;
625 
626                 // Trilinear filtered (quality 1)
627             case TX_TRILINEAR_1:
628                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR, 0 );
629                 break;
630 
631                 // Trilinear filtered (quality 2)
632             case TX_TRILINEAR_2:
633                 oglx_bind( target, local_binding, wrap_s, wrap_t, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, 0 );
634                 break;
635         };
636     }
637 
638     return local_binding;
639 }