1
0
forked from 0ad/0ad

ogl_tex: work around bug #69; greatly simplify upload implementation; add support for emulating s3tc

tex: add support for generating mipmaps in software (simplifies
ogl_tex_upload)
all codecs: change interface, adding "restrict" keyword; rename util
functions to tex_util
tex_dds: support for decompressing images with mipmaps included

This was SVN commit r2909.
This commit is contained in:
janwas 2005-10-12 04:41:28 +00:00
parent b53e236a02
commit c1ec44d751
10 changed files with 880 additions and 683 deletions

View File

@ -18,6 +18,8 @@
#include "precompiled.h"
#include "sysdep/gfx.h"
#include "lib.h"
#include "../res.h"
#include "ogl.h"
@ -43,6 +45,7 @@ static bool filter_valid(GLint filter)
default:
return false;
}
UNREACHABLE;
}
@ -59,6 +62,7 @@ static bool wrap_valid(GLint wrap)
default:
return false;
}
UNREACHABLE;
}
@ -74,6 +78,23 @@ static bool filter_uses_mipmaps(GLint filter)
default:
return false;
}
UNREACHABLE;
}
static bool fmt_is_s3tc(GLenum fmt)
{
switch(fmt)
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return true;
default:
return false;
}
UNREACHABLE;
}
@ -174,10 +195,11 @@ static GLint choose_int_fmt(GLenum fmt, uint q_flags)
// true => 4 bits per component; otherwise, 8
const bool half_bpp = (q_flags & OGL_TEX_HALF_BPP) != 0;
// early-out for S3TC textures. they don't need an internal format
// (because upload is via glCompressedTexImage2DARB), but we return
// a meaningful value anyway and avoid triggering the warning below.
if(ogl_dxt_from_fmt(fmt))
// early-out for S3TC textures: they don't need an internal format
// (because upload is via glCompressedTexImage2DARB), but we must avoid
// triggering the default case below. we might as well return a
// meaningful value (i.e. int_fmt = fmt).
if(fmt_is_s3tc(fmt))
return fmt;
switch(fmt)
@ -339,6 +361,8 @@ struct OglTex
// indicates if this texture is currently uploaded.
// used by state setters; reset in dtor.
uint is_currently_uploaded : 1;
uint tex_valid : 1;
};
H_TYPE_DEFINE(OglTex);
@ -360,6 +384,7 @@ static void OglTex_init(OglTex* ot, va_list args)
static void OglTex_dtor(OglTex* ot)
{
if(ot->tex_valid)
(void)tex_free(&ot->t);
glDeleteTextures(1, &ot->id);
@ -382,6 +407,7 @@ static int OglTex_reload(OglTex* ot, const char* fn, Handle h)
// Tex object (i.e. copy its values and be done).
if(!ot->was_wrapped)
RETURN_ERR(tex_load(fn, &ot->t));
ot->tex_valid = 1;
ot->is_loaded = 1;
glGenTextures(1, &ot->id);
@ -394,6 +420,46 @@ static int OglTex_reload(OglTex* ot, const char* fn, Handle h)
return 0;
}
static int OglTex_validate(const OglTex* ot)
{
RETURN_ERR(tex_validate(&ot->t));
// width, height
// (note: this is done here because tex.cpp doesn't impose any
// restrictions on dimensions, while OpenGL does).
GLsizei w = (GLsizei)ot->t.w;
GLsizei h = (GLsizei)ot->t.h;
// .. == 0; texture file probably not loaded successfully.
if(w == 0 || h == 0)
return -100;
// .. greater than max supported tex dimension.
// no-op if oglInit not yet called
if(w > (GLsizei)ogl_max_tex_size || h > (GLsizei)ogl_max_tex_size)
return -101;
// .. not power-of-2.
// note: we can't work around this because both NV_texture_rectangle
// and subtexture require work for the client (changing tex coords).
// TODO: ARB_texture_non_power_of_two
if(!is_pow2(w) || !is_pow2(h))
return -102;
// texture state
if(!filter_valid(ot->state.filter))
return -103;
if(!wrap_valid(ot->state.wrap))
return -104;
// misc
if(!q_flags_valid(ot->q_flags))
return -105;
if(ot->tmu >= 128) // unexpected that there will ever be this many
return -106;
// .. note: don't check ot->fmt and ot->int_fmt - they aren't set
// until during ogl_tex_upload.
return 0;
}
// load and return a handle to the texture given in <fn>.
// for a list of supported formats, see tex.h's tex_load.
@ -438,89 +504,37 @@ int ogl_tex_free(Handle& ht)
}
static int ogl_tex_validate(const uint line, const OglTex* ot)
{
RETURN_ERR(tex_validate(line, &ot->t));
const char* msg = 0;
int err = -1;
// width, height
// (note: this is done here because tex.cpp doesn't impose any
// restrictions on dimensions, while OpenGL does).
GLsizei w = (GLsizei)ot->t.w;
GLsizei h = (GLsizei)ot->t.h;
// .. if w or h is 0, texture file probably not loaded successfully.
if(w == 0 || h == 0)
msg = "width or height is 0 - texture probably not loaded successfully";
// .. greater than max supported tex dimension?
// no-op if oglInit not yet called
if(w > (GLsizei)ogl_max_tex_size || h > (GLsizei)ogl_max_tex_size)
msg = "texture dimensions exceed OpenGL implementation limit";
// .. both NV_texture_rectangle and subtexture require work for the client
// (changing tex coords) => we'll just disallow non-power of 2 textures.
// TODO: ARB_texture_non_power_of_two
if(!is_pow2(w) || !is_pow2(h))
msg = "width or height is not a power-of-2";
// texture state
GLint filter = ot->state.filter;
GLint wrap = ot->state.wrap;
if(!filter_valid(filter))
msg = "invalid filter";
if(!wrap_valid(wrap))
msg = "invalid wrap mode";
// misc
if(!q_flags_valid(ot->q_flags))
msg = "invalid q_flags";
if(ot->tmu >= 128)
msg = "TMU invalid? it's >= 128!";
// .. note: don't check ot->fmt and ot->int_fmt - they aren't set
// until during ogl_tex_upload.
if(msg)
{
debug_printf("ogl_tex_validate at line %d failed: %s (error code %d)\n", line, msg, err);
debug_warn("ogl_tex_validate failed");
return err;
}
return 0;
}
#define CHECK_OGL_TEX(ot) CHECK_ERR(ogl_tex_validate(__LINE__, ot))
//----------------------------------------------------------------------------
// state setters (see "Texture Parameters" in docs)
// we require the below functions be called before uploading; this avoids
// redundant glTexParameter calls (we'd otherwise need to always
// potentially redundant glTexParameter calls (we'd otherwise need to always
// set defaults because we don't know if an override is forthcoming).
//
// this raises a warning for purposes of debugging if the texture has
// already been uploaded (in violation of the above requirement).
// raise a debug warning if the texture has already been uploaded
// (except in the few cases where this is allowed; see below).
// this is so that you will notice incorrect usage - only one instance of a
// texture should be active at a time, because otherwise they vie for
// control of one shared OglTexState.
static void warn_if_uploaded(Handle ht, const OglTex* ot)
{
#ifndef NDEBUG
// note: if a texture is ogl_tex_load-ed several times, each caller
// (typically a higher-level LoadTexture) will want to set
// e.g. its filter. however, after the first time, the texture will
// have been uploaded, making this pointless (and illegal).
//
// we do not want to complain, though, since the caller's intent is
// legitimate. ideally LoadTexture would take care of this by
// skipping the set/upload calls if the texture wasn't newly loaded,
// but we'll also have to work around this here to be safe.
//
// unfortunately, h_alloc provides no direct way of notifying its
// caller whether it actually loaded anew or reactivated a
// cached resource. we have to check the reference count.
// we do not require users of this module to remember if they've
// already uploaded a texture (inconvenient). since they also can't
// tell if the texture was newly loaded (due to h_alloc interface),
// we have to squelch this warning in 2 cases:
// - it's ogl_tex_loaded several times (i.e. refcount > 1) and the
// caller (typically a higher-level LoadTexture) is setting filter etc.
// - caller is using our Handle as a caching mechanism, and calls
// ogl_tex_set_* before every use of the texture. note: this
// need not fall under the above check, e.g. if freed but cached.
// workaround is that ogl_tex_set_* won't call us if the
// same state values are being set (harmless anyway).
int refs = h_get_refcnt(ht);
if(refs == 1 && ot->is_currently_uploaded)
if(refs > 1)
return; // don't complain
if(ot->is_currently_uploaded)
debug_warn("ogl_tex_set_*: texture already uploaded and shouldn't be changed");
#else
// (prevent warnings; the alternative of wrapping all call sites in
@ -538,13 +552,15 @@ static void warn_if_uploaded(Handle ht, const OglTex* ot)
int ogl_tex_set_filter(Handle ht, GLint filter)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
if(!filter_valid(filter))
CHECK_ERR(ERR_INVALID_PARAM);
if(ot->state.filter != filter)
{
warn_if_uploaded(ht, ot);
ot->state.filter = filter;
}
return 0;
}
@ -556,13 +572,15 @@ int ogl_tex_set_filter(Handle ht, GLint filter)
int ogl_tex_set_wrap(Handle ht, GLint wrap)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
if(!wrap_valid(wrap))
CHECK_ERR(ERR_INVALID_PARAM);
if(ot->state.wrap != wrap)
{
warn_if_uploaded(ht, ot);
ot->state.wrap = wrap;
}
return 0;
}
@ -570,6 +588,262 @@ int ogl_tex_set_wrap(Handle ht, GLint wrap)
//----------------------------------------------------------------------------
// upload
static get_mipmaps(Tex* t, GLint filter, uint q_flags, int* plevels_to_skip)
{
// decisions:
// .. does this OpenGL implementation support auto mipmap generation?
static const bool have_auto_mipmap_gen = oglHaveVersion("1.4") || oglHaveExtension("GL_SGIS_generate_mipmap");
// .. does filter call for uploading mipmaps?
const bool need_mipmaps = filter_uses_mipmaps(filter);
// .. does the image data include mipmaps? (stored as separate
// images after the regular texels)
const bool includes_mipmaps = (t->flags & TEX_MIPMAPS) != 0;
// .. is this texture in S3TC format? (more generally, "compressed")
const bool is_s3tc = (t->flags & TEX_DXT) != 0;
*plevels_to_skip = TEX_BASE_LEVEL_ONLY;
if(!need_mipmaps)
return 0;
// image already contains pregenerated mipmaps; we need do nothing.
// this is the nicest case, because they are fastest to load
// (no extra processing needed) and typically filtered better than
// if automatically generated.
if(includes_mipmaps)
*plevels_to_skip = 0; // t contains mipmaps
// OpenGL supports automatic generation; we need only
// activate that and upload the base image.
else if(have_auto_mipmap_gen)
{
// note: we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS
// have the same values - it's heavily implied by the spec
// governing 'promoted' ARB extensions and just plain makes sense.
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
}
// image is S3TC-compressed and the previous 2 alternatives weren't
// available; we're going to cheat and just disable mipmapping.
// rationale: having tex_transform add mipmaps would be slow (since
// all<->all transforms aren't implemented, it'd have to decompress
// from S3TC first), and DDS images ought to include mipmaps!
else if(is_s3tc)
return -1;
// image is uncompressed and we're on an old OpenGL implementation;
// we will generate mipmaps in software.
else
{
RETURN_ERR(tex_transform_to(t, t->flags|TEX_MIPMAPS));
*plevels_to_skip = 0; // t contains mipmaps
}
// t contains mipmaps; we can apply our resolution reduction trick:
if(*plevels_to_skip == 0)
{
// this saves texture memory by skipping some of the lower
// (high-resolution) mip levels.
//
// note: we don't just use GL_TEXTURE_BASE_LEVEL because it would
// require uploading unused levels, which is wasteful.
// .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags.
const uint reduce = (q_flags & OGL_TEX_HALF_RES)? 2 : 1;
*plevels_to_skip = log2(reduce);
}
return 0;
}
struct UploadParams
{
GLenum fmt;
GLint int_fmt;
};
static void upload_level(uint level, uint level_w, uint level_h,
const u8* restrict level_data, size_t UNUSED(level_data_size), void* restrict ctx)
{
const UploadParams* up = (const UploadParams*)ctx;
glTexImage2D(GL_TEXTURE_2D, level, up->int_fmt, level_w, level_h, 0,
up->fmt, GL_UNSIGNED_BYTE, level_data);
}
static void upload_compressed_level(uint level, uint level_w, uint level_h,
const u8* restrict level_data, size_t level_data_size, void* restrict ctx)
{
const UploadParams* up = (const UploadParams*)ctx;
glCompressedTexImage2DARB(GL_TEXTURE_2D, level, up->fmt,
(GLsizei)level_w, (GLsizei)level_h, 0, (GLsizei)level_data_size, level_data);
}
// upload the texture in the specified (internal) format.
// split out of ogl_tex_upload because it was too big.
//
// pre: <t> is valid for OpenGL use; texture is bound.
static void upload_impl(Tex* t, GLenum fmt, GLint int_fmt, int levels_to_skip)
{
const GLsizei w = (GLsizei)t->w;
const GLsizei h = (GLsizei)t->h;
const uint bpp = t->bpp;
const u8* data = (const u8*)tex_get_data(t);
const UploadParams up = { fmt, int_fmt };
if(t->flags & TEX_DXT)
tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 4, upload_compressed_level, (void*)&up);
else
tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 1, upload_level, (void*)&up);
}
static bool detect_s3tc()
{
// 1. require extensions to be advertised.
// note: we don't bother checking for GL_S3_s3tc - it is incompatible
// and irrelevant (was never widespread).
bool have_s3tc = oglHaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", 0) == 0;
// 2. exclude any card/driver combos on which it is known to break.
if(gfx_card[0] == '\0')
debug_warn("ogl_tex requires get_gfx_info be called before ogl_tex_upload");
if(!strcmp(gfx_card, "S3 SuperSavage/IXC 1014"))
{
if(strstr(gfx_drv_ver, "ssicdnt.dll (2.60.115)"))
have_s3tc = false;
}
return have_s3tc;
}
// upload the texture to OpenGL.
// if not 0, parameters override the following:
// fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags;
// q_flags_ovr : global default "quality vs. performance" flags;
// int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags.
//
// side effects:
// - enables texturing on TMU 0 and binds the texture to it;
// - frees the texel data! see ogl_tex_get_data.
int ogl_tex_upload(const Handle ht, GLenum fmt_ovr, uint q_flags_ovr, GLint int_fmt_ovr)
{
H_DEREF(ht, OglTex, ot);
Tex* t = &ot->t;
const char* fn = h_filename(ht);
if(!fn)
fn = "(could not determine filename)";
debug_assert(q_flags_valid(q_flags_ovr));
// we don't bother verifying *fmt_ovr - there are too many values
// upload already happened; no work to do.
// (this also happens if a cached texture is "loaded")
if(ot->is_currently_uploaded)
return 0;
// decompress S3TC if that's not supported by OpenGL.
static const bool have_s3tc = detect_s3tc();
if((t->flags & TEX_DXT) && !have_s3tc)
{
//ONCE(DISPLAY_ERROR(L"Performance warning: your graphics card does not support compressed textures. The game will try to continue anyway, but may be slower than expected. Please try updating your graphics drivers; if that doesn't help, please try upgrading your hardware."));
(void)tex_transform_to(t, t->flags & ~TEX_DXT);
}
// determine fmt and int_fmt, allowing for user override.
ot->fmt = choose_fmt(t->bpp, t->flags);
if(fmt_ovr) ot->fmt = fmt_ovr;
if(q_flags_ovr) ot->q_flags = q_flags_ovr;
ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags);
if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr;
// now actually send to OpenGL:
oglCheck();
{
// (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can
// fail in debug builds if OglTex.id isn't a valid texture name)
RETURN_ERR(ogl_tex_bind(ht, ot->tmu));
int levels_to_skip;
if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0)
// error => disable mipmapping
ot->state.filter = GL_LINEAR;
// (note: if first time, applies our defaults/previous overrides;
// otherwise, replays all state changes)
state_latch(&ot->state);
upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip);
}
oglCheck();
ot->need_auto_upload = 1;
ot->is_currently_uploaded = 1;
// see rationale for <refs> at declaration of OglTex.
// note: tex_free is safe even if this OglTex was wrapped -
// the Tex contains a mem handle.
int refs = h_get_refcnt(ht);
if(refs == 1)
{
tex_free(t);
ot->tex_valid = 0;
}
return 0;
}
//----------------------------------------------------------------------------
// getters
// retrieve texture dimensions and bits per pixel.
// all params are optional and filled if non-NULL.
int ogl_tex_get_size(Handle ht, uint* w, uint* h, uint* bpp)
{
H_DEREF(ht, OglTex, ot);
if(w)
*w = ot->t.w;
if(h)
*h = ot->t.h;
if(bpp)
*bpp = ot->t.bpp;
return 0;
}
// retrieve Tex.flags and the corresponding OpenGL format.
// the latter is determined during ogl_tex_upload and is 0 before that.
// all params are optional and filled if non-NULL.
int ogl_tex_get_format(Handle ht, uint* flags, GLenum* fmt)
{
H_DEREF(ht, OglTex, ot);
if(flags)
*flags = ot->t.flags;
if(fmt)
{
if(!ot->is_currently_uploaded)
debug_warn("ogl_tex_get_format: hasn't been defined yet!");
*fmt = ot->fmt;
}
return 0;
}
// retrieve pointer to texel data.
//
// note: this memory is freed after a successful ogl_tex_upload for
// this texture. after that, the pointer we retrieve is NULL but ps_dbg.exe!ogl_tex_set_filter(__int64 ht=476741374144, int filter=9729) Line 490 + 0x4a C++
// the function doesn't fail (negative return value) by design.
// if you still need to get at the data, add a reference before
// uploading it or read directly from OpenGL (discouraged).
int ogl_tex_get_data(Handle ht, void** p)
{
H_DEREF(ht, OglTex, ot);
*p = tex_get_data(&ot->t);
return 0;
}
//----------------------------------------------------------------------------
// misc API
// bind the texture to the specified unit [number] in preparation for
// using it in rendering. assumes multitexturing is available.
// not necessary before calling ogl_tex_upload!
@ -596,8 +870,6 @@ int ogl_tex_bind(const Handle ht, GLenum unit)
UNREACHABLE;
}
CHECK_OGL_TEX(ot);
#ifndef NDEBUG
if(!ot->id)
{
@ -623,337 +895,11 @@ disable_texturing:
}
// this has gotten to be quite complex. due to various fallbacks, we
// implement this as an automaton with the following states:
enum UploadState
{
auto_uncomp, auto_comp,
mipped_uncomp, mipped_comp,
normal_uncomp, normal_comp,
broken_comp,
glubuild
};
// use parameters describing a texture to decide which upload method is
// called for; returns one of the above "states".
static UploadState determine_upload_state(GLenum fmt, GLint filter, uint tex_flags)
{
/*
There are various combinations of desires/abilities, relating to how
(and whether) mipmaps should be generated. Currently there are only
2^4 of them:
/mipmaps available in texture
#######
/mipmaps needed
#######
.---+---+---+---.
| Au| Mu| Nu| Nu|#-auto mipmap generation available
|---+---+---+---|#
| Ac| Mc| Nc| Nc|# #-texture is compressed
|---+---+---+---|# #
| X | Mc| Nc| Nc| #
|---+---+---+---| #
| G | Mu| Nu| Nu|
`---+---+---+---'
Au (auto_uncomp) = auto_mipmap_gen, then 'Nu'
Ac (auto_comp) = auto_mipmap_gen, then 'Nc'
X (broken_comp) = failure; just fall back to GL_LINEAR and 'Nc'
G (glubuild) = gluBuild2DMipmaps
Mu (mipped_uncomp) = glTexImage2D, mipmap levels
Mc (mipped_comp) = glCompressedTexImage2DARB, mipmap levels
Nu (normal_uncomp) = glTexImage2D
Nc (normal_comp) = glCompressedTexImage2DARB
if (Au || Ac)
enable automatic mipmap generation
switch to 'N*'
if (X)
set GL_LINEAR
switch to 'Nc'
if (G)
gluBuild2DMipmaps
if (Nu)
glTexImage2D
if (Nc)
glCompressedTexImage2DARB
if (Mu)
for each mipmap level
glTexImage2D
if (Mc)
for each mipmap level
glCompressedTexImage2DARB
[This documentation feels like more than is really necessary, but
hopefully it'll prevent the logic getting horribly tangled...]
*/
// decisions:
// .. does filter call for uploading mipmaps?
const bool need_mipmaps = filter_uses_mipmaps(filter);
// .. does this OpenGL implementation support auto mipmap generation?
static const bool auto_mipmap_gen = oglHaveVersion("1.4") || oglHaveExtension("GL_SGIS_generate_mipmap");
// .. is this texture in S3TC format? (more generally, "compressed")
const bool is_s3tc = ogl_dxt_from_fmt(fmt) != 0;
// .. does the image data include mipmaps? (stored as separate
// images after the regular texels)
const bool includes_mipmaps = (tex_flags & TEX_MIPMAPS) != 0;
static const UploadState states[4][4] =
{
{ auto_uncomp, mipped_uncomp, normal_uncomp, normal_uncomp },
{ auto_comp, mipped_comp, normal_comp, normal_comp },
{ broken_comp, mipped_comp, normal_comp, normal_comp },
{ glubuild, mipped_uncomp, normal_uncomp, normal_uncomp }
};
const int row = auto_mipmap_gen ? (is_s3tc ? 1 : 0) : (is_s3tc ? 2 : 3);
const int col = need_mipmaps ? (includes_mipmaps ? 1 : 0) : (includes_mipmaps ? 2 : 3);
return states[row][col];
}
// <data> holds the image as well as all mip levels (stored consecutively).
// upload them with the given internal format and quality flags.
//
// this is called whenever possible because pregenerated mipmaps are
// higher-quality and faster than gluBuildMipmaps resp. automatic generation.
//
// pre: w, h > 0; texture is bound.
static void upload_mipmaps(uint w, uint h, uint bpp, const u8* data,
UploadState state, GLenum fmt, GLint int_fmt, uint q_flags)
{
GLsizei level_w = w;
GLsizei level_h = h;
// resolution reduction (see OGL_TEX_HALF_RES for rationale).
// this effectively reduces resolution by skipping some of the
// lower (high-resolution) mip levels.
//
// we iterate through the loop (necessary to skip over image data),
// but do not actually upload until the requisite number of
// levels have been skipped (i.e. level == 0).
//
// note: we don't just use GL_TEXTURE_BASE_LEVEL because it would
// require uploading unused levels, which is wasteful.
// .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags.
const uint reduce = (q_flags & OGL_TEX_HALF_RES)? 2 : 1;
const uint levels_to_skip = log2(reduce);
int level = -(int)levels_to_skip;
// until at level 1x1:
for(;;)
{
GLsizei mip_size; // used to skip past this mip level in <data>
if(state == mipped_uncomp)
{
mip_size = level_w * level_h * bpp/8;
if(level >= 0)
glTexImage2D(GL_TEXTURE_2D, level, int_fmt, level_w, level_h, 0, fmt, GL_UNSIGNED_BYTE, data);
}
else // state == mipped_comp
{
mip_size = (GLsizei)(round_up(level_w, 4) * round_up(level_h, 4) * bpp/8);
if(level >= 0)
glCompressedTexImage2DARB(GL_TEXTURE_2D, level, fmt, level_w, level_h, 0, mip_size, data);
}
data += mip_size;
// 1x1 reached - done
if(level_w == 1 && level_h == 1)
break;
level_w /= 2;
level_h /= 2;
// if the texture is non-square, one of the dimensions will become
// 0 before the other. to satisfy OpenGL's expectations, change it
// back to 1.
if(level_w == 0) level_w = 1;
if(level_h == 0) level_h = 1;
level++;
}
}
// upload manager: given all texture format details, determines the
// initial "state" and runs through the upload automaton.
//
// split out of ogl_tex_upload because it was too big.
//
// pre: <t> is valid for OpenGL use; texture is bound.
static void upload_impl(const Tex* t, GLenum fmt, GLint int_fmt, GLint filter, uint q_flags)
{
// convenient local copies (t has been validated already).
const GLsizei w = (GLsizei)t->w;
const GLsizei h = (GLsizei)t->h;
const uint bpp = t->bpp; // used for S3TC/mipmap size calc
const uint flags = t->flags; // tells us if img holds mipmaps
const u8* data = (const u8*)tex_get_data(t);
UploadState state = determine_upload_state(fmt, filter, flags);
if(state == auto_uncomp || state == auto_comp)
{
// notes:
// - if this state is reached, OpenGL supports auto mipmap gen
// (we check for that above)
// - we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS
// have the same values - it's heavily implied by the spec
// governing 'promoted' ARB extensions and just plain makes sense.
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
state = (state == auto_comp)? normal_comp : normal_uncomp;
}
if(state == broken_comp)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
state = normal_comp;
}
if(state == glubuild)
gluBuild2DMipmaps(GL_TEXTURE_2D, int_fmt, w, h, fmt, GL_UNSIGNED_BYTE, data);
else if(state == normal_uncomp)
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, w, h, 0, fmt, GL_UNSIGNED_BYTE, data);
else if(state == normal_comp)
{
const GLsizei tex_size = w * h * bpp / 8;
glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, fmt, w, h, 0, tex_size, data);
}
else if(state == mipped_uncomp || state == mipped_comp)
upload_mipmaps(w, h, bpp, data, state, fmt, int_fmt, q_flags);
else
debug_warn("invalid state in ogl_tex_upload");
}
// upload the texture to OpenGL.
// if not 0, parameters override the following:
// fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags;
// q_flags_ovr : global default "quality vs. performance" flags;
// int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags.
//
// side effects:
// - enables texturing on TMU 0 and binds the texture to it;
// - frees the texel data! see ogl_tex_get_data.
int ogl_tex_upload(const Handle ht, GLenum fmt_ovr, uint q_flags_ovr, GLint int_fmt_ovr)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
debug_assert(q_flags_valid(q_flags_ovr));
// we don't bother verifying *fmt_ovr - there are too many values
const char* fn = h_filename(ht);
if(!fn)
fn = "(could not determine filename)";
// someone's requesting upload, but has already been uploaded.
// this happens if a cached texture is "loaded". no work to do.
if(ot->is_currently_uploaded)
return 0;
// determine fmt and int_fmt, allowing for user override.
ot->fmt = choose_fmt(ot->t.bpp, ot->t.flags);
if(fmt_ovr) ot->fmt = fmt_ovr;
if(q_flags_ovr) ot->q_flags = q_flags_ovr;
ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags);
if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr;
// we know ht is valid (H_DEREF above), but ogl_tex_bind can
// fail in debug builds if OglTex.id isn't a valid texture name
CHECK_ERR(ogl_tex_bind(ht, ot->tmu));
// if first time: apply our defaults/previous overrides;
// otherwise, replays all state changes.
state_latch(&ot->state);
upload_impl(&ot->t, ot->fmt, ot->int_fmt, ot->state.filter, ot->q_flags);
// see rationale for <refs> at declaration of OglTex.
// note: tex_free is safe even if this OglTex was wrapped -
// the Tex contains a mem handle.
int refs = h_get_refcnt(ht);
if(refs == 1)
tex_free(&ot->t);
ot->need_auto_upload = 1;
ot->is_currently_uploaded = 1;
#ifndef NDEBUG
oglCheck();
#endif
return 0;
}
//----------------------------------------------------------------------------
// getters
// retrieve texture dimensions and bits per pixel.
// all params are optional and filled if non-NULL.
int ogl_tex_get_size(Handle ht, uint* w, uint* h, uint* bpp)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
if(w)
*w = ot->t.w;
if(h)
*h = ot->t.h;
if(bpp)
*bpp = ot->t.bpp;
return 0;
}
// retrieve Tex.flags and the corresponding OpenGL format.
// the latter is determined during ogl_tex_upload and is 0 before that.
// all params are optional and filled if non-NULL.
int ogl_tex_get_format(Handle ht, uint* flags, GLenum* fmt)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
if(flags)
*flags = ot->t.flags;
if(fmt)
{
if(!ot->is_currently_uploaded)
debug_warn("ogl_tex_get_format: hasn't been defined yet!");
*fmt = ot->fmt;
}
return 0;
}
// retrieve pointer to texel data.
//
// note: this memory is freed after a successful ogl_tex_upload for
// this texture. after that, the pointer we retrieve is NULL but ps_dbg.exe!ogl_tex_set_filter(__int64 ht=476741374144, int filter=9729) Line 490 + 0x4a C++
// the function doesn't fail (negative return value) by design.
// if you still need to get at the data, add a reference before
// uploading it or read directly from OpenGL (discouraged).
int ogl_tex_get_data(Handle ht, void** p)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
*p = tex_get_data(&ot->t);
return 0;
}
//----------------------------------------------------------------------------
// misc API
// apply the specified transforms (as in tex_transform) to the image.
// must be called before uploading (raises a warning if called afterwards).
int ogl_tex_transform(Handle ht, uint transforms)
{
H_DEREF(ht, OglTex, ot);
CHECK_OGL_TEX(ot);
int ret = tex_transform(&ot->t, transforms);
return ret;
}

