2005-09-29 07:00:20 +02:00
// OpenGL texture API
//
// Copyright (c) 2003-2005 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
2004-06-26 00:18:03 +02:00
# include "precompiled.h"
2005-09-29 07:00:20 +02:00
# include "lib.h"
2005-08-12 19:06:53 +02:00
# include "../res.h"
2004-10-06 16:00:43 +02:00
# include "ogl.h"
2004-09-19 13:36:26 +02:00
# include "tex.h"
# include "ogl_tex.h"
2004-06-26 00:18:03 +02:00
2005-03-10 18:04:42 +01:00
2005-09-21 18:11:43 +02:00
//----------------------------------------------------------------------------
2005-03-10 18:04:42 +01:00
// OpenGL helper routines
2005-09-21 18:11:43 +02:00
//----------------------------------------------------------------------------
2005-03-10 18:04:42 +01:00
2005-09-29 07:00:20 +02:00
static bool filter_valid ( GLint filter )
2005-08-09 18:23:19 +02:00
{
switch ( filter )
{
case GL_NEAREST :
case GL_LINEAR :
case GL_NEAREST_MIPMAP_NEAREST :
case GL_LINEAR_MIPMAP_NEAREST :
case GL_NEAREST_MIPMAP_LINEAR :
case GL_LINEAR_MIPMAP_LINEAR :
return true ;
2005-09-29 07:00:20 +02:00
default :
return false ;
}
}
static bool wrap_valid ( GLint wrap )
{
switch ( wrap )
{
case GL_CLAMP :
case GL_CLAMP_TO_EDGE :
case GL_CLAMP_TO_BORDER :
case GL_REPEAT :
case GL_MIRRORED_REPEAT :
return true ;
default :
return false ;
2005-08-09 18:23:19 +02:00
}
2005-03-10 18:04:42 +01:00
}
2004-06-26 00:18:03 +02:00
2005-03-10 18:04:42 +01:00
static bool filter_uses_mipmaps ( GLint filter )
{
switch ( filter )
2004-06-26 00:18:03 +02:00
{
2005-03-10 18:04:42 +01:00
case GL_NEAREST_MIPMAP_NEAREST :
case GL_LINEAR_MIPMAP_NEAREST :
case GL_NEAREST_MIPMAP_LINEAR :
case GL_LINEAR_MIPMAP_LINEAR :
return true ;
default :
return false ;
}
}
2005-09-29 19:33:30 +02:00
// determine OpenGL texture format, given <bpp> and Tex <flags>.
static GLint choose_fmt ( uint bpp , uint flags )
{
const bool alpha = ( flags & TEX_ALPHA ) ! = 0 ;
const bool bgr = ( flags & TEX_BGR ) ! = 0 ;
const bool grey = ( flags & TEX_GREY ) ! = 0 ;
const uint dxt = flags & TEX_DXT ;
// S3TC
if ( dxt ! = 0 )
{
switch ( dxt )
{
case 1 :
return alpha ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT ;
case 3 :
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ;
case 5 :
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ;
default :
debug_warn ( " choose_fmt: invalid DXT value " ) ;
return 0 ;
}
}
// uncompressed
switch ( bpp )
{
case 8 :
debug_assert ( grey ) ;
return GL_LUMINANCE ;
case 16 :
return GL_LUMINANCE_ALPHA ;
case 24 :
debug_assert ( ! alpha ) ;
return bgr ? GL_BGR : GL_RGB ;
case 32 :
debug_assert ( alpha ) ;
return bgr ? GL_BGRA : GL_RGBA ;
default :
debug_warn ( " choose_fmt: invalid bpp " ) ;
return 0 ;
}
UNREACHABLE ;
}
2005-09-29 07:00:20 +02:00
//----------------------------------------------------------------------------
// quality mechanism
//----------------------------------------------------------------------------
static GLint default_filter = GL_LINEAR ; // one of the GL *minify* filters
static uint default_q_flags = OGL_TEX_FULL_QUALITY ; // OglTexQualityFlags
static bool q_flags_valid ( uint q_flags )
{
const uint bits = OGL_TEX_FULL_QUALITY | OGL_TEX_HALF_BPP | OGL_TEX_HALF_RES ;
// unrecognized bits are set - invalid
if ( ( q_flags & ~ bits ) ! = 0 )
return false ;
// "full quality" but other reduction bits are set - invalid
if ( q_flags & OGL_TEX_FULL_QUALITY & & q_flags & ~ OGL_TEX_FULL_QUALITY )
return false ;
return true ;
}
// change default settings - these affect performance vs. quality.
// may be overridden for individual textures via parameter to
// ogl_tex_upload or ogl_tex_set_filter, respectively.
//
// pass 0 to keep the current setting; defaults and legal values are:
// - q_flags: OGL_TEX_FULL_QUALITY; combination of OglTexQualityFlags
// - filter: GL_LINEAR; any valid OpenGL minification filter
void ogl_tex_set_defaults ( uint q_flags , GLint filter )
{
if ( q_flags )
{
debug_assert ( q_flags_valid ( q_flags ) ) ;
default_q_flags = q_flags ;
}
if ( filter )
{
debug_assert ( filter_valid ( filter ) ) ;
default_filter = filter ;
}
}
2005-09-29 19:33:30 +02:00
// choose an internal format for <fmt> based on the given q_flags.
static GLint choose_int_fmt ( GLenum fmt , uint q_flags )
2005-03-10 18:04:42 +01:00
{
2005-09-29 07:00:20 +02:00
// true => 4 bits per component; otherwise, 8
const bool half_bpp = ( q_flags & OGL_TEX_HALF_BPP ) ! = 0 ;
2005-03-10 18:04:42 +01:00
2005-09-29 19:33:30 +02:00
// early-out for S3TC textures. they don't need an internal format
// (because upload is via glCompressedTexImage2DARB), but we return
// a meaningful value anyway and avoid triggering the warning below.
if ( ogl_dxt_from_fmt ( fmt ) )
return fmt ;
2005-03-10 18:04:42 +01:00
2005-09-29 19:33:30 +02:00
switch ( fmt )
2005-03-10 18:04:42 +01:00
{
2005-09-29 19:33:30 +02:00
// 8bpp
case GL_LUMINANCE :
return half_bpp ? GL_LUMINANCE4 : GL_LUMINANCE8 ;
case GL_INTENSITY :
return half_bpp ? GL_INTENSITY4 : GL_INTENSITY8 ;
case GL_ALPHA :
return half_bpp ? GL_ALPHA4 : GL_ALPHA8 ;
// 16bpp
case GL_LUMINANCE_ALPHA :
return half_bpp ? GL_LUMINANCE4_ALPHA4 : GL_LUMINANCE8_ALPHA8 ;
// 24bpp
case GL_RGB :
case GL_BGR : // note: BGR can't be used as internal format
return half_bpp ? GL_RGB4 : GL_RGB8 ;
// 32bpp
case GL_RGBA :
case GL_BGRA : // note: BGRA can't be used as internal format
return half_bpp ? GL_RGBA4 : GL_RGBA8 ;
2004-06-26 00:18:03 +02:00
2005-03-10 18:04:42 +01:00
default :
2005-09-29 19:33:30 +02:00
debug_warn ( " choose_int_fmt doesn't cover the given fmt! please add it. " ) ;
// fall back to a reasonable default
return half_bpp ? GL_RGB4 : GL_RGB8 ;
2004-06-26 00:18:03 +02:00
}
2005-08-09 18:23:19 +02:00
UNREACHABLE ;
2005-03-10 18:04:42 +01:00
}
2005-09-29 07:00:20 +02:00
//----------------------------------------------------------------------------
// texture state to allow seamless reload
//----------------------------------------------------------------------------
2005-03-10 18:04:42 +01:00
2005-09-29 07:00:20 +02:00
// see "Texture Parameters" in docs.
2005-03-10 18:04:42 +01:00
2005-09-29 07:00:20 +02:00
// all GL state tied to the texture that must be reapplied after reload.
// (this mustn't get too big, as it's stored in the already sizeable OglTex)
struct OglTexState
{
// glTexParameter
// note: there are more options, but they do not look to
// be important and will not be applied after a reload!
// in particular, LOD_BIAS isn't needed because that is set for
// the entire texturing unit via glTexEnv.
// .. texture filter
// note: this is the minification filter value; magnification filter
// is GL_NEAREST if it's GL_NEAREST, otherwise GL_LINEAR.
// we don't store mag_filter explicitly because it
// doesn't appear useful - either apps can tolerate LINEAR, or
// mipmaps aren't called for and filter could be NEAREST anyway).
GLint filter ;
// .. wrap mode
// note: to simplify things, we assume that apps will never want to
// set S/T modes independently. it that becomes necessary,
// it's easy to add.
GLint wrap ;
} ;
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
// fill the given state object with default values.
static void state_set_to_defaults ( OglTexState * ots )
{
ots - > filter = default_filter ;
ots - > wrap = GL_REPEAT ;
}
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// send all state to OpenGL (actually the currently bound texture).
// called from ogl_tex_upload.
static void state_latch ( OglTexState * ots )
2005-09-21 18:11:43 +02:00
{
2005-09-29 07:00:20 +02:00
// filter
const GLint filter = ots - > filter ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , filter ) ;
const GLint mag_filter = ( filter = = GL_NEAREST ) ? GL_NEAREST : GL_LINEAR ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , mag_filter ) ;
// wrap
const GLint wrap = ots - > wrap ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , wrap ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , wrap ) ;
// .. only CLAMP and REPEAT are guaranteed to be available.
// if we're using one of the others, we squelch the error that
// may have resulted if this GL implementation is old.
if ( wrap ! = GL_CLAMP & & wrap ! = GL_REPEAT )
oglSquelchError ( GL_INVALID_ENUM ) ;
2005-09-21 18:11:43 +02:00
}
//----------------------------------------------------------------------------
2005-09-29 07:00:20 +02:00
// texture resource object
2005-09-21 18:11:43 +02:00
//----------------------------------------------------------------------------
// ideally we would split OglTex into data and state objects as in
// snd.cpp's SndData / VSrc. this gives us the benefits of caching while
// still leaving each "instance" (state object, which owns a data reference)
// free to change its state. however, unlike in OpenAL, there is no state
// independent of the data object - all parameters are directly tied to the
// GL texture object. therefore, splitting them up is impossible.
// (we shouldn't even keep the texel data in memory since that's already
// covered by the FS cache).
2005-03-10 18:04:42 +01:00
//
2005-09-21 18:11:43 +02:00
// given that multiple "instances" share the state stored here, we conclude:
// - a refcount is necessary to prevent ogl_tex_upload from freeing
// <t> as long as other instances are active.
// - concurrent use risks cross-talk (if the 2nd "instance" changes state and
// the first is reloaded, its state may change to that of the 2nd)
//
2005-09-29 07:00:20 +02:00
// as bad as it sounds, the latter issue isn't a problem: we do not expect
// multiple instances of the same texture where someone changes its filter.
// even if it is reloaded, the differing state is not critical.
2005-09-21 18:11:43 +02:00
// the alternative is even worse: disabling *all* caching/reuse would
2005-09-29 07:00:20 +02:00
// really hurt performance and h_mgr doesn't support only disallowing
// reuse of active objects (this would break the index lookup code, since
// multiple instances may then exist).
2005-03-10 18:04:42 +01:00
2005-09-02 04:54:02 +02:00
struct OglTex
2004-06-26 00:18:03 +02:00
{
2005-09-02 04:54:02 +02:00
Tex t ;
2004-06-26 00:18:03 +02:00
2005-09-21 18:11:43 +02:00
// allocated by OglTex_reload; indicates the texture is currently uploaded.
2004-06-26 00:18:03 +02:00
GLuint id ;
2005-09-29 19:33:30 +02:00
// ogl_tex_upload calls choose_fmt to determine these from <t>.
2005-09-29 07:00:20 +02:00
// however, its caller may override those values via parameters.
// note: these are stored here to allow retrieving via ogl_tex_get_format;
// they are only used within ogl_tex_upload.
2005-03-10 18:04:42 +01:00
GLenum fmt ;
2005-09-02 04:54:02 +02:00
GLint int_fmt ;
2004-08-28 19:44:28 +02:00
2005-09-29 07:00:20 +02:00
OglTexState state ;
// OglTexQualityFlags
uint q_flags : 8 ;
// to which Texture Mapping Unit was this bound?
uint tmu : 8 ;
2005-03-10 18:04:42 +01:00
2005-09-29 19:33:30 +02:00
// flags influencing reload() behavior
// .. reload() should be a no-op, because we either have the texture in
// memory or it's been uploaded to OpenGL. reset in dtor.
2005-09-21 18:11:43 +02:00
uint is_loaded : 1 ;
2005-09-29 19:33:30 +02:00
// .. reload() should automatically re-upload the texture, because
// it had been uploaded before the reload.
uint need_auto_upload : 1 ;
// .. reload() doesn't need to load from disk, because <t> already
// contains the texture data. note: in this case, actual reloads are
// disallowed by ogl_tex_wrap, but we still need this flag to
// correctly handle the reload() triggered by h_alloc.
2005-09-21 18:11:43 +02:00
uint was_wrapped : 1 ;
2005-09-29 19:33:30 +02:00
// indicates if this texture is currently uploaded.
// used by state setters; reset in dtor.
uint is_currently_uploaded : 1 ;
2004-06-26 00:18:03 +02:00
} ;
2005-09-02 04:54:02 +02:00
H_TYPE_DEFINE ( OglTex ) ;
2004-06-26 00:18:03 +02:00
2005-09-20 00:48:20 +02:00
static void OglTex_init ( OglTex * ot , va_list args )
2004-06-26 00:18:03 +02:00
{
2005-09-20 00:48:20 +02:00
Tex * wrapped_tex = va_arg ( args , Tex * ) ;
if ( wrapped_tex )
{
2005-09-29 07:00:20 +02:00
// note: this only happens once; ogl_tex_wrap makes sure
// this OglTex cannot be reloaded, so it's safe.
2005-09-20 00:48:20 +02:00
ot - > t = * wrapped_tex ;
2005-09-21 18:11:43 +02:00
ot - > was_wrapped = 1 ;
2005-09-20 00:48:20 +02:00
}
2005-09-29 07:00:20 +02:00
state_set_to_defaults ( & ot - > state ) ;
ot - > q_flags = default_q_flags ;
2004-06-26 00:18:03 +02:00
}
2005-09-02 04:54:02 +02:00
static void OglTex_dtor ( OglTex * ot )
2004-06-26 00:18:03 +02:00
{
2005-09-20 01:40:33 +02:00
( void ) tex_free ( & ot - > t ) ;
2004-06-26 00:18:03 +02:00
2005-09-02 04:54:02 +02:00
glDeleteTextures ( 1 , & ot - > id ) ;
ot - > id = 0 ;
2004-08-28 19:44:28 +02:00
2005-09-29 07:00:20 +02:00
ot - > is_currently_uploaded = 0 ;
2004-08-28 19:44:28 +02:00
// need to clear this so actual reloads (triggered by h_reload)
// actually reload.
2005-09-21 18:11:43 +02:00
ot - > is_loaded = 0 ;
2004-06-26 00:18:03 +02:00
}
2005-09-02 04:54:02 +02:00
static int OglTex_reload ( OglTex * ot , const char * fn , Handle h )
2004-06-26 00:18:03 +02:00
{
2005-09-29 07:00:20 +02:00
// make sure the texture has been loaded
// .. already done (<h> had been freed but not yet unloaded)
2005-09-02 04:54:02 +02:00
if ( ot - > is_loaded )
2004-06-26 00:18:03 +02:00
return 0 ;
2005-09-29 07:00:20 +02:00
// .. load from file - but only if we weren't wrapping an existing
// Tex object (i.e. copy its values and be done).
2005-09-20 00:48:20 +02:00
if ( ! ot - > was_wrapped )
2005-09-21 18:11:43 +02:00
RETURN_ERR ( tex_load ( fn , & ot - > t ) ) ;
ot - > is_loaded = 1 ;
2004-06-26 00:18:03 +02:00
2005-09-02 04:54:02 +02:00
glGenTextures ( 1 , & ot - > id ) ;
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
// if it had already been uploaded before this reload,
// re-upload it (this also does state_latch).
2005-09-29 19:33:30 +02:00
if ( ot - > need_auto_upload )
2005-09-29 07:00:20 +02:00
( void ) ogl_tex_upload ( h ) ;
2004-06-26 00:18:03 +02:00
return 0 ;
}
2005-09-21 18:11:43 +02:00
// load and return a handle to the texture given in <fn>.
// for a list of supported formats, see tex.h's tex_load.
Handle ogl_tex_load ( const char * fn , uint flags )
2004-06-26 00:18:03 +02:00
{
2005-09-20 00:48:20 +02:00
Tex * wrapped_tex = 0 ; // we're loading from file
2005-09-21 18:11:43 +02:00
return h_alloc ( H_OglTex , fn , flags , wrapped_tex ) ;
2005-09-20 00:48:20 +02:00
}
2005-09-21 18:11:43 +02:00
// make the given Tex object ready for use as an OpenGL texture
// and return a handle to it. this will be as if its contents
// had been loaded by ogl_tex_load.
//
// we need only add bookkeeping information and "wrap" it in
// a resource object (accessed via Handle), hence the name.
//
// <fn> isn't strictly needed but should describe the texture so that
// h_filename will return a meaningful comment for debug purposes.
2005-10-03 14:57:31 +02:00
// note: because we cannot guarantee that callers will pass distinct
// "filenames", caching is disabled for the created object. this avoids
// mistakenly reusing previous objects that share the same comment.
2005-09-21 18:11:43 +02:00
Handle ogl_tex_wrap ( Tex * t , const char * fn , uint flags )
2005-09-20 00:48:20 +02:00
{
2005-09-29 07:00:20 +02:00
// this object may not be backed by a file ("may", because
// someone could do tex_load and then ogl_tex_wrap).
// if h_mgr asks for a reload, the dtor will be called but
// we won't be able to reconstruct it. therefore, disallow reloads.
// (they are improbable anyway since caller is supposed to pass a
// 'descriptive comment' instead of filename, but don't rely on that)
2005-10-03 14:57:31 +02:00
// also disable caching as explained above.
flags | = RES_DISALLOW_RELOAD | RES_NO_CACHE ;
2005-09-21 18:11:43 +02:00
return h_alloc ( H_OglTex , fn , flags , t ) ;
2004-06-26 00:18:03 +02:00
}
2005-09-21 18:11:43 +02:00
// free all resources associated with the texture and make further
// use of it impossible. (subject to refcount)
2005-09-02 04:54:02 +02:00
int ogl_tex_free ( Handle & ht )
2004-06-26 00:18:03 +02:00
{
2005-09-02 04:54:02 +02:00
return h_free ( ht , H_OglTex ) ;
2004-06-26 00:18:03 +02:00
}
2005-09-02 04:54:02 +02:00
static int ogl_tex_validate ( const uint line , const OglTex * ot )
2004-06-26 00:18:03 +02:00
{
2005-09-21 18:11:43 +02:00
RETURN_ERR ( tex_validate ( line , & ot - > t ) ) ;
2004-06-26 00:18:03 +02:00
const char * msg = 0 ;
int err = - 1 ;
// width, height
2005-09-20 01:40:33 +02:00
// (note: this is done here because tex.cpp doesn't impose any
// restrictions on dimensions, while OpenGL does).
2005-09-02 04:54:02 +02:00
GLsizei w = ( GLsizei ) ot - > t . w ;
GLsizei h = ( GLsizei ) ot - > t . h ;
2005-09-29 07:00:20 +02:00
// .. if w or h is 0, texture file probably not loaded successfully.
2004-06-26 00:18:03 +02:00
if ( w = = 0 | | h = = 0 )
msg = " width or height is 0 - texture probably not loaded successfully " ;
2005-09-29 07:00:20 +02:00
// .. greater than max supported tex dimension?
// no-op if oglInit not yet called
2005-06-16 23:52:35 +02:00
if ( w > ( GLsizei ) ogl_max_tex_size | | h > ( GLsizei ) ogl_max_tex_size )
2004-06-26 00:18:03 +02:00
msg = " texture dimensions exceed OpenGL implementation limit " ;
2005-09-29 07:00:20 +02:00
// .. both NV_texture_rectangle and subtexture require work for the client
// (changing tex coords) => we'll just disallow non-power of 2 textures.
// TODO: ARB_texture_non_power_of_two
2004-06-26 00:18:03 +02:00
if ( ! is_pow2 ( w ) | | ! is_pow2 ( h ) )
msg = " width or height is not a power-of-2 " ;
2005-09-29 19:33:30 +02:00
// texture state
2005-09-29 07:00:20 +02:00
GLint filter = ot - > state . filter ;
GLint wrap = ot - > state . wrap ;
2005-09-29 19:33:30 +02:00
if ( ! filter_valid ( filter ) )
2005-08-09 18:23:19 +02:00
msg = " invalid filter " ;
2005-09-29 19:33:30 +02:00
if ( ! wrap_valid ( wrap ) )
2005-09-29 07:00:20 +02:00
msg = " invalid wrap mode " ;
2005-09-29 19:33:30 +02:00
// misc
2005-09-29 07:00:20 +02:00
if ( ! q_flags_valid ( ot - > q_flags ) )
msg = " invalid q_flags " ;
2005-09-04 22:49:58 +02:00
if ( ot - > tmu > = 128 )
msg = " TMU invalid? it's >= 128! " ;
2005-09-29 07:00:20 +02:00
// .. note: don't check ot->fmt and ot->int_fmt - they aren't set
// until during ogl_tex_upload.
2004-06-26 00:18:03 +02:00
if ( msg )
{
2005-09-20 01:40:33 +02:00
debug_printf ( " ogl_tex_validate at line %d failed: %s (error code %d) \n " , line , msg , err ) ;
debug_warn ( " ogl_tex_validate failed " ) ;
2004-06-26 00:18:03 +02:00
return err ;
}
return 0 ;
}
2005-09-21 18:11:43 +02:00
# define CHECK_OGL_TEX(ot) CHECK_ERR(ogl_tex_validate(__LINE__, ot))
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
//----------------------------------------------------------------------------
// state setters (see "Texture Parameters" in docs)
2005-09-21 18:11:43 +02:00
2005-09-29 19:33:30 +02:00
// we require the below functions be called before uploading; this avoids
// redundant glTexParameter calls (we'd otherwise need to always
// set defaults because we don't know if an override is forthcoming).
//
// this raises a warning for purposes of debugging if the texture has
// already been uploaded (in violation of the above requirement).
2005-09-29 07:00:20 +02:00
static void warn_if_uploaded ( Handle ht , const OglTex * ot )
{
2005-09-29 19:33:30 +02:00
# ifndef NDEBUG
// note: if a texture is ogl_tex_load-ed several times, each caller
// (typically a higher-level LoadTexture) will want to set
// e.g. its filter. however, after the first time, the texture will
// have been uploaded, making this pointless (and illegal).
//
// we do not want to complain, though, since the caller's intent is
// legitimate. ideally LoadTexture would take care of this by
// skipping the set/upload calls if the texture wasn't newly loaded,
// but we'll also have to work around this here to be safe.
//
// unfortunately, h_alloc provides no direct way of notifying its
// caller whether it actually loaded anew or reactivated a
// cached resource. we have to check the reference count.
2005-09-29 07:00:20 +02:00
int refs = h_get_refcnt ( ht ) ;
if ( refs = = 1 & & ot - > is_currently_uploaded )
debug_warn ( " ogl_tex_set_*: texture already uploaded and shouldn't be changed " ) ;
2005-09-29 19:33:30 +02:00
# else
// (prevent warnings; the alternative of wrapping all call sites in
// #ifndef is worse)
UNUSED2 ( ht ) ;
UNUSED2 ( ot ) ;
# endif
2005-09-29 07:00:20 +02:00
}
// override default filter (as set above) for this texture.
// must be called before uploading (raises a warning if called afterwards).
// filter is as defined by OpenGL; it is applied for both minification and
// magnification (for rationale and details, see OglTexState)
int ogl_tex_set_filter ( Handle ht , GLint filter )
2004-06-26 00:18:03 +02:00
{
2005-09-02 04:54:02 +02:00
H_DEREF ( ht , OglTex , ot ) ;
2005-09-21 18:11:43 +02:00
CHECK_OGL_TEX ( ot ) ;
2005-03-10 18:04:42 +01:00
2005-09-29 07:00:20 +02:00
if ( ! filter_valid ( filter ) )
CHECK_ERR ( ERR_INVALID_PARAM ) ;
warn_if_uploaded ( ht , ot ) ;
ot - > state . filter = filter ;
return 0 ;
}
2005-03-10 18:04:42 +01:00
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
// override default wrap mode (GL_REPEAT) for this texture.
// must be called before uploading (raises a warning if called afterwards).
// wrap is as defined by OpenGL and applies to both S and T coordinates
// (rationale: see OglTexState).
int ogl_tex_set_wrap ( Handle ht , GLint wrap )
{
H_DEREF ( ht , OglTex , ot ) ;
CHECK_OGL_TEX ( ot ) ;
if ( ! wrap_valid ( wrap ) )
CHECK_ERR ( ERR_INVALID_PARAM ) ;
warn_if_uploaded ( ht , ot ) ;
ot - > state . wrap = wrap ;
return 0 ;
}
//----------------------------------------------------------------------------
// upload
// bind the texture to the specified unit [number] in preparation for
// using it in rendering. assumes multitexturing is available.
// not necessary before calling ogl_tex_upload!
// side effects:
// - enables (or disables, if <ht> == 0) texturing on the given unit.
//
// note: there are many call sites of glActiveTextureARB, so caching
// those and ignoring redundant sets isn't feasible.
int ogl_tex_bind ( const Handle ht , GLenum unit )
{
int id = 0 ;
// special case: avoid dereference and disable texturing directly.
if ( ht = = 0 )
goto disable_texturing ;
2004-06-26 00:18:03 +02:00
{
2005-09-29 07:00:20 +02:00
// (we can't use H_DEREF because it exits immediately)
OglTex * ot = H_USER_DATA ( ht , OglTex ) ;
if ( ! ot )
{
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
CHECK_ERR ( ERR_INVALID_HANDLE ) ;
UNREACHABLE ;
}
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
CHECK_OGL_TEX ( ot ) ;
# ifndef NDEBUG
if ( ! ot - > id )
{
debug_warn ( " ogl_tex_bind: OglTex.id is not a valid texture " ) ;
return - 1 ;
}
# endif
2005-03-10 18:04:42 +01:00
2005-09-29 07:00:20 +02:00
id = ot - > id ;
ot - > tmu = unit ;
}
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
disable_texturing :
glActiveTextureARB ( GL_TEXTURE0 + unit ) ;
if ( id )
{
glEnable ( GL_TEXTURE_2D ) ;
glBindTexture ( GL_TEXTURE_2D , id ) ;
}
else
glDisable ( GL_TEXTURE_2D ) ;
return 0 ;
}
2005-03-10 18:04:42 +01:00
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
// this has gotten to be quite complex. due to various fallbacks, we
// implement this as an automaton with the following states:
enum UploadState
{
auto_uncomp , auto_comp ,
mipped_uncomp , mipped_comp ,
normal_uncomp , normal_comp ,
broken_comp ,
glubuild
} ;
2004-06-26 00:18:03 +02:00
2005-09-29 07:00:20 +02:00
// use parameters describing a texture to decide which upload method is
// called for; returns one of the above "states".
static UploadState determine_upload_state ( GLenum fmt , GLint filter , uint tex_flags )
{
2005-05-10 20:29:18 +02:00
/*
There are various combinations of desires / abilities , relating to how
( and whether ) mipmaps should be generated . Currently there are only
2005-09-29 07:00:20 +02:00
2 ^ 4 of them :
2005-05-10 20:29:18 +02:00
2005-09-21 18:11:43 +02:00
/ mipmaps available in texture
# ######
/ mipmaps needed
# ######
2005-05-10 20:29:18 +02:00
. - - - + - - - + - - - + - - - .
| Au | Mu | Nu | Nu | # - auto mipmap generation available
| - - - + - - - + - - - + - - - | #
| Ac | Mc | Nc | Nc | # # - texture is compressed
| - - - + - - - + - - - + - - - | # #
| X | Mc | Nc | Nc | #
| - - - + - - - + - - - + - - - | #
| G | Mu | Nu | Nu |
` - - - + - - - + - - - + - - - '
2005-05-11 06:56:05 +02:00
Au ( auto_uncomp ) = auto_mipmap_gen , then ' Nu '
Ac ( auto_comp ) = auto_mipmap_gen , then ' Nc '
X ( broken_comp ) = failure ; just fall back to GL_LINEAR and ' Nc '
G ( glubuild ) = gluBuild2DMipmaps
Mu ( mipped_uncomp ) = glTexImage2D , mipmap levels
Mc ( mipped_comp ) = glCompressedTexImage2DARB , mipmap levels
Nu ( normal_uncomp ) = glTexImage2D
Nc ( normal_comp ) = glCompressedTexImage2DARB
2005-05-10 20:29:18 +02:00
if ( Au | | Ac )
enable automatic mipmap generation
switch to ' N * '
if ( X )
set GL_LINEAR
switch to ' Nc '
if ( G )
gluBuild2DMipmaps
if ( Nu )
glTexImage2D
if ( Nc )
glCompressedTexImage2DARB
if ( Mu )
for each mipmap level
glTexImage2D
if ( Mc )
for each mipmap level
glCompressedTexImage2DARB
2005-05-11 06:56:05 +02:00
[ This documentation feels like more than is really necessary , but
hopefully it ' ll prevent the logic getting horribly tangled . . . ]
2005-05-10 20:29:18 +02:00
*/
2005-09-29 07:00:20 +02:00
// decisions:
// .. does filter call for uploading mipmaps?
const bool need_mipmaps = filter_uses_mipmaps ( filter ) ;
// .. does this OpenGL implementation support auto mipmap generation?
static const bool auto_mipmap_gen = oglHaveVersion ( " 1.4 " ) | | oglHaveExtension ( " GL_SGIS_generate_mipmap " ) ;
// .. is this texture in S3TC format? (more generally, "compressed")
2005-09-21 18:11:43 +02:00
const bool is_s3tc = ogl_dxt_from_fmt ( fmt ) ! = 0 ;
2005-09-29 07:00:20 +02:00
// .. does the image data include mipmaps? (stored as separate
// images after the regular texels)
const bool includes_mipmaps = ( tex_flags & TEX_MIPMAPS ) ! = 0 ;
2005-05-10 20:29:18 +02:00
2005-09-29 07:00:20 +02:00
static const UploadState states [ 4 ] [ 4 ] =
2005-05-12 00:40:19 +02:00
{
2005-05-11 06:56:05 +02:00
{ auto_uncomp , mipped_uncomp , normal_uncomp , normal_uncomp } ,
{ auto_comp , mipped_comp , normal_comp , normal_comp } ,
{ broken_comp , mipped_comp , normal_comp , normal_comp } ,
{ glubuild , mipped_uncomp , normal_uncomp , normal_uncomp }
2005-05-10 20:29:18 +02:00
} ;
2005-09-29 07:00:20 +02:00
const int row = auto_mipmap_gen ? ( is_s3tc ? 1 : 0 ) : ( is_s3tc ? 2 : 3 ) ;
const int col = need_mipmaps ? ( includes_mipmaps ? 1 : 0 ) : ( includes_mipmaps ? 2 : 3 ) ;
return states [ row ] [ col ] ;
}
// <data> holds the image as well as all mip levels (stored consecutively).
// upload them with the given internal format and quality flags.
//
// this is called whenever possible because pregenerated mipmaps are
// higher-quality and faster than gluBuildMipmaps resp. automatic generation.
//
// pre: w, h > 0; texture is bound.
static void upload_mipmaps ( uint w , uint h , uint bpp , const u8 * data ,
UploadState state , GLenum fmt , GLint int_fmt , uint q_flags )
{
GLsizei level_w = w ;
GLsizei level_h = h ;
// resolution reduction (see OGL_TEX_HALF_RES for rationale).
// this effectively reduces resolution by skipping some of the
// lower (high-resolution) mip levels.
//
// we iterate through the loop (necessary to skip over image data),
// but do not actually upload until the requisite number of
// levels have been skipped (i.e. level == 0).
//
// note: we don't just use GL_TEXTURE_BASE_LEVEL because it would
// require uploading unused levels, which is wasteful.
// .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags.
const uint reduce = ( q_flags & OGL_TEX_HALF_RES ) ? 2 : 1 ;
const uint levels_to_skip = log2 ( reduce ) ;
int level = - ( int ) levels_to_skip ;
// until at level 1x1:
for ( ; ; )
{
GLsizei mip_size ; // used to skip past this mip level in <data>
if ( state = = mipped_uncomp )
{
mip_size = level_w * level_h * bpp / 8 ;
if ( level > = 0 )
glTexImage2D ( GL_TEXTURE_2D , level , int_fmt , level_w , level_h , 0 , fmt , GL_UNSIGNED_BYTE , data ) ;
}
else // state == mipped_comp
{
mip_size = ( GLsizei ) ( round_up ( level_w , 4 ) * round_up ( level_h , 4 ) * bpp / 8 ) ;
if ( level > = 0 )
glCompressedTexImage2DARB ( GL_TEXTURE_2D , level , fmt , level_w , level_h , 0 , mip_size , data ) ;
}
data + = mip_size ;
// 1x1 reached - done
if ( level_w = = 1 & & level_h = = 1 )
break ;
level_w / = 2 ;
level_h / = 2 ;
// if the texture is non-square, one of the dimensions will become
// 0 before the other. to satisfy OpenGL's expectations, change it
// back to 1.
if ( level_w = = 0 ) level_w = 1 ;
if ( level_h = = 0 ) level_h = 1 ;
level + + ;
}
}
// upload manager: given all texture format details, determines the
// initial "state" and runs through the upload automaton.
//
// split out of ogl_tex_upload because it was too big.
//
// pre: <t> is valid for OpenGL use; texture is bound.
static void upload_impl ( const Tex * t , GLenum fmt , GLint int_fmt , GLint filter , uint q_flags )
{
// convenient local copies (t has been validated already).
const GLsizei w = ( GLsizei ) t - > w ;
const GLsizei h = ( GLsizei ) t - > h ;
const uint bpp = t - > bpp ; // used for S3TC/mipmap size calc
const uint flags = t - > flags ; // tells us if img holds mipmaps
const u8 * data = ( const u8 * ) tex_get_data ( t ) ;
UploadState state = determine_upload_state ( fmt , filter , flags ) ;
2005-05-11 06:56:05 +02:00
if ( state = = auto_uncomp | | state = = auto_comp )
2004-06-26 00:18:03 +02:00
{
2005-09-29 07:00:20 +02:00
// notes:
// - if this state is reached, OpenGL supports auto mipmap gen
// (we check for that above)
// - we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS
// have the same values - it's heavily implied by the spec
// governing 'promoted' ARB extensions and just plain makes sense.
glTexParameteri ( GL_TEXTURE_2D , GL_GENERATE_MIPMAP , GL_TRUE ) ;
state = ( state = = auto_comp ) ? normal_comp : normal_uncomp ;
2005-05-05 00:03:00 +02:00
}
2004-10-23 16:39:28 +02:00
2005-05-11 06:56:05 +02:00
if ( state = = broken_comp )
2005-05-10 20:29:18 +02:00
{
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ) ;
2005-05-11 06:56:05 +02:00
state = normal_comp ;
2005-05-10 20:29:18 +02:00
}
2005-05-11 06:56:05 +02:00
if ( state = = glubuild )
2005-09-29 07:00:20 +02:00
gluBuild2DMipmaps ( GL_TEXTURE_2D , int_fmt , w , h , fmt , GL_UNSIGNED_BYTE , data ) ;
2005-05-12 00:40:19 +02:00
else if ( state = = normal_uncomp )
2005-09-29 07:00:20 +02:00
glTexImage2D ( GL_TEXTURE_2D , 0 , int_fmt , w , h , 0 , fmt , GL_UNSIGNED_BYTE , data ) ;
2005-05-12 00:40:19 +02:00
else if ( state = = normal_comp )
2005-05-05 00:03:00 +02:00
{
2005-03-10 18:04:42 +01:00
const GLsizei tex_size = w * h * bpp / 8 ;
2005-09-29 07:00:20 +02:00
glCompressedTexImage2DARB ( GL_TEXTURE_2D , 0 , fmt , w , h , 0 , tex_size , data ) ;
2004-06-26 00:18:03 +02:00
}
2005-05-12 00:40:19 +02:00
else if ( state = = mipped_uncomp | | state = = mipped_comp )
2005-09-29 07:00:20 +02:00
upload_mipmaps ( w , h , bpp , data , state , fmt , int_fmt , q_flags ) ;
2005-05-10 20:29:18 +02:00
else
2005-09-29 07:00:20 +02:00
debug_warn ( " invalid state in ogl_tex_upload " ) ;
2005-05-10 20:29:18 +02:00
2004-06-26 00:18:03 +02:00
}
2005-09-29 07:00:20 +02:00
// upload the texture to OpenGL.
2005-09-29 19:33:30 +02:00
// if not 0, parameters override the following:
// fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags;
// q_flags_ovr : global default "quality vs. performance" flags;
// int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags.
//
2005-09-29 07:00:20 +02:00
// side effects:
// - enables texturing on TMU 0 and binds the texture to it;
// - frees the texel data! see ogl_tex_get_data.
2005-09-29 19:33:30 +02:00
int ogl_tex_upload ( const Handle ht , GLenum fmt_ovr , uint q_flags_ovr , GLint int_fmt_ovr )
2005-09-21 18:11:43 +02:00
{
2005-09-29 07:00:20 +02:00
H_DEREF ( ht , OglTex , ot ) ;
CHECK_OGL_TEX ( ot ) ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
debug_assert ( q_flags_valid ( q_flags_ovr ) ) ;
// we don't bother verifying *fmt_ovr - there are too many values
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
const char * fn = h_filename ( ht ) ;
if ( ! fn )
fn = " (could not determine filename) " ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// someone's requesting upload, but has already been uploaded.
// this happens if a cached texture is "loaded". no work to do.
if ( ot - > is_currently_uploaded )
return 0 ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// determine fmt and int_fmt, allowing for user override.
2005-09-29 19:33:30 +02:00
ot - > fmt = choose_fmt ( ot - > t . bpp , ot - > t . flags ) ;
if ( fmt_ovr ) ot - > fmt = fmt_ovr ;
2005-09-29 07:00:20 +02:00
if ( q_flags_ovr ) ot - > q_flags = q_flags_ovr ;
2005-09-29 19:33:30 +02:00
ot - > int_fmt = choose_int_fmt ( ot - > fmt , ot - > q_flags ) ;
2005-09-29 07:00:20 +02:00
if ( int_fmt_ovr ) ot - > int_fmt = int_fmt_ovr ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// we know ht is valid (H_DEREF above), but ogl_tex_bind can
// fail in debug builds if OglTex.id isn't a valid texture name
CHECK_ERR ( ogl_tex_bind ( ht , ot - > tmu ) ) ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// if first time: apply our defaults/previous overrides;
// otherwise, replays all state changes.
state_latch ( & ot - > state ) ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
upload_impl ( & ot - > t , ot - > fmt , ot - > int_fmt , ot - > state . filter , ot - > q_flags ) ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
// see rationale for <refs> at declaration of OglTex.
// note: tex_free is safe even if this OglTex was wrapped -
// the Tex contains a mem handle.
int refs = h_get_refcnt ( ht ) ;
if ( refs = = 1 )
tex_free ( & ot - > t ) ;
2005-09-21 18:11:43 +02:00
2005-09-29 19:33:30 +02:00
ot - > need_auto_upload = 1 ;
2005-09-29 07:00:20 +02:00
ot - > is_currently_uploaded = 1 ;
2005-09-21 18:11:43 +02:00
2005-09-29 07:00:20 +02:00
# ifndef NDEBUG
oglCheck ( ) ;
# endif
2005-09-21 18:11:43 +02:00
return 0 ;
}
//----------------------------------------------------------------------------
2005-09-29 07:00:20 +02:00
// getters
2005-09-21 18:11:43 +02:00
// retrieve texture dimensions and bits per pixel.
// all params are optional and filled if non-NULL.
2005-09-29 07:00:20 +02:00
int ogl_tex_get_size ( Handle ht , uint * w , uint * h , uint * bpp )
2004-06-26 00:18:03 +02:00
{
2005-09-02 04:54:02 +02:00
H_DEREF ( ht , OglTex , ot ) ;
2005-09-21 18:11:43 +02:00
CHECK_OGL_TEX ( ot ) ;
2004-06-26 00:18:03 +02:00
if ( w )
2005-09-02 04:54:02 +02:00
* w = ot - > t . w ;
2004-06-26 00:18:03 +02:00
if ( h )
2005-09-02 04:54:02 +02:00
* h = ot - > t . h ;
2004-06-26 00:18:03 +02:00
if ( bpp )
2005-09-02 04:54:02 +02:00
* bpp = ot - > t . bpp ;
return 0 ;
}
2004-06-26 00:18:03 +02:00
2005-09-02 04:54:02 +02:00
2005-09-21 18:11:43 +02:00
// retrieve Tex.flags and the corresponding OpenGL format.
2005-09-29 07:00:20 +02:00
// the latter is determined during ogl_tex_upload and is 0 before that.
2005-09-21 18:11:43 +02:00
// all params are optional and filled if non-NULL.
2005-09-29 07:00:20 +02:00
int ogl_tex_get_format ( Handle ht , uint * flags , GLenum * fmt )
2005-09-02 04:54:02 +02:00
{
H_DEREF ( ht , OglTex , ot ) ;
2005-09-21 18:11:43 +02:00
CHECK_OGL_TEX ( ot ) ;
2005-09-02 04:54:02 +02:00
if ( flags )
* flags = ot - > t . flags ;
if ( fmt )
2005-09-29 07:00:20 +02:00
{
2005-09-29 19:33:30 +02:00
if ( ! ot - > is_currently_uploaded )
2005-09-29 07:00:20 +02:00
debug_warn ( " ogl_tex_get_format: hasn't been defined yet! " ) ;
2005-09-02 04:54:02 +02:00
* fmt = ot - > fmt ;
2005-09-29 07:00:20 +02:00
}
2005-09-02 04:54:02 +02:00
return 0 ;
}
2005-09-21 18:11:43 +02:00
// retrieve pointer to texel data.
//
// note: this memory is freed after a successful ogl_tex_upload for
2005-09-29 07:00:20 +02:00
// this texture. after that, the pointer we retrieve is NULL but ps_dbg.exe!ogl_tex_set_filter(__int64 ht=476741374144, int filter=9729) Line 490 + 0x4a C++
2005-09-21 18:11:43 +02:00
// the function doesn't fail (negative return value) by design.
// if you still need to get at the data, add a reference before
// uploading it or read directly from OpenGL (discouraged).
2005-09-02 04:54:02 +02:00
int ogl_tex_get_data ( Handle ht , void * * p )
{
H_DEREF ( ht , OglTex , ot ) ;
2005-09-21 18:11:43 +02:00
CHECK_OGL_TEX ( ot ) ;
2005-09-02 04:54:02 +02:00
* p = tex_get_data ( & ot - > t ) ;
2004-06-26 00:18:03 +02:00
return 0 ;
}
2005-09-29 07:00:20 +02:00
//----------------------------------------------------------------------------
// misc API
// apply the specified transforms (as in tex_transform) to the image.
// must be called before uploading (raises a warning if called afterwards).
int ogl_tex_transform ( Handle ht , uint transforms )
{
H_DEREF ( ht , OglTex , ot ) ;
CHECK_OGL_TEX ( ot ) ;
int ret = tex_transform ( & ot - > t , transforms ) ;
return ret ;
2005-10-07 17:24:29 +02:00
}