From d6f31e4157e4d5da787996368e34544ea5449c86 Mon Sep 17 00:00:00 2001 From: janwas Date: Wed, 21 Sep 2005 16:11:43 +0000 Subject: [PATCH] ogl_tex: documented and cleaned up. h_mgr: add h_get_refcnt; saw h_alloc was a mess and split it up mem: remove obsolete 'scope' This was SVN commit r2759. --- source/lib/res/graphics/ogl_tex.cpp | 322 ++++++++++++++++--------- source/lib/res/graphics/ogl_tex.h | 161 ++++++++++++- source/lib/res/graphics/tex.h | 5 +- source/lib/res/h_mgr.cpp | 278 +++++++++++---------- source/lib/res/h_mgr.h | 360 ++++++++++++++-------------- source/lib/res/mem.cpp | 8 - source/lib/sysdep/ia32.cpp | 7 +- 7 files changed, 706 insertions(+), 435 deletions(-) diff --git a/source/lib/res/graphics/ogl_tex.cpp b/source/lib/res/graphics/ogl_tex.cpp index 5ac707df43..9ba12e99df 100755 --- a/source/lib/res/graphics/ogl_tex.cpp +++ b/source/lib/res/graphics/ogl_tex.cpp @@ -8,15 +8,12 @@ #include "lib.h" -GLint ogl_tex_filter = GL_LINEAR; -uint ogl_tex_bpp = 32; // 16 or 32 +static GLint default_filter = GL_LINEAR; // all legal GL *minify* values +static uint default_bpp = 32; // 16 or 32 - -////////////////////////////////////////////////////////////////////////////// -// +//---------------------------------------------------------------------------- // OpenGL helper routines -// -////////////////////////////////////////////////////////////////////////////// +//---------------------------------------------------------------------------- static bool filter_is_known(GLint filter) { @@ -50,7 +47,7 @@ static bool filter_uses_mipmaps(GLint filter) // determine OpenGL texture format, given and Tex . -// also choose an internal format based on the global +// also choose an internal format based on the global // performance vs. quality setting. // // rationale: we override the user's previous internal format preference. @@ -96,7 +93,7 @@ static int get_gl_fmt(int bpp, int flags, GLenum* fmt, GLint* int_fmt) // true => 8 bits per component; otherwise, 4 - const bool high_quality = (ogl_tex_bpp == 32); + const bool high_quality = (default_bpp == 32); switch(bpp) { @@ -112,14 +109,14 @@ static int get_gl_fmt(int bpp, int flags, GLenum* fmt, GLint* int_fmt) case 24: debug_assert(!alpha); *fmt = bgr? GL_BGR : GL_RGB; + // note: BGR can't be used as internal format *int_fmt = high_quality? GL_RGB8 : GL_RGB4; - // note: BGR can't be used as internal format return 0; case 32: debug_assert(alpha); *fmt = bgr? GL_BGRA : GL_RGBA; + // note: BGR can't be used as internal format *int_fmt = high_quality? GL_RGBA8 : GL_RGBA4; - // note: BGR can't be used as internal format return 0; default: debug_warn("get_gl_fmt: invalid bpp"); @@ -154,26 +151,70 @@ static GLint detect_auto_mipmap_gen() } -////////////////////////////////////////////////////////////////////////////// -// + + +// change default upload settings - these affect performance vs. quality. +// may be overridden for individual textures via ogl_tex_upload parameters. +// pass 0 to keep the current setting; defaults and legal values are: +// - filter: GL_LINEAR; any valid OpenGL minification filter +// - bpp : 32; 16 or 32 (this toggles between RGBA4 and RGBA8) +void ogl_tex_set_default_upload(GLint filter, uint bpp) +{ + if(filter) + { + debug_assert(filter_is_known(filter)); + default_filter = filter; + } + if(bpp) + { + debug_assert(bpp == 16 || bpp == 32); + default_bpp = bpp; + } +} + + +//---------------------------------------------------------------------------- // texture resource implementation +//---------------------------------------------------------------------------- + +// 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: +// multiple instances of the same texture where someone changes its +// internal format aren't expected. 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 disallowing reuse of +// active objects only (would break the index lookup code, since +// multiple instances may exist). struct OglTex { Tex t; - // allocated by Tex_reload; indicates the texture is currently uploaded. + // allocated by OglTex_reload; indicates the texture is currently uploaded. GLuint id; - // determined from Tex by gl_get_fmt (called from Tex_reload); + // determined from Tex by gl_get_fmt (called from OglTex_reload); // user settings passed to ogl_tex_upload will override this until the // next actual reload. GLenum fmt; GLint int_fmt; - // set to default by Tex_init; user settings passed to + // set to by OglTex_init; user settings passed to // ogl_tex_upload will permanently override this. GLint filter; @@ -181,9 +222,9 @@ struct OglTex // .. either we have the texture in memory (referenced by t.hm), // or it's already been uploaded to OpenGL => no reload necessary. // needs to be a flag so it can be reset in Tex_dtor. - bool is_loaded; - bool has_been_uploaded; - bool was_wrapped; + uint is_loaded : 1; + uint has_been_uploaded : 1; + uint was_wrapped : 1; // to which Texture Mapping Unit was this bound? // used when re-binding after reload. @@ -198,13 +239,14 @@ static void OglTex_init(OglTex* ot, va_list args) if(wrapped_tex) { ot->t = *wrapped_tex; - ot->was_wrapped = true; + ot->was_wrapped = 1; } // set to default (once) - ot->filter = ogl_tex_filter; + ot->filter = default_filter; } + static void OglTex_dtor(OglTex* ot) { (void)tex_free(&ot->t); @@ -214,7 +256,7 @@ static void OglTex_dtor(OglTex* ot) // need to clear this so actual reloads (triggered by h_reload) // actually reload. - ot->is_loaded = false; + ot->is_loaded = 0; } @@ -226,13 +268,13 @@ static int OglTex_reload(OglTex* ot, const char* fn, Handle h) Tex* const t = &ot->t; if(!ot->was_wrapped) - CHECK_ERR(tex_load(fn, t)); + RETURN_ERR(tex_load(fn, &ot->t)); // always override previous settings, since format in // texture file may have changed (e.g. 24 -> 32 bpp). CHECK_ERR(get_gl_fmt(t->bpp, t->flags, &ot->fmt, &ot->int_fmt)); - ot->is_loaded = true; + ot->is_loaded = 1; glGenTextures(1, &ot->id); @@ -244,107 +286,47 @@ static int OglTex_reload(OglTex* ot, const char* fn, Handle h) } -Handle ogl_tex_load(const char* fn, int scope) +// 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, scope, wrapped_tex); + return h_alloc(H_OglTex, fn, flags, wrapped_tex); } -Handle ogl_tex_wrap(const char* fn, Tex* 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. +Handle ogl_tex_wrap(Tex* t, const char* fn, uint flags) { - return h_alloc(H_OglTex, fn, 0, wrapped_tex); + 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); } -/* -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// BindTexture: bind a GL texture object to current active unit -void CRenderer::BindTexture(int unit,GLuint tex) -{ -#if 0 -glActiveTextureARB(GL_TEXTURE0+unit); -if (tex==m_ActiveTextures[unit]) return; - -if (tex) { -glBindTexture(GL_TEXTURE_2D,tex); -if (!m_ActiveTextures[unit]) { -glEnable(GL_TEXTURE_2D); -} -} else if (m_ActiveTextures[unit]) { -glDisable(GL_TEXTURE_2D); -} -m_ActiveTextures[unit]=tex; -#endif - -glActiveTextureARB(GL_TEXTURE0+unit); - -glBindTexture(GL_TEXTURE_2D,tex); -if (tex) { -glEnable(GL_TEXTURE_2D); -} else { -glDisable(GL_TEXTURE_2D); -} -m_ActiveTextures[unit]=tex; -} -*/ - - -// note: there are many call sites of glActiveTextureARB, so caching -// those and ignoring redundant sets isn't feasible. -int ogl_tex_bind(const Handle h, GLenum unit) -{ - int id = 0; - - // special case: avoid dereference and disable texturing directly. - if(h == 0) - goto disable_texturing; - - { - // (we can't use H_DEREF because it exits immediately) - OglTex* ot = H_USER_DATA(h, OglTex); - if(!ot) - { - glBindTexture(GL_TEXTURE_2D, 0); - return ERR_INVALID_HANDLE; - } - -#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); - glBindTexture(GL_TEXTURE_2D, id); - if(id) - glEnable(GL_TEXTURE_2D); - else - glDisable(GL_TEXTURE_2D); - return 0; -} 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; - RETURN_ERR(tex_validate(line, &ot->t)); - // width, height // (note: this is done here because tex.cpp doesn't impose any // restrictions on dimensions, while OpenGL does). @@ -390,12 +372,20 @@ static int ogl_tex_validate(const uint line, const OglTex* ot) return 0; } -#define CHECK_OGL_TEX(t) CHECK_ERR(ogl_tex_validate(__LINE__, t)) +#define CHECK_OGL_TEX(ot) CHECK_ERR(ogl_tex_validate(__LINE__, ot)) + + +// upload the texture to OpenGL. texture filter and [internal] format +// may be specified to override the global defaults (see below). +// 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, GLint filter_ovr, GLint int_fmt_ovr, GLenum fmt_ovr) { H_DEREF(ht, OglTex, ot); + CHECK_OGL_TEX(ot); // someone's requesting upload, but has already been uploaded. // this happens if a cached texture is "loaded". no work to do. @@ -446,10 +436,10 @@ int ogl_tex_upload(const Handle ht, GLint filter_ovr, GLint int_fmt_ovr, GLenum (and whether) mipmaps should be generated. Currently there are only 2^4 such combinations: - /mipmaps available in texture - ####### - /mipmaps needed - ####### + /mipmaps available in texture + ####### + /mipmaps needed + ####### .---+---+---+---. | Au| Mu| Nu| Nu|#-auto mipmap generation available |---+---+---+---|# @@ -492,8 +482,8 @@ int ogl_tex_upload(const Handle ht, GLint filter_ovr, GLint int_fmt_ovr, GLenum hopefully it'll prevent the logic getting horribly tangled...] */ - bool is_s3tc = ogl_dxt_from_fmt(fmt) != 0; - bool has_mipmaps = (ot->t.flags & TEX_MIPMAPS ? true : false); + const bool is_s3tc = ogl_dxt_from_fmt(fmt) != 0; + const bool has_mipmaps = (ot->t.flags & TEX_MIPMAPS) != 0; enum UploadState { @@ -570,9 +560,12 @@ int ogl_tex_upload(const Handle ht, GLint filter_ovr, GLint int_fmt_ovr, GLenum else debug_warn("Invalid state in ogl_tex_upload"); - mem_free_h(ot->t.hm); + // see rationale at declaration of OglTex + int refs = h_get_refcnt(ht); + if(refs > 0) + tex_free(&ot->t); - ot->has_been_uploaded = true; + ot->has_been_uploaded = 1; oglCheck(); @@ -580,9 +573,99 @@ int ogl_tex_upload(const Handle ht, GLint filter_ovr, GLint int_fmt_ovr, GLenum } +/* +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BindTexture: bind a GL texture object to current active unit +void CRenderer::BindTexture(int unit,GLuint tex) +{ +#if 0 +glActiveTextureARB(GL_TEXTURE0+unit); +if (tex==m_ActiveTextures[unit]) return; + +if (tex) { +glBindTexture(GL_TEXTURE_2D,tex); +if (!m_ActiveTextures[unit]) { +glEnable(GL_TEXTURE_2D); +} +} else if (m_ActiveTextures[unit]) { +glDisable(GL_TEXTURE_2D); +} +m_ActiveTextures[unit]=tex; +#endif + +glActiveTextureARB(GL_TEXTURE0+unit); + +glBindTexture(GL_TEXTURE_2D,tex); +if (tex) { +glEnable(GL_TEXTURE_2D); +} else { +glDisable(GL_TEXTURE_2D); +} +m_ActiveTextures[unit]=tex; +} +*/ + + +// bind the texture to the specified unit [number] in preparation for +// using it in rendering. assumes multitexturing is available. +// 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); + glBindTexture(GL_TEXTURE_2D, id); + if(id) + glEnable(GL_TEXTURE_2D); + else + glDisable(GL_TEXTURE_2D); + return 0; +} + + +//---------------------------------------------------------------------------- + + +// retrieve texture dimensions and bits per pixel. +// all params are optional and filled if non-NULL. int ogl_tex_get_size(Handle ht, int* w, int* h, int* bpp) { H_DEREF(ht, OglTex, ot); + CHECK_OGL_TEX(ot); + if(w) *w = ot->t.w; if(h) @@ -593,9 +676,13 @@ int ogl_tex_get_size(Handle ht, int* w, int* h, int* bpp) } +// retrieve Tex.flags and the corresponding OpenGL format. +// all params are optional and filled if non-NULL. int ogl_tex_get_format(Handle ht, int* flags, GLenum* fmt) { H_DEREF(ht, OglTex, ot); + CHECK_OGL_TEX(ot); + if(flags) *flags = ot->t.flags; if(fmt) @@ -604,9 +691,18 @@ int ogl_tex_get_format(Handle ht, int* flags, GLenum* fmt) } +// 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 +// 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; } diff --git a/source/lib/res/graphics/ogl_tex.h b/source/lib/res/graphics/ogl_tex.h index c2176a8d46..8d3e85f328 100755 --- a/source/lib/res/graphics/ogl_tex.h +++ b/source/lib/res/graphics/ogl_tex.h @@ -1,3 +1,111 @@ +// 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/ + +/* + +[KEEP IN SYNC WITH WIKI!] + +Introduction +------------ + +This module simplifies use of textures in OpenGL. It provides an +easy-to-use load/upload/bind/free API that completely replaces +direct access to OpenGL's texturing calls. + +It basically wraps tex.cpp's texture info in a resource object +(see h_mgr.h) that maintains associated GL state and provides for +reference counting, caching, hotloading and safe access. + + +Caching and Texture Instances +----------------------------- + +Caching is both an advantage and drawback. When opening the same +texture twice without previously freeing it, a reference to the +first instance is returned. Therefore, be advised that concurrent use of the +same texture but with differing parameters (e.g. upload quality) followed by +a reload of the first instance will result in using the wrong parameters. +For background and rationale why this is acceptable, see struct OglTex. + + +Uploading to OpenGL +------------------- + +.. deserves some clarification. This entails calling glTexImage2D +(or its variants for mipmaps/compressed textures) and transfers +texture parameters and data from system memory to OpenGL +(and thereby usually video memory). + +In so doing, choices are made as to filtering algorithm and the +texture's internal representation (how it is stored in vmem) - +in particular, the bit depth. This can trade performance +(more/less data to copy) for quality (fidelity to original). + +We provide a mechanism that applies defaults to all uploads unless +overrides are specified (for individual ogl_tex_upload calls). +This allows a global "quality" setting that can boost performance on +older graphics cards without requiring anything else to be changed. + + +Example Usage +------------- + +Note: to keep the examples simple, we leave out error handling by +ignoring all return values. Each function will still raise a warning +(assert) if it fails and passing e.g. invalid Handles will only cause +the next function to fail, but real apps should check and report errors. + +1) Basic usage: load texture from file. + +Handle hTexture = ogl_tex_load("filename.dds"); +(void)ogl_tex_upload(hTexture); + +[when rendering:] +(void)ogl_tex_bind(hTexture); +[.. do something with OpenGL that uses the currently bound texture] + +[at exit:] +// (done automatically, but this avoids it showing up as a leak) +(void)ogl_tex_free(hTexture); + + +2) Advanced usage: wrap existing texture data and specify + filter/internal_format overrides. +Tex t; +const uint flags = 0; // image is plain RGB, default orientation +void* data = [pre-existing image] +(void)tex_wrap(w, h, 24, flags, data, &t); +Handle hCompositeAlphaMap = ogl_tex_wrap(&t, "(alpha map composite)"); +(void)ogl_tex_upload(hCompositeAlphaMap, GL_LINEAR, GL_INTENSITY); +// (your responsibility! tex_wrap attaches a reference but it is +// removed by ogl_tex_upload.) +free(data); + +[when rendering:] +(void)ogl_tex_bind(hCompositeAlphaMap, 1); +[.. do something with OpenGL that uses the currently bound texture] + +[at exit:] +// (done automatically, but this avoids it showing up as a leak) +(void)ogl_tex_free(hCompositeAlphaMap); + +*/ + #ifndef OGL_TEX_H__ #define OGL_TEX_H__ @@ -6,26 +114,63 @@ #include "lib/ogl.h" #include "tex.h" +// change default upload settings - these affect performance vs. quality. +// may be overridden for individual textures via ogl_tex_upload parameters. +// pass 0 to keep the current setting; defaults and legal values are: +// - filter: GL_LINEAR; any valid OpenGL minification filter +// - bpp : 32; 16 or 32 (this toggles between RGBA4 and RGBA8) +extern void ogl_tex_set_default_upload(GLint filter, uint bpp); + + // load and return a handle to the texture given in . -// supports RAW, BMP, JP2, PNG, TGA, DDS -extern Handle ogl_tex_load(const char* fn, int scope = 0); +// for a list of supported formats, see tex.h's tex_load. +extern Handle ogl_tex_load(const char* fn, uint flags = 0); -extern Handle ogl_tex_wrap(const char* fn, Tex* 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. +extern Handle ogl_tex_wrap(Tex* t, const char* fn = 0, uint flags = 0); +// free all resources associated with the texture and make further +// use of it impossible. (subject to refcount) extern int ogl_tex_free(Handle& ht); -extern GLint ogl_tex_filter; // GL values; default: GL_LINEAR -extern uint ogl_tex_bpp; // 16 or 32; default: 32 -// upload the specified texture to OpenGL. Texture filter and internal format -// may be specified to override the global defaults. -// side effect: binds the texture to the currently active unit. +// upload the texture to OpenGL. texture filter and [internal] format +// may be specified to override the global defaults (see below). +// side effects: +// - enables texturing on TMU 0 and binds the texture to it; +// - frees the texel data! see ogl_tex_get_data. extern int ogl_tex_upload(Handle ht, GLint filter_override = 0, GLint internal_fmt_override = 0, GLenum format_override = 0); +// bind the texture to the specified unit [number] in preparation for +// using it in rendering. assumes multitexturing is available. +// side effects: +// - enables (or disables, if == 0) texturing on the given unit. extern int ogl_tex_bind(Handle ht, GLenum unit = 0); + +// retrieve texture dimensions and bits per pixel. +// all params are optional and filled if non-NULL. extern int ogl_tex_get_size(Handle ht, int* w, int* h, int* bpp); + +// retrieve Tex.flags and the corresponding OpenGL format. +// all params are optional and filled if non-NULL. extern int ogl_tex_get_format(Handle ht, int* flags, GLenum* fmt); + +// 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 +// 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). extern int ogl_tex_get_data(Handle ht, void** p); #endif // #ifndef OGL_TEX_H__ diff --git a/source/lib/res/graphics/tex.h b/source/lib/res/graphics/tex.h index d2ea337622..ebadd8eb53 100755 --- a/source/lib/res/graphics/tex.h +++ b/source/lib/res/graphics/tex.h @@ -1,6 +1,6 @@ -// OpenGL texturing +// texture loaders and support functions // -// Copyright (c) 2003 Jan Wassenberg +// 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 @@ -72,6 +72,7 @@ struct Tex }; +// supports BMP, TGA, JPG, JP2, PNG, DDS. extern int tex_load(const char* fn, Tex* t); extern int tex_free(Tex* t); diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp index 858fb79eff..32cc6c4788 100755 --- a/source/lib/res/h_mgr.cpp +++ b/source/lib/res/h_mgr.cpp @@ -477,7 +477,7 @@ void h_mgr_shutdown() } - +//---------------------------------------------------------------------------- static int type_validate(H_Type type) { @@ -507,15 +507,131 @@ fail: } +static u32 gen_tag() +{ + static u32 tag; + if(++tag >= TAG_MASK) + { + debug_warn("h_mgr: tag overflow - allocations are no longer unique."\ + "may not notice stale handle reuse. increase TAG_BITS."); + tag = 1; + } + return tag; +} + + +static Handle reuse_existing_handle(uintptr_t key, H_Type type, uint flags) +{ + if(flags & RES_NO_CACHE) + return 0; + + // object of specified key and type doesn't exist yet + Handle h = h_find(type, key); + if(h <= 0) // "failure" is meaningless; we only care if found or not + return 0; + + HDATA* hd = h_data_tag_type(h, type); + if(hd->refs == REF_MAX) + { + debug_warn("reuse_existing: too many references to a handle - increase REF_BITS"); + return ERR_LIMIT; + } + + hd->refs++; + + // we are reactivating a closed but cached handle. + // need to generate a new tag so that copies of the + // previous handle can no longer access the resource. + // (we don't need to reset the tag in h_free, because + // use before this fails due to refs > 0 check in h_user_data). + if(hd->refs == 1) + { + const u32 tag = gen_tag(); + hd->tag = tag; + h = handle(h_idx(h), tag); // can't fail + } + + return h; +} + + +static int call_init_and_reload(Handle h, H_Type type, HDATA* hd, const char* fn, va_list* init_args) +{ + int err = 0; + H_VTbl* vtbl = type; // exact same thing but for clarity + + // init + if(vtbl->init) + vtbl->init(hd->user, *init_args); + + // reload + if(vtbl->reload) + { + // catch exception to simplify reload funcs - let them use new() + try + { + err = vtbl->reload(hd->user, fn, h); + } + catch(std::bad_alloc) + { + err = ERR_NO_MEM; + } + } + + return err; +} + + +static Handle alloc_new_handle(H_Type type, const char* fn, uintptr_t key, + uint flags, va_list* init_args) +{ + i32 idx; + HDATA* hd; + CHECK_ERR(alloc_idx(idx, hd)); + + // (don't want to do this before the add-reference exit, + // so as not to waste tags for often allocated handles.) + const u32 tag = gen_tag(); + Handle h = handle(idx, tag); // can't fail. + + hd->tag = tag; + hd->key = key; + hd->type = type; + hd->refs = 1; + if(!(flags & RES_NO_CACHE)) + hd->keep_open = 1; + hd->unique = (flags & RES_UNIQUE) != 0; + hd->fn = 0; + // .. filename is valid - store in hd + // note: if the original fn param was a key, it was reset to 0 above. + if(fn) + hd->fn = strdup(fn); + + if(key && !hd->unique) + add_key(key, h); + + int err = call_init_and_reload(h, type, hd, fn, init_args); + if(err < 0) + goto fail; + + return h; + +fail: + // reload failed; free the handle + hd->keep_open = 0; // disallow caching (since contents are invalid) + (void)h_free(h, type); // (h_free already does WARN_ERR) + + // note: since some uses will always fail (e.g. loading sounds if + // g_Quickstart), do not complain here. + return (Handle)err; +} + + // any further params are passed to type's init routine Handle h_alloc(H_Type type, const char* fn, uint flags, ...) { CHECK_ERR(type_validate(type)); - Handle h = 0; - i32 idx; - HDATA* hd; - // get key (either hash of filename, or fn param) uintptr_t key = 0; // not backed by file; fn is the key @@ -531,129 +647,24 @@ Handle h_alloc(H_Type type, const char* fn, uint flags, ...) } //debug_printf("alloc %s %s\n", type->name, fn); - + // no key => can never be found. disallow caching if(!key) - flags |= RES_NO_CACHE; // changes scope to RES_TEMP - // check if already loaded (cached) - else if(!(flags & RES_UNIQUE)) - { - // object already loaded? - h = h_find(type, key); - if(h > 0) - { - hd = h_data_tag_type(h, type); - if(hd->refs == REF_MAX) - { - debug_warn("h_alloc: too many references to a handle - increase REF_BITS"); - return 0; - } - hd->refs++; + flags |= RES_NO_CACHE; - // adding reference; already in-use - if(hd->refs > 1) - { -// debug_warn("adding reference to handle (undermines tag security check)"); - return h; - } - - // we re-activated a cached resource. still need to reset tag: - idx = h_idx(h); - goto skip_alloc; - } - } - - CHECK_ERR(alloc_idx(idx, hd)); -skip_alloc: - - const uint scope = flags & RES_SCOPE_MASK; - - // generate next tag value. - // don't want to do this before the add-reference exit, - // so as not to waste tags for often allocated handles. - static u32 tag; - if(++tag >= TAG_MASK) - { - debug_warn("h_alloc: tag overflow - allocations are no longer unique."\ - "may not notice stale handle reuse. increase TAG_BITS."); - tag = 1; - } - - h = handle(idx, tag); - // can't fail. - - // we are reactivating a closed but cached handle. - // need to reset tag, so that copies of the previous - // handle can no longer access the resource. - // (we don't need to reset the tag in h_free, because - // use before this fails due to refs > 0 check in h_user_data). - if(hd->refs == 1) - { - hd->tag = tag; + // see if we can reuse an existing handle + Handle h = reuse_existing_handle(key, type, flags); + // .. error + CHECK_ERR(h); + // .. successfully reused the handle; refcount increased + if(h > 0) return h; - } - - // - // now adding a new handle - // - hd->unique = (flags & RES_UNIQUE) != 0; - - if(key && !hd->unique) - add_key(key, h); - - // one-time init - hd->tag = tag; - hd->key = key; - hd->type = type; - hd->refs = 1; - if(scope != RES_TEMP) - hd->keep_open = 1; - hd->fn = 0; - // .. filename is valid - store in hd - // note: if the original fn param was a key, it was reset to 0 above. - if(fn) - hd->fn = strdup(fn); - - - H_VTbl* vtbl = type; - - int err; - - // init + // .. need to allocate a new one: va_list args; va_start(args, flags); - if(vtbl->init) - { - vtbl->init(hd->user, args); - } + h = alloc_new_handle(type, fn, key, flags, &args); va_end(args); - - // reload - if(vtbl->reload) - { - // catch exception to simplify reload funcs - let them use new() - try - { - err = vtbl->reload(hd->user, fn, h); - } - catch(std::bad_alloc) - { - err = ERR_NO_MEM; - } - if(err < 0) - goto fail; - } - - return h; - -fail: - // reload failed; free the handle - hd->keep_open = 0; // disallow caching (since contents are invalid) - (void)h_free(h, type); // (h_free already does WARN_ERR) - - // note: since some uses will always fail (e.g. loading sounds if - // g_Quickstart), do not complain here. - return (Handle)err; + return h; // alloc_new_handle already does CHECK_ERR } @@ -756,7 +767,7 @@ int h_force_free(Handle h, H_Type type) } -// increment Handle 's refcount. +// increment Handle 's reference count. // only meant to be used for objects that free a Handle in their dtor, // so that they are copy-equivalent and can be stored in a STL container. // do not use this to implement refcounting on top of the Handle scheme, @@ -771,11 +782,32 @@ void h_add_ref(Handle h) return; } + // if there are no refs, how did the caller manage to keep a Handle?! if(!hd->refs) debug_warn("h_add_ref: no refs open - resource is cached"); + hd->refs++; } -int res_cur_scope; +// retrieve the internal reference count or a negative error code. +// background: since h_alloc has no way of indicating whether it +// allocated a new handle or reused an existing one, counting references +// within resource control blocks is impossible. since that is sometimes +// necessary (always wrapping objects in Handles is excessive), we +// provide access to the internal reference count. +int h_get_refcnt(Handle h) +{ + HDATA* hd = h_data_tag(h); + if(!hd) + { + debug_warn("h_get_refcnt: invalid handle"); + return ERR_INVALID_PARAM; + } + // if there are no refs, how did the caller manage to keep a Handle?! + if(!hd->refs) + debug_warn("h_get_refcnt: no refs open - resource is cached"); + + return hd->refs; +} diff --git a/source/lib/res/h_mgr.h b/source/lib/res/h_mgr.h index 501fd29b9f..423455f2d7 100755 --- a/source/lib/res/h_mgr.h +++ b/source/lib/res/h_mgr.h @@ -16,6 +16,178 @@ // Jan.Wassenberg@stud.uni-karlsruhe.de // http://www.stud.uni-karlsruhe.de/~urkt/ +/* + +[KEEP IN SYNC WITH WIKI] + +introduction +------------ + +a resource is an instance of a specific type of game data (e.g. texture), +described by a control block (example fields: format, pointer to tex data). + +this module allocates storage for the control blocks, which are accessed +via handle. it also provides support for transparently reloading resources +from disk (allows in-game editing of data), and caches resource data. +finally, it frees all resources at exit, preventing leaks. + + +handles +------- + +handles are an indirection layer between client code and resources +(represented by their control blocks, which contains/points to its data). +they allow an important check not possible with a direct pointer: +guaranteeing the handle references a given resource /instance/. + +problem: code C1 allocates a resource, and receives a pointer p to its +control block. C1 passes p on to C2, and later frees it. +now other code allocates a resource, and happens to reuse the free slot +pointed to by p (also possible if simply allocating from the heap). +when C2 accesses p, the pointer is valid, but we cannot tell that +it is referring to a resource that had already been freed. big trouble. + +solution: each allocation receives a unique tag (a global counter that +is large enough to never overflow). Handles include this tag, as well +as a reference (array index) to the control block, which isn't directly +accessible. when dereferencing the handle, we check if the handle's tag +matches the copy stored in the control block. this protects against stale +handle reuse, double-free, and accidentally referencing other resources. + +type: each handle has an associated type. these must be checked to prevent +using textures as sounds, for example. with the manual vtbl scheme, +this type is actually a pointer to the resource object's vtbl, and is +set up via H_TYPE_DEFINE. this means that types are private to the module +that declared the handle; knowledge of the type ensures the caller +actually declared, and owns the resource. + + +guide to defining and using resources +------------------------------------- + +1) choose a name for the resource, used to represent all resources +of this type. we will call ours Res1; all occurences of it below +must be replaced with the actual name (exact spelling). +why? the vtbl builder defines its functions as e.g. Res1_reload; +your actual definition must match. + +2) declare its control block: +struct Res1 +{ +void* data1; // data loaded from file +uint flags; // set when resource is created +}; + +3) build its vtbl: +H_TYPE_DEFINE(Res1); + +this defines the symbol H_Res1, which is used whenever the handle +manager needs its type. it is only accessible to this module +(file scope). note that it is actually a pointer to the vtbl. +this must come before uses of H_Res1, and after the CB definition; +there are no restrictions WRT functions, because the macro +forward-declares what it needs. + +4) implement all 'virtual' functions from the resource interface. +note that inheritance isn't really possible with this approach - +all functions must be defined, even if not needed. + +-- + +init: +one-time init of the control block. called from h_alloc. +precondition: control block is initialized to 0. + +static void Type_init(Res1* r, va_list args) +{ +r->flags = va_arg(args, int); +} + +if the caller of h_alloc passed additional args, they are available +in args. if init references more args than were passed, big trouble. +however, this is a bug in your code, and cannot be triggered +maliciously. only your code knows the resource type, and it is the +only call site of h_alloc. +there is no provision for indicating failure. if one-time init fails +(rare, but one example might be failure to allocate memory that is +for the lifetime of the resource, instead of in reload), it will +have to set the control block state such that reload will fail. + +-- + +reload: +does all initialization of the resource that requires its source file. +called after init; also after dtor every time the file is reloaded. + +static int Type_reload(Res1* r, const char* filename, Handle); +{ +// somehow load stuff from filename, and store it in r->data1. +return 0; +} + +reload must abort if the control block data indicates the resource +has already been loaded! example: if texture's reload is called first, +it loads itself from file (triggering file.reload); afterwards, +file.reload will be called again. we can't avoid this, because the +handle manager doesn't know anything about dependencies +(here, texture -> file). +return value: 0 if successful (includes 'already loaded'), +negative error code otherwise. if this fails, the resource is freed +(=> dtor is called!). + +note that any subsequent changes to the resource state must be +stored in the control block and 'replayed' when reloading. +example: when uploading a texture, store the upload parameters +(filter, internal format); when reloading, upload again accordingly. + +-- + +dtor: +frees all data allocated by init and reload. called after h_free, +or at exit. control block will be zeroed afterwards. + +static void Type_dtor (Res1* r); +{ +// free memory r->data1 +} + +again no provision for reporting errors - there's no one to act on it +if called at exit. you can debug_assert or log the error, though. + +5) provide your layer on top of the handle manager: +Handle res1_load(const char* filename, int my_flags) +{ +return h_alloc(H_Res1, filename, 0, my_flags); // my_flags is passed to init +} + +int res1_free(Handle& h) +{ +return h_free(h, H_Res1); +// zeroes h afterwards +} + +(this layer allows a res_load interface on top of all the loaders, +and is necessary because your module is the only one that knows H_Res1). + +6) done. the resource will be freed at exit (if not done already). + +here's how to access the control block, given a handle: +Handle h; +a) H_DEREF(h, Res1, r); + +creates a variable r of type Res1*, which points to the control block +of the resource referenced by h. returns "invalid handle" +(a negative error code) on failure. +b) Res1* r = h_user_data(h, H_Res1); +if(!r) +; // bail + +useful if H_DEREF's error return (of type signed integer) isn't +acceptable. otherwise, prefer a) - this is pretty clunky, and +we could switch H_DEREF to throwing an exception on error. + +*/ + #ifndef H_MGR_H__ #define H_MGR_H__ @@ -164,7 +336,7 @@ enum RES_NO_CACHE = 1, // not cached, and will never reuse a previous instance - RES_UNIQUE = 1|16, + RES_UNIQUE = RES_NO_CACHE|16, // the resource isn't backed by a file. the fn parameter is treated as the search key (uintptr_t) // currently only used by mem manager @@ -202,8 +374,6 @@ extern const char* h_filename(Handle h); extern int h_reload(const char* fn); -extern int res_cur_scope; - // force the resource to be freed immediately, even if cached. // tag is not checked - this allows the first Handle returned // (whose tag will change after being 'freed', but remaining in memory) @@ -212,7 +382,7 @@ extern int res_cur_scope; // at that point, all (cached) OpenAL resources must be freed. extern int h_force_free(Handle h, H_Type type); -// increment Handle 's refcount. +// increment Handle 's reference count. // only meant to be used for objects that free a Handle in their dtor, // so that they are copy-equivalent and can be stored in a STL container. // do not use this to implement refcounting on top of the Handle scheme, @@ -220,181 +390,15 @@ extern int h_force_free(Handle h, H_Type type); // user load the resource; refcounting is done under the hood. extern void h_add_ref(Handle h); +// retrieve the internal reference count or a negative error code. +// background: since h_alloc has no way of indicating whether it +// allocated a new handle or reused an existing one, counting references +// within resource control blocks is impossible. since that is sometimes +// necessary (always wrapping objects in Handles is excessive), we +// provide access to the internal reference count. +extern int h_get_refcnt(Handle h); + extern void h_mgr_shutdown(void); #endif // #ifndef H_MGR_H__ - - -/* - -[KEEP IN SYNC WITH WIKI] - -introduction ------------- - -a resource is an instance of a specific type of game data (e.g. texture), -described by a control block (example fields: format, pointer to tex data). - -this module allocates storage for the control blocks, which are accessed -via handle. it also provides support for transparently reloading resources -from disk (allows in-game editing of data), and caches resource data. -finally, it frees all resources at exit, preventing leaks. - - -handles -------- - -handles are an indirection layer between client code and resources -(represented by their control blocks, which contains/points to its data). -they allow an important check not possible with a direct pointer: -guaranteeing the handle references a given resource /instance/. - -problem: code C1 allocates a resource, and receives a pointer p to its -control block. C1 passes p on to C2, and later frees it. -now other code allocates a resource, and happens to reuse the free slot -pointed to by p (also possible if simply allocating from the heap). -when C2 accesses p, the pointer is valid, but we cannot tell that -it is referring to a resource that had already been freed. big trouble. - -solution: each allocation receives a unique tag (a global counter that -is large enough to never overflow). Handles include this tag, as well -as a reference (array index) to the control block, which isn't directly -accessible. when dereferencing the handle, we check if the handle's tag -matches the copy stored in the control block. this protects against stale -handle reuse, double-free, and accidentally referencing other resources. - -type: each handle has an associated type. these must be checked to prevent -using textures as sounds, for example. with the manual vtbl scheme, -this type is actually a pointer to the resource object's vtbl, and is -set up via H_TYPE_DEFINE. this means that types are private to the module -that declared the handle; knowledge of the type ensures the caller -actually declared, and owns the resource. - - -guide to defining and using resources -------------------------------------- - -1) choose a name for the resource, used to represent all resources - of this type. we will call ours Res1; all occurences of it below - must be replaced with the actual name (exact spelling). - why? the vtbl builder defines its functions as e.g. Res1_reload; - your actual definition must match. - -2) declare its control block: - struct Res1 - { - void* data1; // data loaded from file - uint flags; // set when resource is created - }; - -3) build its vtbl: - H_TYPE_DEFINE(Res1); - - this defines the symbol H_Res1, which is used whenever the handle - manager needs its type. it is only accessible to this module - (file scope). note that it is actually a pointer to the vtbl. - this must come before uses of H_Res1, and after the CB definition; - there are no restrictions WRT functions, because the macro - forward-declares what it needs. - -4) implement all 'virtual' functions from the resource interface. - note that inheritance isn't really possible with this approach - - all functions must be defined, even if not needed. - - -- - - init: - one-time init of the control block. called from h_alloc. - precondition: control block is initialized to 0. - - static void Type_init(Res1* r, va_list args) - { - r->flags = va_arg(args, int); - } - - if the caller of h_alloc passed additional args, they are available - in args. if init references more args than were passed, big trouble. - however, this is a bug in your code, and cannot be triggered - maliciously. only your code knows the resource type, and it is the - only call site of h_alloc. - there is no provision for indicating failure. if one-time init fails - (rare, but one example might be failure to allocate memory that is - for the lifetime of the resource, instead of in reload), it will - have to set the control block state such that reload will fail. - - -- - - reload: - does all initialization of the resource that requires its source file. - called after init; also after dtor every time the file is reloaded. - - static int Type_reload(Res1* r, const char* filename, Handle); - { - // somehow load stuff from filename, and store it in r->data1. - return 0; - } - - reload must abort if the control block data indicates the resource - has already been loaded! example: if texture's reload is called first, - it loads itself from file (triggering file.reload); afterwards, - file.reload will be called again. we can't avoid this, because the - handle manager doesn't know anything about dependencies - (here, texture -> file). - return value: 0 if successful (includes 'already loaded'), - negative error code otherwise. if this fails, the resource is freed - (=> dtor is called!). - - note that any subsequent changes to the resource state must be - stored in the control block and 'replayed' when reloading. - example: when uploading a texture, store the upload parameters - (filter, internal format); when reloading, upload again accordingly. - - -- - - dtor: - frees all data allocated by init and reload. called after h_free, - or at exit. control block will be zeroed afterwards. - - static void Type_dtor (Res1* r); - { - // free memory r->data1 - } - - again no provision for reporting errors - there's no one to act on it - if called at exit. you can debug_assert or log the error, though. - -5) provide your layer on top of the handle manager: - Handle res1_load(const char* filename, int my_flags) - { - return h_alloc(H_Res1, filename, 0, my_flags); // my_flags is passed to init - } - - int res1_free(Handle& h) - { - return h_free(h, H_Res1); - // zeroes h afterwards - } - - (this layer allows a res_load interface on top of all the loaders, - and is necessary because your module is the only one that knows H_Res1). - -6) done. the resource will be freed at exit (if not done already). - - here's how to access the control block, given a handle: - Handle h; - a) H_DEREF(h, Res1, r); - - creates a variable r of type Res1*, which points to the control block - of the resource referenced by h. returns "invalid handle" - (a negative error code) on failure. - b) Res1* r = h_user_data(h, H_Res1); - if(!r) - ; // bail - - useful if H_DEREF's error return (of type signed integer) isn't - acceptable. otherwise, prefer a) - this is pretty clunky, and - we could switch H_DEREF to throwing an exception on error. - -*/ - diff --git a/source/lib/res/mem.cpp b/source/lib/res/mem.cpp index 2a7882a3db..fcd8701a67 100755 --- a/source/lib/res/mem.cpp +++ b/source/lib/res/mem.cpp @@ -317,14 +317,6 @@ void* mem_alloc(size_t size, const size_t align, uint flags, Handle* phm) if(size == 0) size = 1; - // no scope indicated - if(!flags) - // in a handle _reload function - default to its scope - if(res_cur_scope) - flags = res_cur_scope; - // otherwise, assume global scope - - void* raw_p; const size_t raw_size = size + align-1; diff --git a/source/lib/sysdep/ia32.cpp b/source/lib/sysdep/ia32.cpp index 647155fbba..a3e8f46800 100755 --- a/source/lib/sysdep/ia32.cpp +++ b/source/lib/sysdep/ia32.cpp @@ -317,8 +317,9 @@ static void get_cpu_type() // note: cpu_type is guaranteed to hold 48+1 chars, since that's the - // length of the CPU brand string. strcpy(cpu_type, literal) is safe. -#define SAFE_STRCPY strcpy + // length of the CPU brand string => we can safely copy short literals. + // (this macro hides us from 'unsafe string code' searches) +#define SAFE_STRCPY str##cpy // fall back to manual detect of CPU type because either: // - CPU doesn't support brand string (we use a flag to indicate this @@ -367,7 +368,7 @@ static void get_cpu_type() { // strip (tm) from Athlon string if(!strncmp(cpu_type, "AMD Athlon(tm)", 14)) - memmove(cpu_type+10, cpu_type+14, 34); + memmove(cpu_type+10, cpu_type+14, 35); // remove 2x (R) and CPU freq from P4 string float freq;