View File

@ -23,13 +23,15 @@
Introduction
------------
This module simplifies use of textures in OpenGL. It provides an
easy-to-use load/upload/bind/free API that completely replaces
This module simplifies use of textures in OpenGL. An easy-to-use
load/upload/bind/free API is provided, which 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.
Additionally, the upload step provides for trading quality vs. speed
and works around older hardware/drivers.
Texture Parameters
@ -68,6 +70,13 @@ older graphics cards without requiring anything else to be changed.
Textures with specific quality needs can override this via
ogl_tex_set_* or ogl_tex_upload parameters.
Finally, provision is made for coping with hardware/drivers lacking support
for S3TC decompression or mipmap generation: that can be done in software,
if necessary. This avoids the need for alternate asset formats and
lowers hardware requirements. While such cards probably won't run
the app very well (due to their age and lack of other capabilities),
this does make possible developing/testing on older machines/laptops.
Caching and Texture Instances
-----------------------------
@ -90,38 +99,39 @@ 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);
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]
[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);
[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, override filter,
specify internal_format and use multitexturing.
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_set_filter(hCompositeAlphaMap, GL_LINEAR);
(void)ogl_tex_upload(hCompositeAlphaMap, 0, 0, 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]
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_set_filter(hCompositeAlphaMap, GL_LINEAR);
(void)ogl_tex_upload(hCompositeAlphaMap, 0, 0, GL_INTENSITY);
// (your responsibility! tex_wrap attaches a reference but it is
// removed by ogl_tex_upload.)
free(data);
[at exit:]
// (done automatically, but this avoids it showing up as a leak)
(void)ogl_tex_free(hCompositeAlphaMap);
[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);
*/

View File

@ -24,57 +24,47 @@
#include <algorithm>
#include "lib.h"
#include "timer.h"
#include "../res.h"
#include "tex.h"
#include "tex_codec.h"
// be careful not to use other tex_* APIs here because they call us.
int tex_validate(const uint line, const Tex* t)
int tex_validate(const Tex* t)
{
const char* msg = 0;
int err = -1;
// texture data
// pixel data
size_t tex_file_size;
void* tex_file = mem_get_ptr(t->hm, &tex_file_size);
// .. only check validity if the image is still in memory.
// (e.g. ogl_tex frees the data after uploading to GL)
if(tex_file)
{
// file size smaller than header+pixels.
// possible causes: texture file header is invalid,
// or file wasn't loaded completely.
if(tex_file_size < t->ofs + t->w*t->h*t->bpp/8)
msg = "file size smaller than header+texels";
return -2;
}
// bits per pixel
// (we don't bother checking all values; a sanity check is enough)
if(t->bpp % 4 || t->bpp > 32)
msg = "invalid bpp? should be one of {4,8,16,24,32}";
return -3;
// flags
// .. DXT
// .. DXT value
const uint dxt = t->flags & TEX_DXT;
if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5)
msg = "invalid DXT in flags";
return -4;
// .. orientation
const uint orientation = t->flags & TEX_ORIENTATION;
if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN))
msg = "invalid orientation in flags";
if(msg)
{
debug_printf("tex_validate at line %d failed: %s (error code %d)\n", line, msg, err);
debug_warn("tex_validate failed");
return err;
}
return -5;
return 0;
}
#define CHECK_TEX(t) CHECK_ERR(tex_validate(__LINE__, t))
// check if the given texture format is acceptable: 8bpp grey,
// 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
@ -108,6 +98,77 @@ static int validate_format(uint bpp, uint flags)
}
struct CreateLevelData
{
uint num_components;
uint prev_level_w;
uint prev_level_h;
const u8* prev_level_data;
size_t prev_level_data_size;
};
// uses 2x2 box filter
static void create_level(uint level, uint level_w, uint level_h,
const u8* restrict level_data, size_t level_data_size, void* restrict ctx)
{
CreateLevelData* cld = (CreateLevelData*)ctx;
const u8* src = cld->prev_level_data;
u8* dst = (u8*)level_data;
// base level - must be copied over from source buffer
if(level == 0)
{
debug_assert(level_data_size == cld->prev_level_data_size);
memcpy(dst, src, level_data_size);
}
else
{
const uint num_components = cld->num_components;
const size_t dx = num_components, dy = dx*level_w*2;
// special case: image is too small for 2x2 filter
if(cld->prev_level_w == 1 || cld->prev_level_h == 1)
{
for(uint y = 0; y < level_h; y++)
{
for(uint i = 0; i < num_components; i++)
{
*dst++ = (src[0]+src[dy]+1)/2;
src += 1;
}
src += dy;
}
}
// normal
else
{
for(uint y = 0; y < level_h; y++)
{
for(uint x = 0; x < level_w; x++)
{
for(uint i = 0; i < num_components; i++)
{
*dst++ = (src[0]+src[dx]+src[dy]+src[dx+dy]+2)/4;
src += 1;
}
src += dx;
}
src += dy;
}
}
debug_assert(dst == level_data + level_data_size);
debug_assert(src == cld->prev_level_data + cld->prev_level_data_size);
}
cld->prev_level_data = level_data;
cld->prev_level_data_size = level_data_size;
cld->prev_level_w = level_w;
cld->prev_level_h = level_h;
}
// handles BGR and row flipping in "plain" format (see below).
//
// called by codecs after they get their format-specific transforms out of
@ -117,17 +178,20 @@ static int validate_format(uint bpp, uint flags)
// somewhat optimized (loops are hoisted, cache associativity accounted for)
static int plain_transform(Tex* t, uint transforms)
{
CHECK_TEX(t);
// (this is also called directly instead of through ogl_tex, so
// we need to validate)
CHECK_ERR(tex_validate(t));
// extract texture info
const uint w = t->w, h = t->h, bpp = t->bpp, flags = t->flags;
u8* const img = tex_get_data(t);
u8* const data = tex_get_data(t);
const size_t data_size = tex_img_size(t);
// sanity checks (not errors, we just can't handle these cases)
// .. unknown transform
if(transforms & ~(TEX_BGR|TEX_ORIENTATION))
if(transforms & ~(TEX_BGR|TEX_ORIENTATION|TEX_MIPMAPS))
return TEX_CODEC_CANNOT_HANDLE;
// .. img is not in "plain" format
// .. data is not in "plain" format
if(validate_format(bpp, flags) != 0)
return TEX_CODEC_CANNOT_HANDLE;
// .. nothing to do
@ -135,12 +199,12 @@ static int plain_transform(Tex* t, uint transforms)
return 0;
// setup row source/destination pointers (simplifies outer loop)
u8* dst = img;
const u8* src = img;
u8* dst = data;
const u8* src = data;
const size_t pitch = w * bpp/8;
ssize_t row_ofs = (ssize_t)pitch;
// avoid y*pitch multiply in row loop; instead, add row_ofs.
void* clone_img = 0;
void* clone_data = 0;
// flipping rows (0,1,2 -> 2,1,0)
if(transforms & TEX_ORIENTATION)
{
@ -150,12 +214,11 @@ static int plain_transform(Tex* t, uint transforms)
//
// note: we don't want to return a new buffer: the user assumes
// buffer address will remain unchanged.
const size_t size = h*pitch;
clone_img = mem_alloc(size, 32*KiB);
if(!clone_img)
clone_data = mem_alloc(data_size, 4*KiB);
if(!clone_data)
return ERR_NO_MEM;
memcpy(clone_img, img, size);
src = (const u8*)clone_img+size-pitch; // last row
memcpy(clone_data, data, data_size);
src = (const u8*)clone_data+data_size-pitch; // last row
row_ofs = -(ssize_t)pitch;
}
@ -202,34 +265,43 @@ static int plain_transform(Tex* t, uint transforms)
}
}
if(clone_img)
mem_free(clone_img);
if(clone_data)
(void)mem_free(clone_data);
if(!(t->flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS)
{
// this code assumes the image is of POT dimension; we don't
// go to the trouble of implememting image scaling because
// the only place this is used (ogl_tex_upload) requires POT anyway.
if(!is_pow2(w) || !is_pow2(h))
return ERR_TEX_INVALID_SIZE;
t->flags |= TEX_MIPMAPS; // must come before tex_img_size!
const size_t mipmap_size = tex_img_size(t);
Handle hm;
const u8* mipmap_data = (const u8*)mem_alloc(mipmap_size, 4*KiB, 0, &hm);
if(!mipmap_data)
return ERR_NO_MEM;
CreateLevelData cld = { bpp/8, w, h, data, data_size };
tex_util_foreach_mipmap(w, h, bpp, mipmap_data, 0, 1, create_level, &cld);
mem_free_h(t->hm);
t->hm = hm;
}
return 0;
}
//-----------------------------------------------------------------------------
// image orientation
//-----------------------------------------------------------------------------
// rationale for default: see tex_set_global_orientation
// see "Default Orientation" in docs.
static int global_orientation = TEX_TOP_DOWN;
// switch between top-down and bottom-up orientation.
//
// the default top-down is to match the Photoshop DDS plugin's output.
// DDS is the optimized format, so we don't want to have to flip that.
// notes:
// - there's no way to tell which orientation a DDS file has;
// we have to go with what the DDS encoder uses.
// - flipping DDS is possible without re-encoding; we'd have to shuffle
// around the pixel values inside the 4x4 blocks.
//
// the app can change orientation, e.g. to speed up loading
// "upside-down" formats, or to match OpenGL's bottom-up convention.
// set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which
// all loaded images will automatically be converted
// (excepting file formats that don't specify their orientation, i.e. DDS).
void tex_set_global_orientation(int o)
{
debug_assert(o == TEX_TOP_DOWN || o == TEX_BOTTOM_UP);
@ -240,13 +312,17 @@ void tex_set_global_orientation(int o)
static void flip_to_global_orientation(Tex* t)
{
uint orientation = t->flags & TEX_ORIENTATION;
// only if it knows which way around it is (DDS doesn't)
// if codec knows which way around the image is (i.e. not DDS):
if(orientation)
{
// flip image if necessary
uint transforms = orientation ^ global_orientation;
WARN_ERR(plain_transform(t, transforms));
}
// indicate image is at global orientation. this is still done even
// if the codec doesn't know: the default orientation should be chosen
// to make that work correctly (see "Default Orientation" in docs).
t->flags = (t->flags & ~TEX_ORIENTATION) | global_orientation;
}
@ -264,6 +340,99 @@ static bool orientations_match(uint src_flags, uint dst_orientation)
}
//-----------------------------------------------------------------------------
// util
//-----------------------------------------------------------------------------
// allocate an array of row pointers that point into the given texture data.
// <file_orientation> indicates whether the file format is top-down or
// bottom-up; the row array is inverted if necessary to match global
// orienatation. (this is more efficient than "transforming" later)
//
// used by PNG and JPG codecs; caller must free() rows when done.
//
// note: we don't allocate the data param ourselves because this function is
// needed for encoding, too (where data is already present).
int tex_util_alloc_rows(const u8* data, size_t h, size_t pitch,
uint src_flags, uint dst_orientation, RowArray& rows)
{
const bool flip = !orientations_match(src_flags, dst_orientation);
rows = (RowArray)malloc(h * sizeof(RowPtr));
if(!rows)
return ERR_NO_MEM;
// determine start position and direction
RowPtr pos = flip? data+pitch*(h-1) : data;
const ssize_t add = flip? -(ssize_t)pitch : (ssize_t)pitch;
const RowPtr end = flip? data-pitch : data+pitch*h;
for(size_t i = 0; i < h; i++)
{
rows[i] = pos;
pos += add;
}
debug_assert(pos == end);
return 0;
}
int tex_util_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da)
{
RETURN_ERR(tex_transform(t, transforms));
void* img_data = tex_get_data(t); const size_t img_size = tex_img_size(t);
RETURN_ERR(da_append(da, hdr, hdr_size));
RETURN_ERR(da_append(da, img_data, img_size));
return 0;
}
void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx)
{
uint level_w = w, level_h = h;
const u8* level_data = data;
// we iterate through the loop (necessary to skip over image data),
// but do not actually call back until the requisite number of
// levels have been skipped (i.e. level == 0).
int level = -(int)levels_to_skip;
if(levels_to_skip == -1)
level = 0;
// until at level 1x1:
for(;;)
{
// used to skip past this mip level in <data>
const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
if(level >= 0)
cb((uint)level, level_w, level_h, level_data, level_data_size, ctx);
level_data += level_data_size;
// 1x1 reached - done
if(level_w == 1 && level_h == 1)
break;
level_w /= 2;
level_h /= 2;
// if the texture is non-square, one of the dimensions will become
// 0 before the other. to satisfy OpenGL's expectations, change it
// back to 1.
if(level_w == 0) level_w = 1;
if(level_h == 0) level_h = 1;
level++;
// special case: no mipmaps, we were only supposed to call for
// the base level
if(levels_to_skip == -1)
break;
}
}
//-----------------------------------------------------------------------------
// support routines for codecs
//-----------------------------------------------------------------------------
@ -340,48 +509,30 @@ int tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl**
}
// allocate an array of row pointers that point into the given texture data.
// <file_orientation> indicates whether the file format is top-down or
// bottom-up; the row array is inverted if necessary to match global
// orienatation. (this is more efficient than "transforming" later)
//
// used by PNG and JPG codecs; caller must free() rows when done.
//
// note: we don't allocate the data param ourselves because this function is
// needed for encoding, too (where data is already present).
int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
uint src_flags, uint dst_orientation, RowArray& rows)
static int tex_codec_transform(Tex* t, uint transforms)
{
const bool flip = !orientations_match(src_flags, dst_orientation);
int ret = TEX_CODEC_CANNOT_HANDLE;
rows = (RowArray)malloc(h * sizeof(RowPtr));
if(!rows)
return ERR_NO_MEM;
// determine start position and direction
RowPtr pos = flip? data+pitch*(h-1) : data;
const ssize_t add = flip? -(ssize_t)pitch : (ssize_t)pitch;
const RowPtr end = flip? data-pitch : data+pitch*h;
for(size_t i = 0; i < h; i++)
// find codec that understands the data, and transform
for(int i = 0; i < MAX_CODECS; i++)
{
rows[i] = pos;
pos += add;
// MAX_CODECS isn't a tight bound and we have hit a 0 entry
if(!codecs[i])
continue;
int err = codecs[i]->transform(t, transforms);
if(err == 0)
return 0;
else if(err == TEX_CODEC_CANNOT_HANDLE)
continue;
else
{
ret = err;
debug_warn("tex_codec_transform: codec indicates error");
}
}
debug_assert(pos == end);
return 0;
}
int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da)
{
RETURN_ERR(tex_transform(t, transforms));
void* img_data = tex_get_data(t); const size_t img_size = tex_img_size(t);
RETURN_ERR(da_append(da, hdr, hdr_size));
RETURN_ERR(da_append(da, img_data, img_size));
return 0;
return ret;
}
@ -389,7 +540,7 @@ int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, D
// API
//-----------------------------------------------------------------------------
// split out of tex_load to ease resource cleanup
static int tex_load_impl(void* file_, size_t file_size, Tex* t)
{
u8* file = (u8*)file_;
@ -420,11 +571,12 @@ static int tex_load_impl(void* file_, size_t file_size, Tex* t)
flip_to_global_orientation(t);
CHECK_TEX(t);
return 0;
}
// load the specified image from file into the given Tex object.
// currently supports BMP, TGA, JPG, JP2, PNG, DDS.
int tex_load(const char* fn, Tex* t)
{
// load file
@ -433,22 +585,32 @@ int tex_load(const char* fn, Tex* t)
RETURN_ERR(hm); // (need handle below; can't test return value directly)
t->hm = hm;
int ret = tex_load_impl(file, file_size, t);
// do not free hm! it either still holds the image data (i.e. texture
// wasn't compressed) or was replaced by a new buffer for the image data.
if(ret < 0)
{
// note: don't use tex_free - we don't want its CHECK_TEX to
// complain that t wasn't successfully loaded (duh).
mem_free_h(hm);
memset(t, 0, sizeof(Tex));
debug_printf("tex_load(%s): %d", fn, ret);
(void)tex_free(t);
debug_warn("tex_load failed");
}
// do not free hm! it either still holds the image data (i.e. texture
// wasn't compressed) or was replaced by a new buffer for the image data.
return ret;
}
// store the given image data into a Tex object; this will be as if
// it had been loaded via tex_load.
//
// rationale: support for in-memory images is necessary for
// emulation of glCompressedTexImage2D and useful overall.
// however, we don't want to provide an alternate interface for each API;
// these would have to be changed whenever fields are added to Tex.
// instead, provide one entry point for specifying images.
// note: since we do not know how <img> was allocated, the caller must do
// so (after calling tex_free, which is required regardless of alloc type).
//
// we need only add bookkeeping information and "wrap" it in
// our Tex struct, hence the name.
int tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t)
{
t->w = w;
@ -471,72 +633,56 @@ int tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t)
// TODO: remove when mem_wrap / mem_get_ptr add a reference correctly
h_add_ref(t->hm);
CHECK_TEX(t);
return 0;
}
// free all resources associated with the image and make further
// use of it impossible.
int tex_free(Tex* t)
{
CHECK_TEX(t);
mem_free_h(t->hm);
// do not validate <t> - this is called from tex_load if loading
// failed, so not all fields may be valid.
int ret = mem_free_h(t->hm);
// do not zero out the fields! that could lead to trouble since
// ogl_tex_upload followed by ogl_tex_free is legit, but would
// cause OglTex_validate to fail (since its Tex.w is == 0).
return 0;
}
//-----------------------------------------------------------------------------
u8* tex_get_data(const Tex* t)
{
if(tex_validate(__LINE__, t) < 0)
return 0;
u8* p = (u8*)mem_get_ptr(t->hm);
if(!p)
return 0;
return p + t->ofs;
}
size_t tex_img_size(const Tex* t)
{
if(tex_validate(__LINE__, t) < 0)
return 0;
return t->w * t->h * t->bpp/8;
return ret;
}
//-----------------------------------------------------------------------------
static TimerClient* tc_transform = timer_add_client("tex_transform");
// change <t>'s pixel format by flipping the state of all TEX_* flags
// that are set in transforms.
int tex_transform(Tex* t, uint transforms)
{
CHECK_TEX(t);
SUM_TIMER(tc_transform);
// find codec that understands the data, and transform
for(uint i = 0; i < MAX_CODECS; i++)
const uint target_flags = t->flags ^ transforms;
for(;;)
{
// MAX_CODECS isn't a tight bound and we have hit a 0 entry
if(!codecs[i])
continue;
int err = codecs[i]->transform(t, transforms);
if(err == TEX_CODEC_CANNOT_HANDLE)
continue;
if(err == 0)
// we're finished (all required transforms have been done)
if(t->flags == target_flags)
return 0;
debug_printf("tex_transform (%s): failed, error %d", codecs[i]->name, err);
CHECK_ERR(err);
int ret = tex_codec_transform(t, transforms);
if(ret != 0)
break;
}
// last chance
return plain_transform(t, transforms);
CHECK_ERR(plain_transform(t, transforms));
return 0;
}
// change <t>'s pixel format to the new format specified by <new_flags>.
// (note: this is equivalent to tex_transform(t, t->flags^new_flags).
int tex_transform_to(Tex* t, uint new_flags)
{
const uint transforms = t->flags ^ new_flags;
@ -546,7 +692,53 @@ int tex_transform_to(Tex* t, uint new_flags)
//-----------------------------------------------------------------------------
// returns a pointer to the image data (pixels), taking into account any
// header(s) that may come before it. see Tex.hm comment above.
u8* tex_get_data(const Tex* t)
{
if(tex_validate(t) < 0)
return 0;
u8* p = (u8*)mem_get_ptr(t->hm);
if(!p)
return 0;
return p + t->ofs;
}
static void add_level_size(uint UNUSED(level), uint UNUSED(level_w), uint UNUSED(level_h),
const u8* restrict UNUSED(level_data), size_t level_data_size, void* restrict ctx)
{
size_t* ptotal_size = (size_t*)ctx;
*ptotal_size += level_data_size;
}
// return total byte size of the image pixels. (including mipmaps!)
// this is preferable to calculating manually because it's
// less error-prone (e.g. confusing bits_per_pixel with bytes).
size_t tex_img_size(const Tex* t)
{
if(tex_validate(t) < 0)
return 0;
const int levels_to_skip = (t->flags & TEX_MIPMAPS)? 0 : TEX_BASE_LEVEL_ONLY;
const uint data_padding = (t->flags & TEX_DXT)? 4 : 1;
size_t out_size = 0;
tex_util_foreach_mipmap(t->w, t->h, t->bpp, 0, levels_to_skip,
data_padding, add_level_size, &out_size);
return out_size;
}
//-----------------------------------------------------------------------------
// return the minimum header size (i.e. offset to pixel data) of the
// file format indicated by <fn>'s extension (that is all it need contain:
// e.g. ".bmp"). returns 0 on error (i.e. no codec found).
// this can be used to optimize calls to tex_write: when allocating the
// buffer that will hold the image, allocate this much extra and
// pass the pointer as base+hdr_size. this allows writing the header
// directly into the output buffer and makes for zero-copy IO.
size_t tex_hdr_size(const char* fn)
{
const TexCodecVTbl* c;
@ -555,6 +747,9 @@ size_t tex_hdr_size(const char* fn)
}
// write the specified texture to disk.
// note: <t> cannot be made const because the image may have to be
// transformed to write it out in the format determined by <fn>'s extension.
int tex_write(Tex* t, const char* fn)
{
CHECK_ERR(validate_format(t->bpp, t->flags));

View File

@ -57,12 +57,15 @@ close to the final pixel format.
Default Orientation
-------------------
After loading, all images (2) are automatically converted to the
default row orientation: top-down or bottom-up, as specified by
tex_set_global_orientation.
2) except those loaded from a file format whose orientation is
indeterminate (currently only DDS); we leave those alone.
After loading, all images (except DDS, because its orientation is
indeterminate) are automatically converted to the global row
orientation: top-down or bottom-up, as specified by
tex_set_global_orientation. If that isn't called, the default is top-down
to match Photoshop's DDS output (since this is meant to be the
no-preprocessing-required optimized format).
Reasons to change it might be to speed up loading bottom-up
BMP or TGA images, or to match OpenGL's convention for convenience;
however, be aware of the abovementioned issues with DDS.
Rationale: it is not expected that this will happen at the renderer layer
(a 'flip all texcoords' flag is too much trouble), so the
@ -70,19 +73,14 @@ application would have to do the same anyway. By taking care of it here,
we unburden the app and save time, since some codecs (e.g. PNG) can
flip for free when loading.
As to what value is best: if using DDS, set the default to match your
renderer and the orientation output by your DDS authoring tool (since this
is the only format that doesn't reliably specify its orientation).
Otherwise, it doesn't much matter unless using many images where
flipping isn't free (BMP or TGA) - in that case, go with their orientation.
Codecs / IO Implementation
--------------------------
To ease adding support for new formats, they are organized as codecs.
The interface aims to minimize code duplication, so it's organized similar
to "Template Method".
The interface aims to minimize code duplication, so it's organized
following the principle of "Template Method" - this module both
calls into codecs, and provides helper functions that they use.
IO is done via VFS, but the codecs are decoupled from this and
work with memory buffers. Access to them is endian-safe.
@ -106,7 +104,8 @@ enum TexFlags
// flags & TEX_DXT is a field indicating compression.
// if 0, the texture is uncompressed;
// otherwise, it holds the S3TC type: 1,3,5 or DXT1A.
// not converted by default - glCompressedTexImage2D takes it as-is.
// not converted by default - glCompressedTexImage2D receives
// the compressed data.
TEX_DXT = 0x7, // mask
// we need a special value for DXT1a to avoid having to consider
// flags & TEX_ALPHA to determine S3TC type.
@ -126,7 +125,7 @@ enum TexFlags
// indicates the image is 8bpp greyscale. this is required to
// differentiate between alpha-only and intensity formats.
// (conversion is not applicable here)
// not converted by default - it's an acceptable format for OpenGL.
TEX_GREY = 0x20,
// flags & TEX_ORIENTATION is a field indicating orientation,
@ -134,7 +133,7 @@ enum TexFlags
//
// tex_load always sets this to the global orientation
// (and flips the image accordingly).
// texture codecs may (in intermediate steps during loading) set this
// texture codecs may in intermediate steps during loading set this
// to 0 if they don't know which way around they are (e.g. DDS),
// or to whatever their file contains.
TEX_BOTTOM_UP = 0x40,
@ -176,6 +175,7 @@ struct Tex
// set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which
// all loaded images will automatically be converted
// (excepting file formats that don't specify their orientation, i.e. DDS).
// see "Default Orientation" in docs.
extern void tex_set_global_orientation(int orientation);
@ -216,6 +216,7 @@ extern int tex_free(Tex* t);
extern int tex_transform(Tex* t, uint transforms);
// change <t>'s pixel format to the new format specified by <new_flags>.
// (note: this is equivalent to tex_transform(t, t->flags^new_flags).
extern int tex_transform_to(Tex* t, uint new_flags);
@ -232,8 +233,8 @@ extern int tex_transform_to(Tex* t, uint new_flags);
// header(s) that may come before it. see Tex.hm comment above.
extern u8* tex_get_data(const Tex* t);
// return total byte size of the image pixels. this is preferable to
// calculating manually via num_pixels * pixel_size because it's
// return total byte size of the image pixels. (including mipmaps!)
// this is preferable to calculating manually because it's
// less error-prone (e.g. confusing bits_per_pixel with bytes).
extern size_t tex_img_size(const Tex* t);
@ -242,9 +243,9 @@ extern size_t tex_img_size(const Tex* t);
// image writing
//
// returns the minimum header size (i.e. offset to pixel data) of the
// return the minimum header size (i.e. offset to pixel data) of the
// file format indicated by <fn>'s extension (that is all it need contain:
// e.g. ".bmp").
// e.g. ".bmp"). returns 0 on error (i.e. no codec found).
// this can be used to optimize calls to tex_write: when allocating the
// buffer that will hold the image, allocate this much extra and
// pass the pointer as base+hdr_size. this allows writing the header
@ -258,6 +259,17 @@ extern int tex_write(Tex* t, const char* fn);
// internal use only:
extern int tex_validate(uint line, const Tex* t);
extern int tex_validate(const Tex* t);
typedef void(*MipmapCB)(uint level, uint level_w, uint level_h,
const u8* level_data, size_t level_data_size, void* ctx);
// special value for levels_to_skip: the callback will only be called
// for the base mipmap level (i.e. 100%)
const int TEX_BASE_LEVEL_ONLY = -1;
extern void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx);
#endif // TEX_H__

