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:
parent
8ba42d5a3c
commit
d6f31e4157
@ -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;
|
||||
}
|
||||
|
@ -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__
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
@ -534,126 +650,21 @@ Handle h_alloc(H_Type type, const char* fn, uint flags, ...)
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
*/
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user