forked from 0ad/0ad
janwas
a859562ea7
- properly differentiate between buffer/offset alignment and length alignment (relevant since block size has been increased to 256k) - use VfsPath for most game paths instead of CStr - clean up timer interface and implementation - self-tests no longer crash - file_cache.cpp: fix for the case where allocation fails (prevent deleter from seeing a null pointer) - allocators: move all shared_ptr-related stuff to its own component; add DummySharedPtr - codec: disable checksums (important for performance at work) - File: made into an interface class to avoid export problems. not entirely sure about this.. - vfs_path.h, path.h, os_path.h: proper fix for using fs::change_extension and similar utility functions with derivatives of basic_path - lib_api: automatically link against import lib if building lib/ as a DLL - path_util: remove unused functions (this component is deprecated) - compiler.h: add INLINE - Xeromyces.cpp: pass PIVFS so that GetXMBPath works in self-test (should do this mostly everywhere rather than have one singleton g_VFS) This was SVN commit r5537.
657 lines
19 KiB
C++
657 lines
19 KiB
C++
/**
|
|
* =========================================================================
|
|
* File : tex.cpp
|
|
* Project : 0 A.D.
|
|
* Description : support routines for 2d texture access/writing.
|
|
* =========================================================================
|
|
*/
|
|
|
|
// license: GPL; see lib/license.txt
|
|
|
|
#include "precompiled.h"
|
|
#include "tex.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <algorithm>
|
|
|
|
#include "lib/timer.h"
|
|
#include "lib/bits.h"
|
|
#include "lib/sysdep/cpu.h"
|
|
|
|
#include "tex_codec.h"
|
|
|
|
|
|
ERROR_ASSOCIATE(ERR::TEX_FMT_INVALID, "Invalid/unsupported texture format", -1);
|
|
ERROR_ASSOCIATE(ERR::TEX_INVALID_COLOR_TYPE, "Invalid color type", -1);
|
|
ERROR_ASSOCIATE(ERR::TEX_NOT_8BIT_PRECISION, "Not 8-bit channel precision", -1);
|
|
ERROR_ASSOCIATE(ERR::TEX_INVALID_LAYOUT, "Unsupported texel layout, e.g. right-to-left", -1);
|
|
ERROR_ASSOCIATE(ERR::TEX_COMPRESSED, "Unsupported texture compression", -1);
|
|
ERROR_ASSOCIATE(WARN::TEX_INVALID_DATA, "Warning: invalid texel data encountered", -1);
|
|
ERROR_ASSOCIATE(ERR::TEX_INVALID_SIZE, "Texture size is incorrect", -1);
|
|
ERROR_ASSOCIATE(INFO::TEX_CODEC_CANNOT_HANDLE, "Texture codec cannot handle the given format", -1);
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// validation
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// be careful not to use other tex_* APIs here because they call us.
|
|
LibError tex_validate(const Tex* t)
|
|
{
|
|
if(t->flags & TEX_UNDEFINED_FLAGS)
|
|
WARN_RETURN(ERR::_1);
|
|
|
|
// pixel data (only check validity if the image is still in memory;
|
|
// ogl_tex frees the data after uploading to GL)
|
|
if(t->data)
|
|
{
|
|
// file size smaller than header+pixels.
|
|
// possible causes: texture file header is invalid,
|
|
// or file wasn't loaded completely.
|
|
if(t->dataSize < t->ofs + t->w*t->h*t->bpp/8)
|
|
WARN_RETURN(ERR::_2);
|
|
}
|
|
|
|
// bits per pixel
|
|
// (we don't bother checking all values; a sanity check is enough)
|
|
if(t->bpp % 4 || t->bpp > 32)
|
|
WARN_RETURN(ERR::_3);
|
|
|
|
// flags
|
|
// .. DXT value
|
|
const uint dxt = t->flags & TEX_DXT;
|
|
if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5)
|
|
WARN_RETURN(ERR::_4);
|
|
// .. orientation
|
|
const uint orientation = t->flags & TEX_ORIENTATION;
|
|
if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN))
|
|
WARN_RETURN(ERR::_5);
|
|
|
|
return INFO::OK;
|
|
}
|
|
|
|
#define CHECK_TEX(t) RETURN_ERR(tex_validate(t))
|
|
|
|
|
|
// check if the given texture format is acceptable: 8bpp grey,
|
|
// 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
|
|
// basically, this is the "plain" format understood by all codecs and
|
|
// tex_codec_plain_transform.
|
|
LibError tex_validate_plain_format(uint bpp, uint flags)
|
|
{
|
|
const bool alpha = (flags & TEX_ALPHA ) != 0;
|
|
const bool grey = (flags & TEX_GREY ) != 0;
|
|
const bool dxt = (flags & TEX_DXT ) != 0;
|
|
const bool mipmaps = (flags & TEX_MIPMAPS) != 0;
|
|
|
|
if(dxt || mipmaps)
|
|
WARN_RETURN(ERR::TEX_FMT_INVALID);
|
|
|
|
// grey must be 8bpp without alpha, or it's invalid.
|
|
if(grey)
|
|
{
|
|
if(bpp == 8 && !alpha)
|
|
return INFO::OK;
|
|
WARN_RETURN(ERR::TEX_FMT_INVALID);
|
|
}
|
|
|
|
if(bpp == 24 && !alpha)
|
|
return INFO::OK;
|
|
if(bpp == 32 && alpha)
|
|
return INFO::OK;
|
|
|
|
WARN_RETURN(ERR::TEX_FMT_INVALID);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// mipmaps
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* pixels, int levels_to_skip, uint data_padding, MipmapCB cb, void* RESTRICT cbData)
|
|
{
|
|
debug_assert(levels_to_skip >= 0 || levels_to_skip == TEX_BASE_LEVEL_ONLY);
|
|
|
|
uint level_w = w, level_h = h;
|
|
const u8* level_data = pixels;
|
|
|
|
// 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 = (levels_to_skip == TEX_BASE_LEVEL_ONLY)? 0 : -levels_to_skip;
|
|
|
|
// 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, cbData);
|
|
|
|
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 == TEX_BASE_LEVEL_ONLY)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
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 cbData)
|
|
{
|
|
CreateLevelData* cld = (CreateLevelData*)cbData;
|
|
const size_t src_w = cld->prev_level_w;
|
|
const size_t src_h = cld->prev_level_h;
|
|
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);
|
|
cpu_memcpy(dst, src, level_data_size);
|
|
}
|
|
else
|
|
{
|
|
const uint num_components = cld->num_components;
|
|
const size_t dx = num_components, dy = dx*src_w;
|
|
|
|
// special case: image is too small for 2x2 filter
|
|
if(cld->prev_level_w == 1 || cld->prev_level_h == 1)
|
|
{
|
|
// image is either a horizontal or vertical line.
|
|
// their memory layout is the same (packed pixels), so no special
|
|
// handling is needed; just pick max dimension.
|
|
for(uint y = 0; y < std::max(src_w, src_h); y += 2)
|
|
{
|
|
for(uint i = 0; i < num_components; i++)
|
|
{
|
|
*dst++ = (src[0]+src[dx]+1)/2;
|
|
src += 1;
|
|
}
|
|
|
|
src += dx; // skip to next pixel (since box is 2x2)
|
|
}
|
|
}
|
|
// normal
|
|
else
|
|
{
|
|
for(uint y = 0; y < src_h; y += 2)
|
|
{
|
|
for(uint x = 0; x < src_w; x += 2)
|
|
{
|
|
for(uint i = 0; i < num_components; i++)
|
|
{
|
|
*dst++ = (src[0]+src[dx]+src[dy]+src[dx+dy]+2)/4;
|
|
src += 1;
|
|
}
|
|
|
|
src += dx; // skip to next pixel (since box is 2x2)
|
|
}
|
|
|
|
src += dy; // skip to next row (since box is 2x2)
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
static LibError add_mipmaps(Tex* t, uint w, uint h, uint bpp, void* newData, size_t data_size)
|
|
{
|
|
// this code assumes the image is of POT dimension; we don't
|
|
// go to the trouble of implementing image scaling because
|
|
// the only place this is used (ogl_tex_upload) requires POT anyway.
|
|
if(!is_pow2(w) || !is_pow2(h))
|
|
WARN_RETURN(ERR::TEX_INVALID_SIZE);
|
|
t->flags |= TEX_MIPMAPS; // must come before tex_img_size!
|
|
const size_t mipmap_size = tex_img_size(t);
|
|
shared_ptr<u8> mipmapData = io_Allocate(mipmap_size, 0);
|
|
CreateLevelData cld = { bpp/8, w, h, (const u8*)newData, data_size };
|
|
tex_util_foreach_mipmap(w, h, bpp, mipmapData.get(), 0, 1, create_level, &cld);
|
|
t->data = mipmapData;
|
|
t->dataSize = mipmap_size;
|
|
t->ofs = 0;
|
|
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// pixel format conversion (transformation)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TIMER_ADD_CLIENT(tc_plain_transform);
|
|
|
|
// handles BGR and row flipping in "plain" format (see below).
|
|
//
|
|
// called by codecs after they get their format-specific transforms out of
|
|
// the way. note that this approach requires several passes over the image,
|
|
// but is much easier to maintain than providing all<->all conversion paths.
|
|
//
|
|
// somewhat optimized (loops are hoisted, cache associativity accounted for)
|
|
static LibError plain_transform(Tex* t, uint transforms)
|
|
{
|
|
TIMER_ACCRUE(tc_plain_transform);
|
|
|
|
// (this is also called directly instead of through ogl_tex, so
|
|
// we need to validate)
|
|
CHECK_TEX(t);
|
|
|
|
// extract texture info
|
|
const uint w = t->w, h = t->h, bpp = t->bpp, flags = t->flags;
|
|
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|TEX_MIPMAPS))
|
|
return INFO::TEX_CODEC_CANNOT_HANDLE;
|
|
// .. data is not in "plain" format
|
|
RETURN_ERR(tex_validate_plain_format(bpp, flags));
|
|
// .. nothing to do
|
|
if(!transforms)
|
|
return INFO::OK;
|
|
|
|
// allocate copy of the image data.
|
|
// rationale: L1 cache is typically A2 => swapping in-place with a
|
|
// line buffer leads to thrashing. we'll assume the whole texture*2
|
|
// fits in cache, allocate a copy, and transfer directly from there.
|
|
//
|
|
// this is necessary even when not flipping because the initial data
|
|
// is read-only.
|
|
shared_ptr<u8> newData = io_Allocate(data_size);
|
|
cpu_memcpy(newData.get(), data, data_size);
|
|
|
|
// setup row source/destination pointers (simplifies outer loop)
|
|
u8* dst = (u8*)newData.get();
|
|
const u8* src = (const u8*)newData.get();
|
|
const size_t pitch = w * bpp/8;
|
|
// .. avoid y*pitch multiply in row loop; instead, add row_ofs.
|
|
ssize_t row_ofs = (ssize_t)pitch;
|
|
|
|
// flipping rows (0,1,2 -> 2,1,0)
|
|
if(transforms & TEX_ORIENTATION)
|
|
{
|
|
src = (const u8*)data+data_size-pitch; // last row
|
|
row_ofs = -(ssize_t)pitch;
|
|
}
|
|
|
|
// no BGR convert necessary
|
|
if(!(transforms & TEX_BGR))
|
|
{
|
|
for(uint y = 0; y < h; y++)
|
|
{
|
|
cpu_memcpy(dst, src, pitch);
|
|
dst += pitch;
|
|
src += row_ofs;
|
|
}
|
|
}
|
|
// RGB <-> BGR
|
|
else if(bpp == 24)
|
|
{
|
|
for(uint y = 0; y < h; y++)
|
|
{
|
|
for(uint x = 0; x < w; x++)
|
|
{
|
|
// need temporaries in case src == dst (i.e. not flipping)
|
|
const u8 b = src[0], g = src[1], r = src[2];
|
|
dst[0] = r; dst[1] = g; dst[2] = b;
|
|
dst += 3;
|
|
src += 3;
|
|
}
|
|
src += row_ofs - pitch; // flip? previous row : stay
|
|
}
|
|
}
|
|
// RGBA <-> BGRA
|
|
else if(bpp == 32)
|
|
{
|
|
for(uint y = 0; y < h; y++)
|
|
{
|
|
for(uint x = 0; x < w; x++)
|
|
{
|
|
// need temporaries in case src == dst (i.e. not flipping)
|
|
const u8 b = src[0], g = src[1], r = src[2], a = src[3];
|
|
dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = a;
|
|
dst += 4;
|
|
src += 4;
|
|
}
|
|
src += row_ofs - pitch; // flip? previous row : stay
|
|
}
|
|
}
|
|
|
|
t->data = newData;
|
|
t->dataSize = data_size;
|
|
t->ofs = 0;
|
|
|
|
if(!(t->flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS)
|
|
RETURN_ERR(add_mipmaps(t, w, h, bpp, newData.get(), data_size));
|
|
|
|
CHECK_TEX(t);
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
TIMER_ADD_CLIENT(tc_transform);
|
|
|
|
// change <t>'s pixel format by flipping the state of all TEX_* flags
|
|
// that are set in transforms.
|
|
LibError tex_transform(Tex* t, uint transforms)
|
|
{
|
|
TIMER_ACCRUE(tc_transform);
|
|
CHECK_TEX(t);
|
|
|
|
const uint target_flags = t->flags ^ transforms;
|
|
uint remaining_transforms;
|
|
for(;;)
|
|
{
|
|
remaining_transforms = target_flags ^ t->flags;
|
|
// we're finished (all required transforms have been done)
|
|
if(remaining_transforms == 0)
|
|
return INFO::OK;
|
|
|
|
LibError ret = tex_codec_transform(t, remaining_transforms);
|
|
if(ret != INFO::OK)
|
|
break;
|
|
}
|
|
|
|
// last chance
|
|
RETURN_ERR(plain_transform(t, remaining_transforms));
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
// 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).
|
|
LibError tex_transform_to(Tex* t, uint new_flags)
|
|
{
|
|
// tex_transform takes care of validating <t>
|
|
const uint transforms = t->flags ^ new_flags;
|
|
return tex_transform(t, transforms);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// image orientation
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// see "Default Orientation" in docs.
|
|
|
|
static int global_orientation = TEX_TOP_DOWN;
|
|
|
|
// 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);
|
|
global_orientation = o;
|
|
}
|
|
|
|
|
|
static void flip_to_global_orientation(Tex* t)
|
|
{
|
|
// (can't use normal CHECK_TEX due to void return)
|
|
WARN_ERR(tex_validate(t));
|
|
|
|
uint orientation = t->flags & TEX_ORIENTATION;
|
|
// 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;
|
|
|
|
// (can't use normal CHECK_TEX due to void return)
|
|
WARN_ERR(tex_validate(t));
|
|
}
|
|
|
|
|
|
// indicate if the orientation specified by <src_flags> matches
|
|
// dst_orientation (if the latter is 0, then the global_orientation).
|
|
// (we ask for src_flags instead of src_orientation so callers don't
|
|
// have to mask off TEX_ORIENTATION)
|
|
bool tex_orientations_match(uint src_flags, uint dst_orientation)
|
|
{
|
|
const uint src_orientation = src_flags & TEX_ORIENTATION;
|
|
if(dst_orientation == 0)
|
|
dst_orientation = global_orientation;
|
|
return (src_orientation == dst_orientation);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// misc. API
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// indicate if <filename>'s extension is that of a texture format
|
|
// supported by tex_load. case-insensitive.
|
|
//
|
|
// rationale: tex_load complains if the given file is of an
|
|
// unsupported type. this API allows users to preempt that warning
|
|
// (by checking the filename themselves), and also provides for e.g.
|
|
// enumerating only images in a file picker.
|
|
// an alternative might be a flag to suppress warning about invalid files,
|
|
// but this is open to misuse.
|
|
bool tex_is_known_extension(const char* filename)
|
|
{
|
|
const TexCodecVTbl* dummy;
|
|
// found codec for it => known extension
|
|
const std::string extension = fs::extension(filename);
|
|
if(tex_codec_for_filename(extension, &dummy) == INFO::OK)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// 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.
|
|
//
|
|
// we need only add bookkeeping information and "wrap" it in
|
|
// our Tex struct, hence the name.
|
|
LibError tex_wrap(uint w, uint h, uint bpp, uint flags, shared_ptr<u8> data, size_t ofs, Tex* t)
|
|
{
|
|
t->w = w;
|
|
t->h = h;
|
|
t->bpp = bpp;
|
|
t->flags = flags;
|
|
t->data = data;
|
|
t->dataSize = ofs + w*h*bpp/8;
|
|
t->ofs = ofs;
|
|
|
|
CHECK_TEX(t);
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
// free all resources associated with the image and make further
|
|
// use of it impossible.
|
|
void tex_free(Tex* t)
|
|
{
|
|
// do not validate <t> - this is called from tex_load if loading
|
|
// failed, so not all fields may be valid.
|
|
|
|
t->data.reset();
|
|
|
|
// 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).
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getters
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// returns a pointer to the image data (pixels), taking into account any
|
|
// header(s) that may come before it.
|
|
u8* tex_get_data(const Tex* t)
|
|
{
|
|
// (can't use normal CHECK_TEX due to u8* return value)
|
|
WARN_ERR(tex_validate(t));
|
|
|
|
u8* p = t->data.get();
|
|
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 cbData)
|
|
{
|
|
size_t* ptotal_size = (size_t*)cbData;
|
|
*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)
|
|
{
|
|
// (can't use normal CHECK_TEX due to size_t return value)
|
|
WARN_ERR(tex_validate(t));
|
|
|
|
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 VfsPath& filename)
|
|
{
|
|
const TexCodecVTbl* c;
|
|
|
|
const std::string extension = fs::extension(filename);
|
|
CHECK_ERR(tex_codec_for_filename(extension, &c));
|
|
return c->hdr_size(0);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// read/write from memory and disk
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LibError tex_decode(shared_ptr<u8> data, size_t data_size, Tex* t)
|
|
{
|
|
const TexCodecVTbl* c;
|
|
RETURN_ERR(tex_codec_for_header(data.get(), data_size, &c));
|
|
|
|
// make sure the entire header is available
|
|
const size_t min_hdr_size = c->hdr_size(0);
|
|
if(data_size < min_hdr_size)
|
|
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
|
|
const size_t hdr_size = c->hdr_size(data.get());
|
|
if(data_size < hdr_size)
|
|
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
|
|
|
|
t->data = data;
|
|
t->dataSize = data_size;
|
|
t->ofs = hdr_size;
|
|
|
|
// for orthogonality, encode and decode both receive the memory as a
|
|
// DynArray. package data into one and free it again after decoding:
|
|
DynArray da;
|
|
RETURN_ERR(da_wrap_fixed(&da, data.get(), data_size));
|
|
|
|
RETURN_ERR(c->decode(&da, t));
|
|
|
|
// note: not reached if decode fails. that's not a problem;
|
|
// this call just zeroes <da> and could be left out.
|
|
(void)da_free(&da);
|
|
|
|
// sanity checks
|
|
if(!t->w || !t->h || t->bpp > 32)
|
|
WARN_RETURN(ERR::TEX_FMT_INVALID);
|
|
if(t->dataSize < t->ofs + tex_img_size(t))
|
|
WARN_RETURN(ERR::TEX_INVALID_SIZE);
|
|
|
|
flip_to_global_orientation(t);
|
|
|
|
CHECK_TEX(t);
|
|
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
LibError tex_encode(Tex* t, const std::string& extension, DynArray* da)
|
|
{
|
|
CHECK_TEX(t);
|
|
CHECK_ERR(tex_validate_plain_format(t->bpp, t->flags));
|
|
|
|
// we could be clever here and avoid the extra alloc if our current
|
|
// memory block ensued from the same kind of texture file. this is
|
|
// most likely the case if in_img == tex_get_data() + c->hdr_size(0).
|
|
// this would make for zero-copy IO.
|
|
|
|
const size_t max_out_size = tex_img_size(t)*4 + 256*KiB;
|
|
RETURN_ERR(da_alloc(da, max_out_size));
|
|
|
|
const TexCodecVTbl* c;
|
|
CHECK_ERR(tex_codec_for_filename(extension, &c));
|
|
|
|
// encode into <da>
|
|
LibError err = c->encode(t, da);
|
|
if(err < 0)
|
|
{
|
|
(void)da_free(da);
|
|
WARN_RETURN(err);
|
|
}
|
|
|
|
return INFO::OK;
|
|
}
|