View File

@ -69,7 +69,7 @@ static size_t bmp_hdr_size(const u8* file)
// requirements: uncompressed, direct colour, bottom up
static int bmp_decode(DynArray* da, Tex* t)
static int bmp_decode(DynArray* restrict da, Tex* restrict t)
{
u8* file = da->base;
@ -100,7 +100,7 @@ static int bmp_decode(DynArray* da, Tex* t)
}
static int bmp_encode(Tex* t, DynArray* da)
static int bmp_encode(Tex* restrict t, DynArray* restrict da)
{
const size_t hdr_size = sizeof(BmpHeader); // needed for BITMAPFILEHEADER
const size_t img_size = tex_img_size(t);
@ -129,7 +129,7 @@ static int bmp_encode(Tex* t, DynArray* da)
(u32)img_size, // biSizeImage
0, 0, 0, 0 // unused (bi?PelsPerMeter, biClr*)
};
return tex_codec_write(t, transforms, &hdr, hdr_size, da);
return tex_util_write(t, transforms, &hdr, hdr_size, da);
}
TEX_CODEC_REGISTER(bmp);

View File

@ -15,7 +15,7 @@ struct TexCodecVTbl
// size is guaranteed to be >= 4.
// (usually enough to compare the header's "magic" field;
// anyway, no legitimate file will be smaller)
int (*decode)(DynArray* da, Tex* t);
int (*decode)(DynArray* restrict da, Tex* restrict t);
// rationale: some codecs cannot calculate the output size beforehand
// (e.g. PNG output via libpng); we therefore require each one to
@ -23,7 +23,7 @@ struct TexCodecVTbl
//
// note: <t> cannot be made const because encoding may require a
// tex_transform.
int (*encode)(Tex* t, DynArray* da);
int (*encode)(Tex* restrict t, DynArray* restrict da);
int (*transform)(Tex* t, uint transforms);
@ -75,11 +75,9 @@ extern int tex_codec_for_header(const u8* file, size_t file_size, const TexCodec
// needed for encoding, too (where data is already present).
typedef const u8* RowPtr;
typedef RowPtr* RowArray;
extern int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
extern int tex_util_alloc_rows(const u8* data, size_t h, size_t pitch,
uint src_flags, uint dst_orientation, RowArray& rows);
extern int tex_codec_set_orientation(Tex* t, uint file_orientation);
extern int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da);
extern int tex_util_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da);
#endif // #ifndef TEX_CODEC_H__

