1
0
forked from 0ad/0ad

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:
janwas 2005-10-03 12:57:31 +00:00
parent c8b3be282d
commit d367014972
14 changed files with 437 additions and 231 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
//

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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__

View File

@ -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__

View File

@ -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);

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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);
}