1
0
forked from 0ad/0ad

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.
This commit is contained in:
janwas 2005-09-21 16:11:43 +00:00
parent 8ba42d5a3c
commit d6f31e4157
7 changed files with 706 additions and 435 deletions

View File

@ -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 <bpp> and Tex <flags>.
// also choose an internal format based on the global <ogl_tex_bpp>
// also choose an internal format based on the global <default_bpp>
// 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
// <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)
//
// 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 <ogl_tex_filter> by Tex_init; user settings passed to
// set to <default_filter> 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 <fn>.
// 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.
//
// <fn> 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 <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;
{
// (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;
}

View File

@ -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 <fn>.
// 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.
//
// <fn> 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 <ht> == 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__

View File

@ -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);

View File

@ -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 <h>'s refcount.
// increment Handle <h>'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;
}

View File

@ -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 <h>'s refcount.
// increment Handle <h>'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.
*/

View File

@ -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;

View File

@ -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;