View File

@ -112,10 +112,12 @@ struct S3tcBlock
// table of 2-bit color selectors
u32 c_selectors;
uint dxt;
};
static void precalc_alpha(int dxt, const u8* a_block, S3tcBlock* b)
static void precalc_alpha(uint dxt, const u8* restrict a_block, S3tcBlock* restrict b)
{
// read block contents
const uint a0 = a_block[0], a1 = a_block[1];
@ -154,7 +156,7 @@ static void precalc_alpha(int dxt, const u8* a_block, S3tcBlock* b)
}
static void precalc_color(int dxt, const u8* c_block, S3tcBlock* b)
static void precalc_color(uint dxt, const u8* restrict c_block, S3tcBlock* restrict b)
{
// read block contents
// .. S3TC reference colors (565 format). the color table is generated
@ -191,8 +193,10 @@ static void precalc_color(int dxt, const u8* c_block, S3tcBlock* b)
}
static void block_precalc(int dxt, const u8* block, S3tcBlock* b)
static void precalc_block(uint dxt, const u8* restrict block, S3tcBlock* restrict b)
{
b->dxt = dxt;
// (careful, 'dxt != 1' doesn't work)
const u8* a_block = block;
const u8* c_block = (dxt == 3 || dxt == 5)? block+8 : block;
@ -202,7 +206,7 @@ static void block_precalc(int dxt, const u8* block, S3tcBlock* b)
}
static void write_pixel(int dxt, uint pixel_idx, const S3tcBlock* b, u8* out)
static void write_pixel(const S3tcBlock* restrict b, uint pixel_idx, u8* restrict out)
{
debug_assert(pixel_idx < 16);
@ -213,17 +217,17 @@ static void write_pixel(int dxt, uint pixel_idx, const S3tcBlock* b, u8* out)
out[i] = c[i];
// if no alpha, done
if(dxt == 1)
if(b->dxt == 1)
return;
uint a;
if(dxt == 3)
if(b->dxt == 3)
{
// table of 4-bit alpha entries
a = access_bit_tbl64(b->a_bits, pixel_idx, 4);
a |= a << 4; // expand to 8 bits (replicate high into low!)
}
else if(dxt == 5)
else if(b->dxt == 5)
{
// pixel index -> alpha selector (3 bit) -> alpha
const uint a_selector = access_bit_tbl64(b->a_bits, pixel_idx, 3);
@ -236,81 +240,113 @@ static void write_pixel(int dxt, uint pixel_idx, const S3tcBlock* b, u8* out)
}
// in ogl_emulate_dds: debug_assert(compressedimageSize == blocks * (dxt1? 8 : 16));
// note: this code is grossly inefficient (mostly due to splitting it up
// note: this code is not so efficient (mostly due to splitting it up
// into function calls for readability). that's because it's only used to
// emulate hardware S3TC support - if that isn't available, everything will
// be dog-slow anyway due to increased vmem usage.
static int dds_decompress(Tex* t)
struct DecompressInfo
{
int dxt = t->flags & TEX_DXT;
debug_assert(dxt == 1 || dxt == 3 || dxt == 5);
if(t->flags & TEX_ALPHA)
dxt = DXT1A;
// due to the above, dxt == 1 is the only non-alpha case.
// note: adding or stripping alpha channels during transform is not
// our job; we merely output the same pixel format as given
// (tex.cpp's plain transform could cover it, if ever needed).
const uint bpp = (dxt != 1)? 32 : 24;
uint dxt;
uint s3tc_block_size;
uint out_Bpp;
u8* out;
};
static void decompress_level(uint UNUSED(level), uint level_w, uint level_h,
const u8* restrict level_data, size_t level_data_size, void* restrict ctx)
{
DecompressInfo* di = (DecompressInfo*)ctx;
const uint dxt = di->dxt;
const uint s3tc_block_size = di->s3tc_block_size;
// note: 1x1 images are legitimate (e.g. in mipmaps). they report their
// width as such for glTexImage, but the S3TC data is padded to
// 4x4 pixel block boundaries.
const uint blocks_w = (uint)(round_up(t->w, 4) / 4);
const uint blocks_h = (uint)(round_up(t->h, 4) / 4);
const uint blocks = blocks_w * blocks_h;
const size_t img_size = blocks * 16 * bpp/8;
Handle hm;
void* img_data = mem_alloc(img_size, 64*KiB, 0, &hm);
if(!img_data)
return ERR_NO_MEM;
const u8* s3tc_data = (const u8*)tex_get_data(t);
// note: do not use tex_img_size! we must take into account padding
// to 4x4 blocks, which is relevant for high mipmap levels (e.g. 2x2).
const size_t s3tc_size = blocks * 16 * t->bpp/8;
const uint blocks_w = (uint)round_up(level_w, 4) / 4;
const uint blocks_h = (uint)round_up(level_h, 4) / 4;
const u8* s3tc_data = level_data;
debug_assert(level_data_size % s3tc_block_size == 0);
for(uint block_y = 0; block_y < blocks_h; block_y++)
for(uint block_x = 0; block_x < blocks_w; block_x++)
{
S3tcBlock b;
block_precalc(dxt, s3tc_data, &b);
s3tc_data += 16 * t->bpp/8;
precalc_block(dxt, s3tc_data, &b);
s3tc_data += s3tc_block_size;
uint pixel_idx = 0;
for(int y = 0; y < 4; y++)
{
u8* out = (u8*)img_data + ((block_y*4+y)*blocks_w*4 + block_x*4) * bpp/8;
// this is ugly, but advancing after x, y and block_y loops
// is no better.
u8* out = (u8*)di->out + ((block_y*4+y)*blocks_w*4 + block_x*4) * di->out_Bpp;
for(int x = 0; x < 4; x++)
{
write_pixel(dxt, pixel_idx, &b, out);
out += bpp/8;
write_pixel(&b, pixel_idx, out);
out += di->out_Bpp;
pixel_idx++;
}
}
} // for block_x
debug_assert(tex_get_data(t) == s3tc_data - s3tc_size);
mem_free_h(t->hm);
t->hm = hm;
t->ofs = 0;
t->bpp = bpp;
t->flags &= ~TEX_DXT;
return 0;
debug_assert(s3tc_data == level_data + level_data_size);
di->out += blocks_w*blocks_h * 16 * di->out_Bpp;
}
static bool is_valid_dxt(uint dxt)
{
switch(dxt)
{
case 0:
case 1:
case DXT1A:
case 3:
case 5:
return true;
default:
return false;
}
UNREACHABLE;
}
static int dds_transform(Tex* t, uint transforms)
{
const int is_dxt = t->flags & TEX_DXT, transform_dxt = transforms & TEX_DXT;
uint dxt = t->flags & TEX_DXT;
debug_assert(is_valid_dxt(dxt));
const uint s3tc_block_size = (dxt == 3 || dxt == 5)? 16 : 8;
if(t->flags & TEX_ALPHA)
dxt = DXT1A;
const uint transform_dxt = transforms & TEX_DXT;
// requesting decompression
if(is_dxt && transform_dxt)
return dds_decompress(t);
if(dxt && transform_dxt)
{
// alloc new image memory
// notes:
// - due to the above, dxt == 1 is the only non-alpha case.
// - adding or stripping alpha channels during transform is not
// our job; we merely output the same pixel format as given
// (tex.cpp's plain transform could cover it, if ever needed).
const uint out_bpp = (dxt != 1)? 32 : 24;
Handle hm;
const size_t out_size = tex_img_size(t) * out_bpp / t->bpp;
void* out_data = mem_alloc(out_size, 64*KiB, 0, &hm);
if(!out_data)
return ERR_NO_MEM;
DecompressInfo di = { dxt, s3tc_block_size, out_bpp/8, (u8*)out_data };
const u8* s3tc_data = tex_get_data(t);
const int levels_to_skip = (t->flags & TEX_MIPMAPS)? 0 : TEX_BASE_LEVEL_ONLY;
tex_util_foreach_mipmap(t->w, t->h, t->bpp, s3tc_data, levels_to_skip, 4, decompress_level, &di);
mem_free_h(t->hm);
t->hm = hm;
t->ofs = 0;
t->bpp = out_bpp;
t->flags &= ~TEX_DXT;
return 0;
}
// both are DXT (unsupported; there are no flags we can change while
// compressed) or requesting compression (not implemented) or
// both not DXT (nothing we can do) - bail.
@ -337,7 +373,7 @@ static size_t dds_hdr_size(const u8* UNUSED(file))
}
static int dds_decode(DynArray* da, Tex* t)
static int dds_decode(DynArray* restrict da, Tex* restrict t)
{
u8* file = da->base;
@ -420,7 +456,7 @@ static int dds_decode(DynArray* da, Tex* t)
}
static int dds_encode(Tex* UNUSED(t), DynArray* UNUSED(da))
static int dds_encode(Tex* restrict UNUSED(t), DynArray* restrict UNUSED(da))
{
// note: do not return ERR_NOT_IMPLEMENTED et al. because that would
// break tex_write (which assumes either this, 0 or errors are returned).

View File

@ -459,7 +459,7 @@ static int jpg_decode_impl(DynArray* da,
return ERR_NO_MEM;
// read rows
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
RETURN_ERR(tex_util_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
// could use cinfo->output_scanline to keep track of progress,
// but we need to count lines_left anyway (paranoia).
JSAMPARRAY row = (JSAMPARRAY)rows;
@ -520,7 +520,7 @@ static int jpg_encode_impl(Tex* t,
const size_t pitch = t->w * t->bpp / 8;
u8* data = tex_get_data(t);
RETURN_ERR(tex_codec_alloc_rows(data, t->h, pitch, t->flags, TEX_TOP_DOWN, rows));
RETURN_ERR(tex_util_alloc_rows(data, t->h, pitch, t->flags, TEX_TOP_DOWN, rows));
// could use cinfo->output_scanline to keep track of progress,
@ -567,7 +567,7 @@ static size_t jpg_hdr_size(const u8* UNUSED(file))
}
static int jpg_decode(DynArray* da, Tex* t)
static int jpg_decode(DynArray* restrict da, Tex* restrict t)
{
int err;
@ -606,7 +606,7 @@ fail:
// limitation: palette images aren't supported
static int jpg_encode(Tex* t, DynArray* da)
static int jpg_encode(Tex* restrict t, DynArray* restrict da)
{
int err;

View File

@ -105,7 +105,7 @@ static int png_decode_impl(DynArray* da,
if(!img)
return ERR_NO_MEM;
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
RETURN_ERR(tex_util_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
png_read_image(png_ptr, (png_bytepp)rows);
png_read_end(png_ptr, info_ptr);
@ -158,7 +158,7 @@ static int png_encode_impl(Tex* t,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
u8* data = tex_get_data(t);
RETURN_ERR(tex_codec_alloc_rows(data, h, pitch, t->flags, TEX_TOP_DOWN, rows));
RETURN_ERR(tex_util_alloc_rows(data, h, pitch, t->flags, TEX_TOP_DOWN, rows));
// PNG is native RGB.
const int png_transforms = (t->flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
@ -192,7 +192,7 @@ static size_t png_hdr_size(const u8* UNUSED(file))
// limitation: palette images aren't supported
static int png_decode(DynArray* da, Tex* t)
static int png_decode(DynArray* restrict da, Tex* restrict t)
{
int err = -1;
// freed when ret is reached:
@ -233,7 +233,7 @@ ret:
// limitation: palette images aren't supported
static int png_encode(Tex* t, DynArray* da)
static int png_encode(Tex* restrict t, DynArray* restrict da)
{
int err = -1;
// freed when ret is reached:

View File

@ -86,7 +86,7 @@ static size_t tga_hdr_size(const u8* file)
// requirements: uncompressed, direct colour, bottom up
static int tga_decode(DynArray* da, Tex* t)
static int tga_decode(DynArray* restrict da, Tex* restrict t)
{
u8* file = da->base;
@ -120,7 +120,7 @@ static int tga_decode(DynArray* da, Tex* t)
}
static int tga_encode(Tex* t, DynArray* da)
static int tga_encode(Tex* restrict t, DynArray* restrict da)
{
u8 img_desc = 0;
if(t->flags & TEX_TOP_DOWN)
@ -146,7 +146,7 @@ static int tga_encode(Tex* t, DynArray* da)
img_desc
};
const size_t hdr_size = sizeof(hdr);
return tex_codec_write(t, transforms, &hdr, hdr_size, da);
return tex_util_write(t, transforms, &hdr, hdr_size, da);
}
TEX_CODEC_REGISTER(tga);