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:
parent
b53e236a02
commit
c1ec44d751
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
*/
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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__
|
||||
|
@ -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);
|
||||
|
@ -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__
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user