// 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/ #include "precompiled.h" #include "lib.h" #include "../res.h" #include "ogl.h" #include "tex.h" #include "ogl_tex.h" //---------------------------------------------------------------------------- // OpenGL helper routines //---------------------------------------------------------------------------- static bool filter_valid(GLint filter) { 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; 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; } } static bool filter_uses_mipmaps(GLint filter) { switch(filter) { 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; } } // determine OpenGL texture format, given and Tex . 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; } //---------------------------------------------------------------------------- // 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; } } // choose an internal format for based on the given q_flags. static GLint choose_int_fmt(GLenum fmt, uint q_flags) { // true => 4 bits per component; otherwise, 8 const bool half_bpp = (q_flags & OGL_TEX_HALF_BPP) != 0; // 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; switch(fmt) { // 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; default: 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; } UNREACHABLE; } //---------------------------------------------------------------------------- // texture state to allow seamless reload //---------------------------------------------------------------------------- // see "Texture Parameters" in docs. // 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; }; // fill the given state object with default values. static void state_set_to_defaults(OglTexState* ots) { ots->filter = default_filter; ots->wrap = GL_REPEAT; } // send all state to OpenGL (actually the currently bound texture). // called from ogl_tex_upload. static void state_latch(OglTexState* ots) { // 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); } //---------------------------------------------------------------------------- // texture resource object //---------------------------------------------------------------------------- // 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). // // given that multiple "instances" share the state stored here, we conclude: // - a refcount is necessary to prevent ogl_tex_upload from freeing // 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) // // 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. // the alternative is even worse: disabling *all* caching/reuse would // 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). struct OglTex { Tex t; // allocated by OglTex_reload; indicates the texture is currently uploaded. GLuint id; // ogl_tex_upload calls choose_fmt to determine these from . // 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. GLenum fmt; GLint int_fmt; OglTexState state; // OglTexQualityFlags uint q_flags : 8; // to which Texture Mapping Unit was this bound? uint tmu : 8; // 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. uint is_loaded : 1; // .. 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 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. uint was_wrapped : 1; // indicates if this texture is currently uploaded. // used by state setters; reset in dtor. uint is_currently_uploaded : 1; }; H_TYPE_DEFINE(OglTex); static void OglTex_init(OglTex* ot, va_list args) { Tex* wrapped_tex = va_arg(args, Tex*); if(wrapped_tex) { // note: this only happens once; ogl_tex_wrap makes sure // this OglTex cannot be reloaded, so it's safe. ot->t = *wrapped_tex; ot->was_wrapped = 1; } state_set_to_defaults(&ot->state); ot->q_flags = default_q_flags; } static void OglTex_dtor(OglTex* ot) { (void)tex_free(&ot->t); glDeleteTextures(1, &ot->id); ot->id = 0; ot->is_currently_uploaded = 0; // need to clear this so actual reloads (triggered by h_reload) // actually reload. ot->is_loaded = 0; } static int OglTex_reload(OglTex* ot, const char* fn, Handle h) { // make sure the texture has been loaded // .. already done ( had been freed but not yet unloaded) if(ot->is_loaded) return 0; // .. load from file - but only if we weren't wrapping an existing // Tex object (i.e. copy its values and be done). if(!ot->was_wrapped) RETURN_ERR(tex_load(fn, &ot->t)); ot->is_loaded = 1; glGenTextures(1, &ot->id); // if it had already been uploaded before this reload, // re-upload it (this also does state_latch). if(ot->need_auto_upload) (void)ogl_tex_upload(h); return 0; } // load and return a handle to the texture given in . // for a list of supported formats, see tex.h's tex_load. Handle ogl_tex_load(const char* fn, uint flags) { Tex* wrapped_tex = 0; // we're loading from file return h_alloc(H_OglTex, fn, flags, wrapped_tex); } // 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. // // isn't strictly needed but should describe the texture so that // h_filename will return a meaningful comment for debug purposes. // 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. Handle ogl_tex_wrap(Tex* t, const char* fn, uint flags) { // 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) // also disable caching as explained above. flags |= RES_DISALLOW_RELOAD|RES_NO_CACHE; return h_alloc(H_OglTex, fn, flags, t); } // free all resources associated with the texture and make further // use of it impossible. (subject to refcount) int ogl_tex_free(Handle& ht) { return h_free(ht, H_OglTex); } static int ogl_tex_validate(const uint line, const OglTex* ot) { RETURN_ERR(tex_validate(line, &ot->t)); const char* msg = 0; int err = -1; // width, height // (note: this is done here because tex.cpp doesn't impose any // restrictions on dimensions, while OpenGL does). GLsizei w = (GLsizei)ot->t.w; GLsizei h = (GLsizei)ot->t.h; // .. if w or h is 0, texture file probably not loaded successfully. if(w == 0 || h == 0) msg = "width or height is 0 - texture probably not loaded successfully"; // .. greater than max supported tex dimension? // no-op if oglInit not yet called if(w > (GLsizei)ogl_max_tex_size || h > (GLsizei)ogl_max_tex_size) msg = "texture dimensions exceed OpenGL implementation limit"; // .. 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 if(!is_pow2(w) || !is_pow2(h)) msg = "width or height is not a power-of-2"; // texture state GLint filter = ot->state.filter; GLint wrap = ot->state.wrap; if(!filter_valid(filter)) msg = "invalid filter"; if(!wrap_valid(wrap)) msg = "invalid wrap mode"; // misc if(!q_flags_valid(ot->q_flags)) msg = "invalid q_flags"; if(ot->tmu >= 128) msg = "TMU invalid? it's >= 128!"; // .. note: don't check ot->fmt and ot->int_fmt - they aren't set // until during ogl_tex_upload. if(msg) { debug_printf("ogl_tex_validate at line %d failed: %s (error code %d)\n", line, msg, err); debug_warn("ogl_tex_validate failed"); return err; } return 0; } #define CHECK_OGL_TEX(ot) CHECK_ERR(ogl_tex_validate(__LINE__, ot)) //---------------------------------------------------------------------------- // state setters (see "Texture Parameters" in docs) // 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). static void warn_if_uploaded(Handle ht, const OglTex* ot) { #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. 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"); #else // (prevent warnings; the alternative of wrapping all call sites in // #ifndef is worse) UNUSED2(ht); UNUSED2(ot); #endif } // 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) { H_DEREF(ht, OglTex, ot); CHECK_OGL_TEX(ot); if(!filter_valid(filter)) CHECK_ERR(ERR_INVALID_PARAM); warn_if_uploaded(ht, ot); ot->state.filter = filter; return 0; } // 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 == 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; { // (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; } CHECK_OGL_TEX(ot); #ifndef NDEBUG if(!ot->id) { debug_warn("ogl_tex_bind: OglTex.id is not a valid texture"); return -1; } #endif id = ot->id; ot->tmu = unit; } disable_texturing: glActiveTextureARB(GL_TEXTURE0+unit); if(id) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, id); } else glDisable(GL_TEXTURE_2D); return 0; } // 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 }; // 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) { /* There are various combinations of desires/abilities, relating to how (and whether) mipmaps should be generated. Currently there are only 2^4 of them: /mipmaps available in texture ####### /mipmaps needed ####### .---+---+---+---. | Au| Mu| Nu| Nu|#-auto mipmap generation available |---+---+---+---|# | Ac| Mc| Nc| Nc|# #-texture is compressed |---+---+---+---|# # | X | Mc| Nc| Nc| # |---+---+---+---| # | G | Mu| Nu| Nu| `---+---+---+---' 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 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 [This documentation feels like more than is really necessary, but hopefully it'll prevent the logic getting horribly tangled...] */ // 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") const bool is_s3tc = ogl_dxt_from_fmt(fmt) != 0; // .. does the image data include mipmaps? (stored as separate // images after the regular texels) const bool includes_mipmaps = (tex_flags & TEX_MIPMAPS) != 0; static const UploadState states[4][4] = { { 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 } }; 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]; } // 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 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: 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); if(state == auto_uncomp || state == auto_comp) { // 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; } if(state == broken_comp) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); state = normal_comp; } if(state == glubuild) gluBuild2DMipmaps(GL_TEXTURE_2D, int_fmt, w, h, fmt, GL_UNSIGNED_BYTE, data); else if(state == normal_uncomp) glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, w, h, 0, fmt, GL_UNSIGNED_BYTE, data); else if(state == normal_comp) { const GLsizei tex_size = w * h * bpp / 8; glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, fmt, w, h, 0, tex_size, data); } else if(state == mipped_uncomp || state == mipped_comp) upload_mipmaps(w, h, bpp, data, state, fmt, int_fmt, q_flags); else debug_warn("invalid state in ogl_tex_upload"); } // upload the texture to OpenGL. // 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. // // side effects: // - enables texturing on TMU 0 and binds the texture to it; // - frees the texel data! see ogl_tex_get_data. int ogl_tex_upload(const Handle ht, GLenum fmt_ovr, uint q_flags_ovr, GLint int_fmt_ovr) { H_DEREF(ht, OglTex, ot); CHECK_OGL_TEX(ot); debug_assert(q_flags_valid(q_flags_ovr)); // we don't bother verifying *fmt_ovr - there are too many values const char* fn = h_filename(ht); if(!fn) fn = "(could not determine filename)"; // 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; // determine fmt and int_fmt, allowing for user override. ot->fmt = choose_fmt(ot->t.bpp, ot->t.flags); if(fmt_ovr) ot->fmt = fmt_ovr; if(q_flags_ovr) ot->q_flags = q_flags_ovr; ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags); if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr; // 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)); // if first time: apply our defaults/previous overrides; // otherwise, replays all state changes. state_latch(&ot->state); upload_impl(&ot->t, ot->fmt, ot->int_fmt, ot->state.filter, ot->q_flags); // see rationale for 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); ot->need_auto_upload = 1; ot->is_currently_uploaded = 1; #ifndef NDEBUG oglCheck(); #endif return 0; } //---------------------------------------------------------------------------- // getters // retrieve texture dimensions and bits per pixel. // all params are optional and filled if non-NULL. int ogl_tex_get_size(Handle ht, uint* w, uint* h, uint* bpp) { H_DEREF(ht, OglTex, ot); CHECK_OGL_TEX(ot); if(w) *w = ot->t.w; if(h) *h = ot->t.h; if(bpp) *bpp = ot->t.bpp; return 0; } // retrieve Tex.flags and the corresponding OpenGL format. // the latter is determined during ogl_tex_upload and is 0 before that. // all params are optional and filled if non-NULL. int ogl_tex_get_format(Handle ht, uint* flags, GLenum* fmt) { H_DEREF(ht, OglTex, ot); CHECK_OGL_TEX(ot); if(flags) *flags = ot->t.flags; if(fmt) { if(!ot->is_currently_uploaded) debug_warn("ogl_tex_get_format: hasn't been defined yet!"); *fmt = ot->fmt; } return 0; } // retrieve pointer to texel data. // // note: this memory is freed after a successful ogl_tex_upload for // 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++ // 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). int ogl_tex_get_data(Handle ht, void** p) { H_DEREF(ht, OglTex, ot); CHECK_OGL_TEX(ot); *p = tex_get_data(&ot->t); return 0; } //---------------------------------------------------------------------------- // 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; }