vfs_store: set return value to #bytes written
fix 2 bugs in tex / ogl_tex reported by philip (related to caching) - thanks! tex: documented header and improved implementation. add tex_transform_to for convenience. change tex_write to require going through tex_wrap. This was SVN commit r2837.
This commit is contained in:
parent
c8b3be282d
commit
d367014972
@ -269,6 +269,7 @@ enum LibError
|
||||
ERR_TEX_COMPRESSED = -1504,
|
||||
WARN_TEX_INVALID_DATA = +1505,
|
||||
ERR_TEX_INVALID_SIZE = -1506,
|
||||
ERR_TEX_HEADER_NOT_COMPLETE = -1507,
|
||||
|
||||
ERR_CPU_FEATURE_MISSING = -1600,
|
||||
|
||||
|
@ -461,7 +461,7 @@ static void CALL_CONV emulate_glCompressedTexImage2D(
|
||||
Tex t;
|
||||
const uint flags = dxt;
|
||||
(void)tex_wrap((uint)w, (uint)h, s3tc_bpp, flags, (void*)data, &t);
|
||||
(void)tex_transform(&t, TEX_DXT);
|
||||
(void)tex_transform_to(&t, flags & ~TEX_DXT);
|
||||
|
||||
// uncompressed RGB[A] format info
|
||||
u8* const uc_data = tex_get_data(&t);
|
||||
|
@ -593,7 +593,7 @@ ret:
|
||||
// caveat: pads file to next max(4kb, sector_size) boundary
|
||||
// (due to limitation of Win32 FILE_FLAG_NO_BUFFERING I/O).
|
||||
// if that's a problem, specify FILE_NO_AIO when opening.
|
||||
int vfs_store(const char* v_fn, void* p, const size_t size, uint flags /* default 0 */)
|
||||
ssize_t vfs_store(const char* v_fn, void* p, const size_t size, uint flags /* default 0 */)
|
||||
{
|
||||
Handle hf = vfs_open(v_fn, flags|FILE_WRITE);
|
||||
RETURN_ERR(hf);
|
||||
@ -601,7 +601,7 @@ int vfs_store(const char* v_fn, void* p, const size_t size, uint flags /* defaul
|
||||
// error, we get "invalid handle" instead of vfs_open's error code.
|
||||
// don't CHECK_ERR because vfs_open already did.
|
||||
H_DEREF(hf, VFile, vf);
|
||||
const int ret = vfs_io(hf, size, &p);
|
||||
const ssize_t ret = vfs_io(hf, size, &p);
|
||||
WARN_ERR(vfs_close(hf));
|
||||
return ret;
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ extern ssize_t vfs_io(Handle hf, size_t size, void** p, FileIOCB cb = 0, uintptr
|
||||
// if flags & FILE_CACHE.
|
||||
extern Handle vfs_load(const char* fn, void*& p, size_t& size, uint flags = 0);
|
||||
|
||||
extern int vfs_store(const char* fn, void* p, size_t size, uint flags = 0);
|
||||
extern ssize_t vfs_store(const char* fn, void* p, size_t size, uint flags = 0);
|
||||
|
||||
|
||||
//
|
||||
|
@ -413,6 +413,9 @@ Handle ogl_tex_load(const char* fn, uint flags)
|
||||
//
|
||||
// <fn> isn't strictly needed but should describe the texture so that
|
||||
// h_filename will return a meaningful comment for debug purposes.
|
||||
// note: because we cannot guarantee that callers will pass distinct
|
||||
// "filenames", caching is disabled for the created object. this avoids
|
||||
// mistakenly reusing previous objects that share the same comment.
|
||||
Handle ogl_tex_wrap(Tex* t, const char* fn, uint flags)
|
||||
{
|
||||
// this object may not be backed by a file ("may", because
|
||||
@ -421,7 +424,8 @@ Handle ogl_tex_wrap(Tex* t, const char* fn, uint flags)
|
||||
// we won't be able to reconstruct it. therefore, disallow reloads.
|
||||
// (they are improbable anyway since caller is supposed to pass a
|
||||
// 'descriptive comment' instead of filename, but don't rely on that)
|
||||
flags |= RES_DISALLOW_RELOAD;
|
||||
// also disable caching as explained above.
|
||||
flags |= RES_DISALLOW_RELOAD|RES_NO_CACHE;
|
||||
return h_alloc(H_OglTex, fn, flags, t);
|
||||
}
|
||||
|
||||
|
@ -193,6 +193,9 @@ extern Handle ogl_tex_load(const char* fn, uint flags = 0);
|
||||
//
|
||||
// <fn> isn't strictly needed but should describe the texture so that
|
||||
// h_filename will return a meaningful comment for debug purposes.
|
||||
// note: because we cannot guarantee that callers will pass distinct
|
||||
// "filenames", caching is disabled for the created object. this avoids
|
||||
// mistakenly reusing previous objects that share the same comment.
|
||||
extern Handle ogl_tex_wrap(Tex* t, const char* fn = 0, uint flags = 0);
|
||||
|
||||
// free all resources associated with the texture and make further
|
||||
|
@ -29,9 +29,6 @@
|
||||
#include "tex_codec.h"
|
||||
|
||||
|
||||
#define ERR_TOO_SHORT -4
|
||||
|
||||
|
||||
// be careful not to use other tex_* APIs here because they call us.
|
||||
int tex_validate(const uint line, const Tex* t)
|
||||
{
|
||||
@ -79,12 +76,6 @@ int tex_validate(const uint line, const Tex* t)
|
||||
#define CHECK_TEX(t) CHECK_ERR(tex_validate(__LINE__, t))
|
||||
|
||||
|
||||
|
||||
// rationale for default: see tex_set_global_orientation
|
||||
static int global_orientation = TEX_TOP_DOWN;
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
@ -218,6 +209,61 @@ static int plain_transform(Tex* t, uint transforms)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// image orientation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// rationale for default: see tex_set_global_orientation
|
||||
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.
|
||||
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)
|
||||
{
|
||||
uint orientation = t->flags & TEX_ORIENTATION;
|
||||
// only if it knows which way around it is (DDS doesn't)
|
||||
if(orientation)
|
||||
{
|
||||
uint transforms = orientation ^ global_orientation;
|
||||
WARN_ERR(plain_transform(t, transforms));
|
||||
}
|
||||
|
||||
t->flags = (t->flags & ~TEX_ORIENTATION) | global_orientation;
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
static bool 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);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// support routines for codecs
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -278,7 +324,7 @@ int tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl**
|
||||
{
|
||||
// we guarantee at least 4 bytes for is_hdr to look at
|
||||
if(file_size < 4)
|
||||
return ERR_TOO_SHORT;
|
||||
return ERR_TEX_HEADER_NOT_COMPLETE;
|
||||
for(uint i = 0; i < MAX_CODECS; i++)
|
||||
{
|
||||
*c = codecs[i];
|
||||
@ -306,17 +352,13 @@ int tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl**
|
||||
int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
uint src_flags, uint dst_orientation, RowArray& rows)
|
||||
{
|
||||
// convenience for caller: allow passing all flags.
|
||||
const uint src_orientation = src_flags & TEX_ORIENTATION;
|
||||
if(dst_orientation == 0)
|
||||
dst_orientation = global_orientation;
|
||||
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
|
||||
const bool flip = (src_orientation ^ dst_orientation) != 0;
|
||||
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;
|
||||
@ -334,8 +376,6 @@ int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
|
||||
int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da)
|
||||
{
|
||||
CHECK_TEX(t);
|
||||
|
||||
RETURN_ERR(tex_transform(t, transforms));
|
||||
|
||||
void* img_data = tex_get_data(t); const size_t img_size = tex_img_size(t);
|
||||
@ -349,25 +389,107 @@ int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, D
|
||||
// API
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// 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.
|
||||
void tex_set_global_orientation(int o)
|
||||
|
||||
static int tex_load_impl(void* file_, size_t file_size, Tex* t)
|
||||
{
|
||||
debug_assert(o == TEX_TOP_DOWN || o == TEX_BOTTOM_UP);
|
||||
global_orientation = o;
|
||||
u8* file = (u8*)file_;
|
||||
|
||||
const TexCodecVTbl* c;
|
||||
RETURN_ERR(tex_codec_for_header(file, file_size, &c));
|
||||
|
||||
// get header make sure enough of the file has been read
|
||||
const size_t min_hdr_size = c->hdr_size(0);
|
||||
if(file_size < min_hdr_size)
|
||||
return ERR_TEX_HEADER_NOT_COMPLETE;
|
||||
const size_t hdr_size = c->hdr_size(file);
|
||||
if(file_size < hdr_size)
|
||||
return ERR_TEX_HEADER_NOT_COMPLETE;
|
||||
t->ofs = hdr_size;
|
||||
|
||||
DynArray da;
|
||||
RETURN_ERR(da_wrap_fixed(&da, file, file_size));
|
||||
|
||||
RETURN_ERR(c->decode(&da, t));
|
||||
|
||||
// sanity checks
|
||||
if(!t->w || !t->h || t->bpp > 32)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
// TODO: need to compare against the new t->hm (file may be compressed, cannot use file_size)
|
||||
//if(mem_size < t->ofs + tex_img_size(t))
|
||||
// return ERR_TEX_INVALID_SIZE;
|
||||
|
||||
flip_to_global_orientation(t);
|
||||
|
||||
CHECK_TEX(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tex_load(const char* fn, Tex* t)
|
||||
{
|
||||
// load file
|
||||
void* file; size_t file_size;
|
||||
Handle hm = vfs_load(fn, file, file_size);
|
||||
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);
|
||||
debug_warn("tex_load failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t)
|
||||
{
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
// note: we can't use tex_img_size because that requires all
|
||||
// Tex fields to be valid, but this calculation must be done first.
|
||||
const size_t img_size = w*h*bpp/8;
|
||||
t->hm = mem_wrap(img, img_size, 0, 0, 0, 0, 0);
|
||||
RETURN_ERR(t->hm);
|
||||
|
||||
// the exact value of img is lost, since the handle references the
|
||||
// allocation and disregards the offset within it given by <img>.
|
||||
// fix that up by setting t->ofs.
|
||||
void* reported_ptr = mem_get_ptr(t->hm);
|
||||
t->ofs = (u8*)img - (u8*)reported_ptr;
|
||||
|
||||
// TODO: remove when mem_wrap / mem_get_ptr add a reference correctly
|
||||
h_add_ref(t->hm);
|
||||
|
||||
CHECK_TEX(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tex_free(Tex* t)
|
||||
{
|
||||
CHECK_TEX(t);
|
||||
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)
|
||||
@ -388,89 +510,7 @@ size_t tex_img_size(const Tex* t)
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t tex_hdr_size(const char* fn)
|
||||
{
|
||||
const TexCodecVTbl* c;
|
||||
CHECK_ERR(tex_codec_for_filename(fn, &c));
|
||||
return c->hdr_size(0);
|
||||
}
|
||||
|
||||
|
||||
int tex_load_mem(Handle hm, Tex* t)
|
||||
{
|
||||
u8* file; size_t file_size;
|
||||
CHECK_ERR(mem_get(hm, &file, &file_size));
|
||||
|
||||
const TexCodecVTbl* c;
|
||||
CHECK_ERR(tex_codec_for_header(file, file_size, &c));
|
||||
|
||||
// make sure enough of the file has been read
|
||||
const size_t min_hdr_size = c->hdr_size(0);
|
||||
if(file_size < min_hdr_size)
|
||||
return ERR_TOO_SHORT;
|
||||
const size_t hdr_size = c->hdr_size(file);
|
||||
if(file_size < hdr_size)
|
||||
return ERR_TOO_SHORT;
|
||||
|
||||
|
||||
DynArray da;
|
||||
CHECK_ERR(da_wrap_fixed(&da, file, file_size));
|
||||
t->hm = hm;
|
||||
t->ofs = hdr_size;
|
||||
|
||||
int err = c->decode(&da, t);
|
||||
if(err < 0)
|
||||
{
|
||||
debug_printf("tex_load_mem (%s): %d", c->name, err);
|
||||
debug_warn("tex_load_mem failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
// sanity checks
|
||||
if(!t->w || !t->h || t->bpp > 32)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
// TODO: need to compare against the new t->hm (file may be compressed, cannot use file_size)
|
||||
//if(mem_size < t->ofs + tex_img_size(t))
|
||||
// return ERR_TOO_SHORT;
|
||||
|
||||
// flip image to global orientation
|
||||
uint orientation = t->flags & TEX_ORIENTATION;
|
||||
// .. but only if it knows which way around it is (DDS doesn't)
|
||||
if(orientation)
|
||||
{
|
||||
uint transforms = orientation ^ global_orientation;
|
||||
WARN_ERR(plain_transform(t, transforms));
|
||||
}
|
||||
|
||||
CHECK_TEX(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tex_load(const char* fn, Tex* t)
|
||||
{
|
||||
// load file
|
||||
void* p; size_t size; // unused
|
||||
Handle hm = vfs_load(fn, p, size);
|
||||
RETURN_ERR(hm); // (need handle below; can't test return value directly)
|
||||
int ret = tex_load_mem(hm, 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)
|
||||
memset(t, 0, sizeof(Tex));
|
||||
|
||||
// <t> has already been validated.
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int tex_free(Tex* t)
|
||||
{
|
||||
CHECK_TEX(t);
|
||||
mem_free_h(t->hm);
|
||||
return 0;
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
int tex_transform(Tex* t, uint transforms)
|
||||
@ -497,70 +537,58 @@ int tex_transform(Tex* t, uint transforms)
|
||||
}
|
||||
|
||||
|
||||
int tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t)
|
||||
int tex_transform_to(Tex* t, uint new_flags)
|
||||
{
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
// note: we can't use tex_img_size because that requires all
|
||||
// Tex fields to be valid, but this calculation must be done first.
|
||||
const size_t img_size = w*h*bpp/8;
|
||||
t->hm = mem_assign(img, img_size, 0, 0, 0, 0, 0);
|
||||
RETURN_ERR(t->hm);
|
||||
|
||||
// the exact value of img is lost, since the handle references the
|
||||
// allocation and disregards the offset within it given by <img>.
|
||||
// fix that up by setting t->ofs.
|
||||
void* reported_ptr = mem_get_ptr(t->hm);
|
||||
t->ofs = (u8*)img - (u8*)reported_ptr;
|
||||
|
||||
// TODO: remove when mem_assign / mem_get_ptr add a reference correctly
|
||||
h_add_ref(t->hm);
|
||||
|
||||
CHECK_TEX(t);
|
||||
return 0;
|
||||
const uint transforms = t->flags ^ new_flags;
|
||||
return tex_transform(t, transforms);
|
||||
}
|
||||
|
||||
|
||||
int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* in_img)
|
||||
{
|
||||
if(validate_format(bpp, flags) != 0)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Tex t;
|
||||
RETURN_ERR(tex_wrap(w, h, bpp, flags, in_img, &t));
|
||||
|
||||
size_t tex_hdr_size(const char* fn)
|
||||
{
|
||||
const TexCodecVTbl* c;
|
||||
CHECK_ERR(tex_codec_for_filename(fn, &c));
|
||||
return c->hdr_size(0);
|
||||
}
|
||||
|
||||
|
||||
int tex_write(Tex* t, const char* fn)
|
||||
{
|
||||
CHECK_ERR(validate_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 == <hm's user pointer> + c->hdr_size(0).
|
||||
// advantage is avoiding
|
||||
// this would make for zero-copy IO.
|
||||
|
||||
DynArray da;
|
||||
const size_t max_out_size = (w*h*4 + 256*KiB)*2;
|
||||
CHECK_ERR(da_alloc(&da, max_out_size));
|
||||
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(fn, &c));
|
||||
|
||||
// encode
|
||||
// encode into <da>
|
||||
int err;
|
||||
size_t rounded_size;
|
||||
|
||||
err = c->encode(&t, &da);
|
||||
err = c->encode(t, &da);
|
||||
if(err < 0)
|
||||
{
|
||||
debug_printf("tex_write (%s): %d", c->name, err);
|
||||
debug_warn("tex_writefailed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// write to disk
|
||||
rounded_size = round_up(da.cur_size, FILE_BLOCK_SIZE);
|
||||
CHECK_ERR(da_set_size(&da, rounded_size));
|
||||
WARN_ERR(vfs_store(fn, da.base, da.pos));
|
||||
err = 0;
|
||||
(void)da_set_size(&da, rounded_size);
|
||||
ssize_t bytes_written = vfs_store(fn, da.base, da.pos);
|
||||
debug_assert(bytes_written == (ssize_t)da.pos);
|
||||
|
||||
fail:
|
||||
(void)tex_free(&t);
|
||||
(void)da_free(&da);
|
||||
return err;
|
||||
}
|
||||
|
@ -16,83 +16,248 @@
|
||||
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
||||
// http://www.stud.uni-karlsruhe.de/~urkt/
|
||||
|
||||
#ifndef __TEX_H__
|
||||
#define __TEX_H__
|
||||
/*
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This module allows reading/writing 2d images in various file formats and
|
||||
encapsulates them in Tex objects.
|
||||
It supports converting between pixel formats; this is to an extent done
|
||||
automatically when reading/writing. Provision is also made for flipping
|
||||
all images to a default orientation.
|
||||
|
||||
|
||||
Format Conversion
|
||||
-----------------
|
||||
|
||||
Image file formats have major differences in their native pixel format:
|
||||
some store in BGR order, or have rows arranged bottom-up.
|
||||
We must balance runtime cost/complexity and convenience for the
|
||||
application (not dumping the entire problem on its lap).
|
||||
That means rejecting really obscure formats (e.g. right-to-left pixels),
|
||||
but converting everything else to uncompressed RGB "plain" format
|
||||
except where noted in enum TexFlags (1).
|
||||
|
||||
Note: conversion is implemented as a pipeline: e.g. "DDS decompress +
|
||||
vertical flip" would be done by decompressing to RGB (DDS codec) and then
|
||||
flipping (generic transform). This is in contrast to all<->all
|
||||
conversion paths: that would be much more complex, if more efficient.
|
||||
|
||||
Since any kind of preprocessing at runtime is undesirable (the absolute
|
||||
priority is minimizing load time), prefer file formats that are
|
||||
close to the final pixel format.
|
||||
|
||||
1) one of the exceptions is S3TC compressed textures. glCompressedTexImage2D
|
||||
requires these be passed in their original format; decompressing would be
|
||||
counterproductive. In this and similar cases, Tex.flags indicates such
|
||||
deviations from the plain 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.
|
||||
|
||||
Rationale: it is not expected that this will happen at the renderer layer
|
||||
(a 'flip all texcoords' flag is too much trouble), so the
|
||||
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".
|
||||
|
||||
IO is done via VFS, but the codecs are decoupled from this and
|
||||
work with memory buffers. Access to them is endian-safe.
|
||||
|
||||
When "writing", the image is put into an expandable memory region.
|
||||
This supports external libraries like libpng that do not know the
|
||||
output size beforehand, but avoids the need for a buffer between
|
||||
library and IO layer. Read and write are zero-copy.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TEX_H__
|
||||
#define TEX_H__
|
||||
|
||||
#include "../handle.h"
|
||||
|
||||
|
||||
|
||||
// flags & TEX_DXT stores the DXT number (1, 3, or 5). we need a special
|
||||
// value for DXT1a to obviate passing around an extra TEX_ALPHA flag.
|
||||
// value is arbitrary; do not rely on its relative ordering!
|
||||
const int DXT1A = 11;
|
||||
|
||||
// flags describing the pixel format. these are to be interpreted as
|
||||
// deviations from "plain" format, i.e. uncompressed RGB.
|
||||
enum TexFlags
|
||||
{
|
||||
TEX_DXT = 0x7, // mask; value = {1,3,5}
|
||||
// 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.
|
||||
TEX_DXT = 0x7, // mask
|
||||
// we need a special value for DXT1a to avoid having to consider
|
||||
// flags & TEX_ALPHA to determine S3TC type.
|
||||
// the value is arbitrary; do not rely on it!
|
||||
DXT1A = 7,
|
||||
|
||||
// indicates B and R pixel components are exchanged. depending on
|
||||
// flags & TEX_ALPHA or bpp, this means either BGR or BGRA.
|
||||
// not converted by default - it's an acceptable format for OpenGL.
|
||||
TEX_BGR = 0x08,
|
||||
|
||||
// indicates the image contains an alpha channel. this is set for
|
||||
// your convenience - there are many formats containing alpha and
|
||||
// divining this information from them is hard.
|
||||
// (conversion is not applicable here)
|
||||
TEX_ALPHA = 0x10,
|
||||
TEX_GREY = 0x20,
|
||||
|
||||
TEX_BGR = 0x08,
|
||||
// indicates the image is 8bpp greyscale. this is required to
|
||||
// differentiate between alpha-only and intensity formats.
|
||||
// (conversion is not applicable here)
|
||||
TEX_GREY = 0x20,
|
||||
|
||||
// orientation - never returned by ogl_tex_load, since it automatically
|
||||
// flips to match global orientation. these are passed to tex_write
|
||||
// to indicate the image orientation, or to tex_set_global_orientation.
|
||||
// flags & TEX_ORIENTATION is a field indicating orientation,
|
||||
// i.e. in what order the pixel rows are stored.
|
||||
//
|
||||
// tex_load always sets this to the global orientation
|
||||
// (and flips the image accordingly).
|
||||
// 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,
|
||||
TEX_TOP_DOWN = 0x80,
|
||||
TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, // mask
|
||||
|
||||
// mipmaps - if this flag is set, the image contains data for all
|
||||
// mipmap levels down to 1x1, stored contiguously.
|
||||
// indicates the image data includes mipmaps. they are stored from lowest
|
||||
// to highest (1x1), one after the other.
|
||||
// (conversion is not applicable here)
|
||||
TEX_MIPMAPS = 0x100
|
||||
};
|
||||
|
||||
// minimize size - stored in ogl tex resource control block
|
||||
|
||||
// stores all data describing an image.
|
||||
// we try to minimize size, since this is stored in OglTex resources
|
||||
// (which are big and pushing the h_mgr limit).
|
||||
struct Tex
|
||||
{
|
||||
Handle hm; // H_Mem handle to loaded file
|
||||
size_t ofs; // offset to image data in file
|
||||
// H_Mem handle to image data. note: during the course of transforms
|
||||
// (which may occur when being loaded), this may be replaced with
|
||||
// a Handle to a new buffer (e.g. if decompressing file contents).
|
||||
Handle hm;
|
||||
|
||||
// offset to image data in file. this is required since
|
||||
// tex_get_data needs to return the pixels, but mem_get_ptr(hm)
|
||||
// returns the actual file buffer. zero-copy load and
|
||||
// write-back to file is also made possible.
|
||||
size_t ofs;
|
||||
|
||||
uint w : 16;
|
||||
uint h : 16;
|
||||
uint bpp : 16;
|
||||
|
||||
// describes the image format; indicates deviations from the
|
||||
// plain image format (e.g. BGR layout, upside-down).
|
||||
//
|
||||
// we don't want to just load as-is and set flags, thereby dumping the
|
||||
// burden of conversion on apps. instead, we convert as much as is
|
||||
// convenient and necessary at load-time. however, we do not go
|
||||
// overboard and support all<->all conversions (too complex) or
|
||||
// encourage transforms at runtime.
|
||||
// in release builds, prefer formats optimized for their intended use
|
||||
// that don't require any preprocessing (minimize load time!)
|
||||
// see TexFlags and "Format Conversion" in docs.
|
||||
uint flags : 16;
|
||||
};
|
||||
|
||||
|
||||
// supports BMP, TGA, JPG, JP2, PNG, DDS.
|
||||
extern int tex_load(const char* fn, Tex* t);
|
||||
extern int tex_free(Tex* t);
|
||||
// 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).
|
||||
extern void tex_set_global_orientation(int orientation);
|
||||
|
||||
|
||||
//
|
||||
// open/close
|
||||
//
|
||||
|
||||
// load the specified image from file into the given Tex object.
|
||||
// currently supports BMP, TGA, JPG, JP2, PNG, DDS.
|
||||
extern int tex_load(const char* fn, Tex* t);
|
||||
|
||||
// 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.
|
||||
extern int tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t);
|
||||
|
||||
extern u8* tex_get_data(const Tex* t);
|
||||
extern size_t tex_img_size(const Tex* t);
|
||||
extern size_t tex_hdr_size(const char* fn);
|
||||
// free all resources associated with the image and make further
|
||||
// use of it impossible.
|
||||
extern int tex_free(Tex* t);
|
||||
|
||||
|
||||
//
|
||||
// modify image
|
||||
//
|
||||
|
||||
// change <t>'s pixel format by flipping the state of all TEX_* flags
|
||||
// that are set in transforms.
|
||||
extern int tex_transform(Tex* t, uint transforms);
|
||||
|
||||
extern int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* img);
|
||||
// change <t>'s pixel format to the new format specified by <new_flags>.
|
||||
extern int tex_transform_to(Tex* t, uint new_flags);
|
||||
|
||||
// rationale: some libraries can flip images for free when loading, so set a
|
||||
// global orientation rather than only flipping at upload time.
|
||||
// param: either TEX_BOTTOM_UP or TEX_TOP_DOWN
|
||||
extern void tex_set_global_orientation(int orientation);
|
||||
|
||||
//
|
||||
// return image information
|
||||
//
|
||||
|
||||
// since Tex is a struct, its fields are accessible to callers.
|
||||
// this is more for C compatibility than convenience; the following should
|
||||
// be used instead of direct access to the corresponding fields because
|
||||
// they take care of some dirty work.
|
||||
|
||||
// returns a pointer to the image data (pixels), taking into account any
|
||||
// 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
|
||||
// less error-prone (e.g. confusing bits_per_pixel with bytes).
|
||||
extern size_t tex_img_size(const Tex* t);
|
||||
|
||||
|
||||
//
|
||||
// image writing
|
||||
//
|
||||
|
||||
// returns 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").
|
||||
// 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.
|
||||
extern 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.
|
||||
extern int tex_write(Tex* t, const char* fn);
|
||||
|
||||
|
||||
// internal use only:
|
||||
extern int tex_validate(uint line, const Tex* t);
|
||||
|
||||
#endif // __TEX_H__
|
||||
#endif // TEX_H__
|
||||
|
@ -20,6 +20,9 @@ struct TexCodecVTbl
|
||||
// rationale: some codecs cannot calculate the output size beforehand
|
||||
// (e.g. PNG output via libpng); we therefore require each one to
|
||||
// allocate memory itself and return the pointer.
|
||||
//
|
||||
// note: <t> cannot be made const because encoding may require a
|
||||
// tex_transform.
|
||||
int (*encode)(Tex* t, DynArray* da);
|
||||
|
||||
int (*transform)(Tex* t, uint transforms);
|
||||
@ -52,6 +55,15 @@ const int TEX_CODEC_CANNOT_HANDLE = 1;
|
||||
// can handle the given format, this is not a problem.
|
||||
extern int tex_codec_register(const TexCodecVTbl* c);
|
||||
|
||||
|
||||
// find codec that recognizes the desired output file extension
|
||||
extern int tex_codec_for_filename(const char* fn, const TexCodecVTbl** c);
|
||||
|
||||
// find codec that recognizes the header's magic field
|
||||
extern int tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl** c);
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
@ -70,12 +82,4 @@ 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);
|
||||
|
||||
|
||||
struct MemSource
|
||||
{
|
||||
u8* p;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
#endif // #ifndef TEX_CODEC_H__
|
||||
|
@ -516,8 +516,7 @@ static int jpg_encode_impl(Tex* t,
|
||||
jpeg_start_compress(cinfo, TRUE);
|
||||
|
||||
// if BGR, convert to RGB.
|
||||
const int transform_bgr = t->flags & TEX_BGR; // JPG is native RGB.
|
||||
WARN_ERR(tex_transform(t, transform_bgr));
|
||||
WARN_ERR(tex_transform_to(t, t->flags & ~TEX_BGR));
|
||||
|
||||
const size_t pitch = t->w * t->bpp / 8;
|
||||
u8* data = tex_get_data(t);
|
||||
|
@ -64,7 +64,7 @@ static PtrToH& get_ptr_to_h()
|
||||
#define ptr_to_h get_ptr_to_h()
|
||||
|
||||
|
||||
// not needed by other modules - mem_get_size and mem_assign is enough.
|
||||
// not needed by other modules - mem_get_size and mem_wrap is enough.
|
||||
static Handle find_alloc(void* target_p, It* out_it = 0)
|
||||
{
|
||||
// early out optimization (don't pay for full subset check)
|
||||
@ -256,19 +256,24 @@ int mem_free_p(void*& p)
|
||||
|
||||
// create a H_MEM handle of type MEM_USER,
|
||||
// and assign it the specified memory range.
|
||||
// dtor is called when the handle is freed, if non-NULL.
|
||||
Handle mem_assign(void* p, size_t size, uint flags, void* raw_p, size_t raw_size, MEM_DTOR dtor, uintptr_t ctx)
|
||||
// if dtor is non-NULL, it is called (passing ctx) when the handle is freed.
|
||||
Handle mem_wrap(void* p, size_t size, uint flags, void* raw_p, size_t raw_size, MEM_DTOR dtor, uintptr_t ctx)
|
||||
{
|
||||
if(!p || !size)
|
||||
CHECK_ERR(ERR_INVALID_PARAM);
|
||||
|
||||
// we've already allocated that pointer - returns its handle
|
||||
Handle hm = find_alloc(p);
|
||||
if(hm > 0)
|
||||
return hm;
|
||||
|
||||
if(!p || !size)
|
||||
{
|
||||
debug_warn("mem_assign: invalid p or size");
|
||||
return 0;
|
||||
}
|
||||
// <p> wasn't allocated via mem_alloc, or we would've found its Handle.
|
||||
// it is therefore some user-allocated mem and might therefore not have
|
||||
// a valid <raw_p> set. since that's our search key, we set it to <p>.
|
||||
if(!raw_p)
|
||||
raw_p = p;
|
||||
if(!raw_size)
|
||||
raw_size = size;
|
||||
|
||||
hm = h_alloc(H_Mem, (const char*)p, flags|RES_KEY|RES_NO_CACHE, raw_p);
|
||||
RETURN_ERR(hm);
|
||||
@ -280,7 +285,6 @@ Handle mem_assign(void* p, size_t size, uint flags, void* raw_p, size_t raw_size
|
||||
m->raw_size = raw_size;
|
||||
m->dtor = dtor;
|
||||
m->ctx = ctx;
|
||||
|
||||
return hm;
|
||||
}
|
||||
|
||||
@ -339,10 +343,10 @@ void* mem_alloc(size_t size, const size_t align, uint flags, Handle* phm)
|
||||
void* p = (void*)round_up((uintptr_t)raw_p, align);
|
||||
|
||||
|
||||
Handle hm = mem_assign(p, size, flags, raw_p, raw_size, dtor, ctx);
|
||||
Handle hm = mem_wrap(p, size, flags, raw_p, raw_size, dtor, ctx);
|
||||
if(!hm) // failed to allocate a handle
|
||||
{
|
||||
debug_warn("mem_alloc: mem_assign failed");
|
||||
debug_warn("mem_alloc: mem_wrap failed");
|
||||
dtor(p, size, ctx);
|
||||
return 0;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ extern int mem_get(Handle hm, u8** pp, size_t* psize);
|
||||
extern void mem_shutdown(void);
|
||||
|
||||
|
||||
extern Handle mem_assign(void* p, size_t size, uint flags, void* raw_p, size_t raw_size, MEM_DTOR dtor, uintptr_t ctx);
|
||||
extern Handle mem_wrap(void* p, size_t size, uint flags, void* raw_p, size_t raw_size, MEM_DTOR dtor, uintptr_t ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -560,8 +560,7 @@ int sys_cursor_load(const char* filename,
|
||||
uint w = t.w, h = t.h;
|
||||
|
||||
// convert to BGRA (required by CreateBitmap).
|
||||
const uint transforms = (t.flags & TEX_BGR) ^ TEX_BGR;
|
||||
RETURN_ERR(tex_transform(&t, transforms));
|
||||
RETURN_ERR(tex_transform_to(&t, t.flags|TEX_BGR));
|
||||
void* tex_bgra = tex_get_data(&t);
|
||||
|
||||
// MSDN says selecting this HBITMAP into a DC is slower since we use
|
||||
|
@ -199,12 +199,11 @@ void WriteScreenshot(const char* extension)
|
||||
return;
|
||||
}
|
||||
GLvoid* img = (u8*)data + hdr_size;
|
||||
|
||||
|
||||
Tex t;
|
||||
if(tex_wrap(w, h, bpp, flags, img, &t) < 0)
|
||||
return;
|
||||
glReadPixels(0, 0, w, h, fmt, GL_UNSIGNED_BYTE, img);
|
||||
|
||||
if(tex_write(fn, w, h, bpp, flags, img) < 0)
|
||||
debug_warn("WriteScreenshot: tex_write failed");
|
||||
|
||||
(void)tex_write(&t, fn);
|
||||
(void)tex_free(&t);
|
||||
mem_free_h(img_hm);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user