dyn_array: efficient expandable array that doesn't waste mem or relocate itself. TODO: self test and dox
file: expose block size constant; always round user transfers up tex: transform() now receives what to change, not what to achieve. tex*: encoding works with DynArray+VFS backend GameSetup: free g_BuildingPlacer to avoid mem leak. fixed some warnings+bugs. This was SVN commit r2679.
This commit is contained in:
parent
9dc8a71b15
commit
1a7d67a97f
213
source/lib/dyn_array.cpp
Normal file
213
source/lib/dyn_array.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include "precompiled.h"
|
||||
#include "posix.h"
|
||||
|
||||
#include "dyn_array.h"
|
||||
|
||||
|
||||
static const size_t page_size = sysconf(_SC_PAGE_SIZE);
|
||||
|
||||
static bool is_page_multiple(uintptr_t x)
|
||||
{
|
||||
return (x % page_size) == 0;
|
||||
}
|
||||
|
||||
static size_t round_up_to_page(size_t size)
|
||||
{
|
||||
return round_up(size, page_size);
|
||||
}
|
||||
|
||||
|
||||
static int validate_da(DynArray* da)
|
||||
{
|
||||
if(!da)
|
||||
return ERR_INVALID_PARAM;
|
||||
u8* const base = da->base;
|
||||
const size_t max_size_pa = da->max_size_pa;
|
||||
const size_t cur_size = da->cur_size;
|
||||
const size_t pos = da->pos;
|
||||
const int prot = da->prot;
|
||||
|
||||
if(debug_is_pointer_bogus(base))
|
||||
return -1;
|
||||
if(!is_page_multiple((uintptr_t)base))
|
||||
return -2;
|
||||
if(!is_page_multiple(max_size_pa))
|
||||
return -3;
|
||||
if(cur_size > max_size_pa)
|
||||
return -4;
|
||||
if(pos > cur_size || pos > max_size_pa)
|
||||
return -5;
|
||||
if(prot & ~(PROT_READ|PROT_WRITE|PROT_EXEC))
|
||||
return -6;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CHECK_DA(da) CHECK_ERR(validate_da(da))
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// very thin wrapper on top of sys/mman.h that makes the intent more obvious
|
||||
// (its commit/decommit semantics are difficult to tell apart).
|
||||
|
||||
static const int mmap_flags = MAP_PRIVATE|MAP_ANONYMOUS;
|
||||
|
||||
static int mem_reserve(size_t size, u8** pp)
|
||||
{
|
||||
void* ret = mmap(0, size, PROT_NONE, mmap_flags|MAP_NORESERVE, -1, 0);
|
||||
if(ret == MAP_FAILED)
|
||||
return ERR_NO_MEM;
|
||||
*pp = (u8*)ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_release(u8* p, size_t size)
|
||||
{
|
||||
return munmap(p, size);
|
||||
}
|
||||
|
||||
static int mem_commit(u8* p, size_t size, int prot)
|
||||
{
|
||||
if(prot == PROT_NONE)
|
||||
{
|
||||
debug_warn("mem_commit: prot=PROT_NONE isn't allowed (misinterpreted by mmap)");
|
||||
return ERR_INVALID_PARAM;
|
||||
}
|
||||
void* ret = mmap(p, size, prot, mmap_flags|MAP_FIXED, -1, 0);
|
||||
return (ret == MAP_FAILED)? -1 : 0;
|
||||
}
|
||||
|
||||
static int mem_decommit(u8* p, size_t size)
|
||||
{
|
||||
void* ret = mmap(p, size, PROT_NONE, mmap_flags|MAP_NORESERVE|MAP_FIXED, -1, 0);
|
||||
return (ret == MAP_FAILED)? -1 : 0;
|
||||
}
|
||||
|
||||
static int mem_protect(u8* p, size_t size, int prot)
|
||||
{
|
||||
return mprotect(p, size, prot);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
int da_alloc(DynArray* da, size_t max_size)
|
||||
{
|
||||
const size_t max_size_pa = round_up_to_page(max_size);
|
||||
|
||||
u8* p;
|
||||
CHECK_ERR(mem_reserve(max_size_pa, &p));
|
||||
|
||||
da->base = p;
|
||||
da->max_size_pa = max_size_pa;
|
||||
da->cur_size = 0;
|
||||
da->pos = 0;
|
||||
da->prot = PROT_READ|PROT_WRITE;
|
||||
CHECK_DA(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int da_free(DynArray* da)
|
||||
{
|
||||
CHECK_DA(da);
|
||||
|
||||
// latch pointer; wipe out the DynArray for safety
|
||||
// (must be done here because mem_release may fail)
|
||||
u8* p = da->base;
|
||||
size_t size = da->max_size_pa;
|
||||
memset(da, 0, sizeof(*da));
|
||||
|
||||
CHECK_ERR(mem_release(p, size));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int da_set_size(DynArray* da, size_t new_size)
|
||||
{
|
||||
CHECK_DA(da);
|
||||
|
||||
// determine how much to add/remove
|
||||
const size_t cur_size_pa = round_up_to_page(da->cur_size);
|
||||
const size_t new_size_pa = round_up_to_page(new_size);
|
||||
if(new_size_pa > da->max_size_pa)
|
||||
CHECK_ERR(ERR_INVALID_PARAM);
|
||||
const ssize_t size_delta_pa = (ssize_t)new_size_pa - (ssize_t)cur_size_pa;
|
||||
|
||||
u8* end = da->base + cur_size_pa;
|
||||
// expanding
|
||||
if(size_delta_pa > 0)
|
||||
CHECK_ERR(mem_commit(end, size_delta_pa, da->prot));
|
||||
// shrinking
|
||||
else if(size_delta_pa < 0)
|
||||
CHECK_ERR(mem_decommit(end+size_delta_pa, size_delta_pa));
|
||||
// else: no change in page count, e.g. if going from size=1 to 2
|
||||
// (we don't want mem_* to have to handle size=0)
|
||||
|
||||
da->cur_size = new_size;
|
||||
CHECK_DA(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int da_set_prot(DynArray* da, int prot)
|
||||
{
|
||||
CHECK_DA(da);
|
||||
|
||||
da->prot = prot;
|
||||
CHECK_ERR(mem_protect(da->base, da->cur_size, prot));
|
||||
|
||||
CHECK_DA(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int da_read(DynArray* da, void* data, size_t size)
|
||||
{
|
||||
void* src = da->base + da->pos;
|
||||
|
||||
// make sure we have enough data to read
|
||||
da->pos += size;
|
||||
if(da->pos > da->cur_size)
|
||||
return -1;
|
||||
|
||||
memcpy(data, src, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int da_append(DynArray* da, const void* data, size_t size)
|
||||
{
|
||||
RETURN_ERR(da_set_size(da, da->pos+size));
|
||||
memcpy(da->base+da->pos, data, size);
|
||||
da->pos += size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_api()
|
||||
{
|
||||
}
|
||||
|
||||
static void test_expand()
|
||||
{
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_api();
|
||||
test_expand();
|
||||
}
|
||||
|
||||
RUN_SELF_TEST;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
25
source/lib/dyn_array.h
Normal file
25
source/lib/dyn_array.h
Normal file
@ -0,0 +1,25 @@
|
||||
#include "lib/types.h"
|
||||
|
||||
struct DynArray
|
||||
{
|
||||
u8* base;
|
||||
size_t max_size_pa; // reserved
|
||||
size_t cur_size; // committed
|
||||
size_t pos;
|
||||
int prot; // applied to newly committed pages
|
||||
};
|
||||
|
||||
|
||||
extern int da_alloc(DynArray* da, size_t max_size);
|
||||
|
||||
extern int da_free(DynArray* da);
|
||||
|
||||
extern int da_set_size(DynArray* da, size_t new_size);
|
||||
|
||||
extern int da_set_prot(DynArray* da, int prot);
|
||||
|
||||
|
||||
|
||||
extern int da_read(DynArray* da, void* data_dst, size_t size);
|
||||
|
||||
extern int da_append(DynArray* da, const void* data_src, size_t size);
|
@ -351,7 +351,6 @@ int dir_open(const char* P_path, DirIterator* d_)
|
||||
CHECK_ERR(err);
|
||||
}
|
||||
|
||||
// (see pp declaration)
|
||||
return pp_set_dir(&d->pp, n_path);
|
||||
}
|
||||
|
||||
@ -1109,12 +1108,8 @@ debug_printf("file_io fd=%d size=%d ofs=%d\n", f->fd, data_size, data_ofs);
|
||||
const size_t ofs_misalign = data_ofs % BLOCK_SIZE;
|
||||
const size_t lead_padding = do_align? ofs_misalign : 0;
|
||||
// for convenience; used below.
|
||||
|
||||
if(do_align)
|
||||
{
|
||||
actual_ofs -= (off_t)ofs_misalign;
|
||||
actual_size = round_up(ofs_misalign + data_size, BLOCK_SIZE);
|
||||
}
|
||||
actual_ofs -= (off_t)lead_padding;
|
||||
actual_size = round_up(lead_padding + data_size, BLOCK_SIZE);
|
||||
|
||||
|
||||
// skip aio code, use lowio
|
||||
|
@ -94,9 +94,12 @@ extern int file_rel_chdir(const char* argv0, const char* rel_path);
|
||||
// rationale: some private storage apart from opendir's DIR* is required
|
||||
// to support stat(). we prefer having the caller reserve room (on the stack)
|
||||
// rather than allocating dynamically (less efficient or more complicated).
|
||||
//
|
||||
// this is an opaque struct to avoid exposing our internals and insulate
|
||||
// user code against changes; we verify at compile-time that the
|
||||
// public/private definitions match.
|
||||
// note: cannot just typedef to DirIterator_ because other modules
|
||||
// instantiate this.
|
||||
struct DirIterator
|
||||
{
|
||||
char opaque[512];
|
||||
@ -245,6 +248,10 @@ extern int file_discard_io(FileIo* io);
|
||||
// synchronous IO
|
||||
//
|
||||
|
||||
// user-specified offsets and transfer lengths must be multiples of this!
|
||||
// (simplifies file_io)
|
||||
const size_t FILE_BLOCK_SIZE = 64*KiB;
|
||||
|
||||
// return value:
|
||||
// < 0: failed; abort transfer.
|
||||
// >= 0: bytes output; continue.
|
||||
|
@ -73,22 +73,21 @@ static int validate_format(uint bpp, uint flags)
|
||||
// but is much easier to maintain than providing all<->all conversion paths.
|
||||
//
|
||||
// somewhat optimized (loops are hoisted, cache associativity accounted for)
|
||||
static int plain_transform(Tex* t, uint new_format)
|
||||
static int plain_transform(Tex* t, uint transforms)
|
||||
{
|
||||
// extract texture info
|
||||
const int pending_transforms = t->flags ^ new_format;
|
||||
const uint w = t->w, h = t->h, bpp = t->bpp, flags = t->flags;
|
||||
u8* const img = tex_get_data(t);
|
||||
|
||||
// sanity checks (not errors, we just can't handle these cases)
|
||||
// .. unknown transform
|
||||
if(pending_transforms & ~(TEX_BGR|TEX_ORIENTATION))
|
||||
if(transforms & ~(TEX_BGR|TEX_ORIENTATION))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
// .. img is not in "plain" format
|
||||
if(validate_format(bpp, flags) != 0)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
// .. nothing to do
|
||||
if(!pending_transforms)
|
||||
if(!transforms)
|
||||
return 0;
|
||||
|
||||
// setup row source/destination pointers (simplifies outer loop)
|
||||
@ -99,7 +98,7 @@ static int plain_transform(Tex* t, uint new_format)
|
||||
// avoid y*pitch multiply in row loop; instead, add row_ofs.
|
||||
void* clone_img = 0;
|
||||
// flipping rows (0,1,2 -> 2,1,0)
|
||||
if(pending_transforms & TEX_ORIENTATION)
|
||||
if(transforms & TEX_ORIENTATION)
|
||||
{
|
||||
// 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,
|
||||
@ -117,7 +116,7 @@ static int plain_transform(Tex* t, uint new_format)
|
||||
}
|
||||
|
||||
// no BGR convert necessary
|
||||
if(!(pending_transforms & TEX_BGR))
|
||||
if(!(transforms & TEX_BGR))
|
||||
{
|
||||
for(uint y = 0; y < h; y++)
|
||||
{
|
||||
@ -206,18 +205,22 @@ int tex_codec_register(const TexCodecVTbl* c)
|
||||
// note: we don't allocate the data param ourselves because this function is
|
||||
// needed for encoding, too (where data is already present).
|
||||
int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
int file_orientation, RowArray& rows)
|
||||
uint src_flags, uint dst_orientation, RowArray& rows)
|
||||
{
|
||||
debug_assert((file_orientation & ~TEX_ORIENTATION) == 0);
|
||||
// convenience for caller: allow passing all flags.
|
||||
const uint src_orientation = src_flags & TEX_ORIENTATION;
|
||||
if(dst_orientation == 0)
|
||||
dst_orientation = global_orientation;
|
||||
|
||||
rows = (RowArray)malloc(h * sizeof(RowPtr));
|
||||
if(!rows)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
// determine start position and direction
|
||||
const bool flip = (file_orientation ^ global_orientation) != 0;
|
||||
const bool flip = (src_orientation ^ dst_orientation) != 0;
|
||||
RowPtr pos = flip? data+pitch*(h-1) : data;
|
||||
ssize_t add = flip? -(ssize_t)pitch : (ssize_t)pitch;
|
||||
const ssize_t add = flip? -(ssize_t)pitch : (ssize_t)pitch;
|
||||
const RowPtr end = flip? data-pitch : data+pitch*h;
|
||||
|
||||
for(size_t i = 0; i < h; i++)
|
||||
{
|
||||
@ -225,15 +228,26 @@ int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
pos += add;
|
||||
}
|
||||
|
||||
debug_assert(pos == end);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tex_codec_set_orientation(Tex* t, int file_orientation)
|
||||
int tex_codec_set_orientation(Tex* t, uint file_orientation)
|
||||
{
|
||||
int pending_transforms = file_orientation ^ global_orientation;
|
||||
int new_flags = t->flags ^ pending_transforms;
|
||||
return plain_transform(t, new_flags);
|
||||
uint transforms = file_orientation ^ global_orientation;
|
||||
return plain_transform(t, transforms);
|
||||
}
|
||||
|
||||
|
||||
int tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da)
|
||||
{
|
||||
RETURN_ERR(tex_transform(t, transforms));
|
||||
|
||||
void* img_data = tex_get_data(t); const size_t img_size = tex_img_size(t);
|
||||
RETURN_ERR(da_append(da, hdr, hdr_size));
|
||||
RETURN_ERR(da_append(da, img_data, img_size));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -277,6 +291,9 @@ int tex_load_mem(Handle hm, const char* fn, Tex* t)
|
||||
// find codec that understands the data, and decode
|
||||
for(int i = 0; i < MAX_CODECS; i++)
|
||||
{
|
||||
// MAX_CODECS isn't a tight bound and we have hit a 0 entry
|
||||
if(!codecs[i])
|
||||
continue;
|
||||
const char* err_msg = 0;
|
||||
int err = codecs[i]->decode(p, size, t, &err_msg);
|
||||
if(err == TEX_CODEC_CANNOT_HANDLE)
|
||||
@ -321,14 +338,21 @@ u8* tex_get_data(const Tex* t)
|
||||
return p + t->ofs;
|
||||
}
|
||||
|
||||
size_t tex_img_size(const Tex* t)
|
||||
{
|
||||
return t->w * t->h * t->bpp/8;
|
||||
}
|
||||
|
||||
int tex_transform(Tex* t, uint new_flags)
|
||||
|
||||
int tex_transform(Tex* t, uint transforms)
|
||||
{
|
||||
// find codec that understands the data, and transform
|
||||
for(int i = 0; i < MAX_CODECS; i++)
|
||||
{
|
||||
const char* err_msg = 0;
|
||||
int err = codecs[i]->transform(t, new_flags);
|
||||
// MAX_CODECS isn't a tight bound and we have hit a 0 entry
|
||||
if(!codecs[i])
|
||||
continue;
|
||||
int err = codecs[i]->transform(t, transforms);
|
||||
if(err == TEX_CODEC_CANNOT_HANDLE)
|
||||
continue;
|
||||
if(err == 0)
|
||||
@ -338,12 +362,14 @@ int tex_transform(Tex* t, uint new_flags)
|
||||
}
|
||||
|
||||
// last chance
|
||||
return plain_transform(t, new_flags);
|
||||
return plain_transform(t, transforms);
|
||||
}
|
||||
|
||||
|
||||
int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* in_img)
|
||||
{
|
||||
int err = -1;
|
||||
|
||||
if(validate_format(bpp, flags) != 0)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
|
||||
@ -362,24 +388,37 @@ int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* in_img
|
||||
w, h, bpp, flags
|
||||
};
|
||||
|
||||
u8* out; size_t out_size;
|
||||
DynArray da;
|
||||
const size_t max_out_size = (w*h*4 + 256*KiB)*2;
|
||||
CHECK_ERR(da_alloc(&da, max_out_size));
|
||||
|
||||
for(int i = 0; i < MAX_CODECS; i++)
|
||||
{
|
||||
// MAX_CODECS isn't a tight bound and we have hit a 0 entry
|
||||
if(!codecs[i])
|
||||
continue;
|
||||
const char* err_msg = 0;
|
||||
int err = codecs[i]->encode(ext, &t, &out, &out_size, &err_msg);
|
||||
err = codecs[i]->encode(ext, &t, &da, &err_msg);
|
||||
if(err == TEX_CODEC_CANNOT_HANDLE)
|
||||
continue;
|
||||
if(err == 0)
|
||||
goto have_codec;
|
||||
if(err < 0)
|
||||
{
|
||||
debug_printf("tex_write(%s): %s: %s", codecs[i]->name, fn, err_msg);
|
||||
CHECK_ERR(err);
|
||||
WARN_ERR(err);
|
||||
goto ret;
|
||||
}
|
||||
goto have_codec; // success
|
||||
}
|
||||
|
||||
// no codec found
|
||||
return ERR_UNKNOWN_FORMAT;
|
||||
|
||||
have_codec:
|
||||
WARN_ERR(vfs_store(fn, out, out_size));
|
||||
free(out);
|
||||
return 0;
|
||||
size_t 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;
|
||||
ret:
|
||||
WARN_ERR(da_free(&da));
|
||||
return err;
|
||||
}
|
||||
|
@ -72,7 +72,9 @@ extern int tex_load_mem(Handle hm, const char* fn, Tex* t);
|
||||
extern int tex_free(Tex* t);
|
||||
|
||||
extern u8* tex_get_data(const Tex* t);
|
||||
extern int tex_transform(Tex* t, uint new_flags);
|
||||
extern size_t tex_img_size(const Tex* t);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -34,7 +34,7 @@ struct BmpHeader
|
||||
#define BI_RGB 0 // biCompression
|
||||
|
||||
|
||||
static int bmp_transform(Tex* t, int new_flags)
|
||||
static int bmp_transform(Tex* UNUSED(t), uint UNUSED(transforms))
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
@ -67,9 +67,7 @@ fail:
|
||||
|
||||
const int orientation = (h_ < 0)? TEX_TOP_DOWN : TEX_BOTTOM_UP;
|
||||
const long h = abs(h_);
|
||||
const size_t pitch = w * bpp/8;
|
||||
const size_t img_size = h * pitch;
|
||||
u8* const img = file + ofs;
|
||||
const size_t img_size = tex_img_size(t);
|
||||
|
||||
int flags = TEX_BGR;
|
||||
if(bpp == 32)
|
||||
@ -101,21 +99,19 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
static int bmp_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
static int bmp_encode(const char* ext, Tex* t, DynArray* da, const char** UNUSED(perr_msg))
|
||||
{
|
||||
if(stricmp(ext, "bmp"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
size_t img_size = t->w * t->h * t->bpp/8;
|
||||
|
||||
const size_t hdr_size = sizeof(BmpHeader); // needed for BITMAPFILEHEADER
|
||||
const size_t img_size = tex_img_size(t);
|
||||
const size_t file_size = hdr_size + img_size;
|
||||
const long h = (t->flags & TEX_TOP_DOWN)? -(long)t->h : t->h;
|
||||
|
||||
int transforms = t->flags;
|
||||
transforms &= ~TEX_ORIENTATION; // no flip needed - we can set top-down bit.
|
||||
transforms ^= TEX_BGR; // BMP is native BGR.
|
||||
tex_transform(t, transforms);
|
||||
|
||||
const BmpHeader hdr =
|
||||
{
|
||||
@ -135,8 +131,7 @@ size_t img_size = t->w * t->h * t->bpp/8;
|
||||
(u32)img_size, // biSizeImage
|
||||
0, 0, 0, 0 // unused (bi?PelsPerMeter, biClr*)
|
||||
};
|
||||
|
||||
return 0;
|
||||
return tex_codec_write(t, transforms, &hdr, hdr_size, da);
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(bmp);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define TEX_CODEC_H__
|
||||
|
||||
#include "tex.h"
|
||||
#include "dyn_array.h"
|
||||
|
||||
// rationale: no C++ to allow us to store const char* name in vtbl.
|
||||
|
||||
@ -17,9 +18,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.
|
||||
int (*encode)(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg);
|
||||
int (*encode)(const char* ext, Tex* t, DynArray* da, const char** perr_msg);
|
||||
|
||||
int (*transform)(Tex* t, int new_flags);
|
||||
int (*transform)(Tex* t, uint transforms);
|
||||
|
||||
const char* name;
|
||||
};
|
||||
@ -52,9 +53,11 @@ extern int tex_codec_register(const TexCodecVTbl* c);
|
||||
typedef const u8* RowPtr;
|
||||
typedef RowPtr* RowArray;
|
||||
extern int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
int file_orientation, RowArray& rows);
|
||||
uint src_flags, uint dst_orientation, RowArray& rows);
|
||||
|
||||
extern int tex_codec_set_orientation(Tex* t, int file_orientation);
|
||||
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
|
||||
|
@ -191,11 +191,11 @@ static int dds_decompress(Tex* t)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dds_transform(Tex* t, int new_flags)
|
||||
static int dds_transform(Tex* t, uint transforms)
|
||||
{
|
||||
const int dxt = t->flags & TEX_DXT, new_dxt = new_flags & TEX_DXT;
|
||||
const int is_dxt = t->flags & TEX_DXT, transform_dxt = transforms & TEX_DXT;
|
||||
// requesting decompression
|
||||
if(dxt && !new_dxt)
|
||||
if(is_dxt && transform_dxt)
|
||||
return dds_decompress(t);
|
||||
// both are DXT (unsupported; there are no flags we can change while
|
||||
// compressed) or requesting compression (not implemented) or
|
||||
@ -308,9 +308,12 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
static int dds_encode(const char* UNUSED(ext), Tex* UNUSED(t), u8** UNUSED(out), size_t* UNUSED(out_size), const char** UNUSED(perr_msg))
|
||||
static int dds_encode(const char* UNUSED(ext), Tex* UNUSED(t), DynArray* UNUSED(da), const char** UNUSED(perr_msg))
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
// note: do not return ERR_NOT_IMPLEMENTED et al. because that would
|
||||
// break tex_write (which assumes either this, 0 or errors are returned).
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(dds);
|
||||
|
@ -197,30 +197,37 @@ GLOBAL(void) src_prepare(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr pub; /* public fields */
|
||||
|
||||
JOCTET* buf;
|
||||
// jpeg-6b interface needs a memory buffer
|
||||
DynArray* da;
|
||||
} DstMgr;
|
||||
|
||||
typedef DstMgr* DstPtr;
|
||||
|
||||
#define OUTPUT_BUF_SIZE 16*KiB /* choose an efficiently writeable size */
|
||||
#define OUTPUT_BUF_SIZE 64*KiB /* choose an efficiently writeable size */
|
||||
|
||||
// note: can't call dst_empty_output_buffer from dst_init or vice versa
|
||||
// because only the former must advance da->pos.
|
||||
static void make_room_in_buffer(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
DynArray* da = dst->da;
|
||||
|
||||
void* start = da->base + da->cur_size;
|
||||
|
||||
if(da_set_size(da, da->cur_size+OUTPUT_BUF_SIZE) != 0)
|
||||
ERREXIT(cinfo, JERR_FILE_WRITE);
|
||||
|
||||
dst->pub.next_output_byte = (JOCTET*)start;
|
||||
dst->pub.free_in_buffer = OUTPUT_BUF_SIZE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize destination --- called by jpeg_start_compress
|
||||
* before any data is actually written.
|
||||
*/
|
||||
|
||||
METHODDEF(void) dst_init(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
|
||||
/* Allocate the output buffer --- it will be released when done with image */
|
||||
dst->buf = (JOCTET*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
OUTPUT_BUF_SIZE * sizeof(JOCTET));
|
||||
|
||||
dst->pub.next_output_byte = dst->buf;
|
||||
dst->pub.free_in_buffer = OUTPUT_BUF_SIZE;
|
||||
make_room_in_buffer(cinfo);
|
||||
}
|
||||
|
||||
|
||||
@ -232,34 +239,20 @@ METHODDEF(void) dst_init(j_compress_ptr cinfo)
|
||||
* reset the pointer & count to the start of the buffer, and return TRUE
|
||||
* indicating that the buffer has been dumped.
|
||||
*
|
||||
* In applications that need to be able to suspend compression due to output
|
||||
* overrun, a FALSE return indicates that the buffer cannot be emptied now.
|
||||
* In this situation, the compressor will return to its caller (possibly with
|
||||
* an indication that it has not accepted all the supplied scanlines). The
|
||||
* application should resume compression after it has made more room in the
|
||||
* output buffer. Note that there are substantial restrictions on the use of
|
||||
* suspension --- see the documentation.
|
||||
*
|
||||
* When suspending, the compressor will back up to a convenient restart point
|
||||
* (typically the start of the current MCU). next_output_byte & free_in_buffer
|
||||
* indicate where the restart point will be if the current call returns FALSE.
|
||||
* Data beyond this point will be regenerated after resumption, so do not
|
||||
* write it out when emptying the buffer externally.
|
||||
* <snip comments on suspended IO>
|
||||
*/
|
||||
|
||||
METHODDEF(boolean) dst_empty_output_buffer(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
DynArray* da = dst->da;
|
||||
|
||||
// writing out OUTPUT_BUF_SIZE-dst->pub.free_in_buffer bytes
|
||||
// sounds reasonable, but makes for broken output.
|
||||
// if(vfs_io(dst->hf, OUTPUT_BUF_SIZE, (void**)&dst->buf) != OUTPUT_BUF_SIZE)
|
||||
// ERREXIT(cinfo, JERR_FILE_WRITE);
|
||||
da->pos += OUTPUT_BUF_SIZE;
|
||||
|
||||
dst->pub.next_output_byte = dst->buf;
|
||||
dst->pub.free_in_buffer = OUTPUT_BUF_SIZE;
|
||||
make_room_in_buffer(cinfo);
|
||||
|
||||
return TRUE;
|
||||
return TRUE; // not suspended
|
||||
}
|
||||
|
||||
|
||||
@ -271,17 +264,13 @@ METHODDEF(boolean) dst_empty_output_buffer(j_compress_ptr cinfo)
|
||||
* application must deal with any cleanup that should happen even
|
||||
* for error exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void) dst_term(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
DynArray* da = dst->da;
|
||||
|
||||
// make sure any data left in the buffer is written out
|
||||
const size_t bytes_in_buf = OUTPUT_BUF_SIZE - dst->pub.free_in_buffer;
|
||||
// if(vfs_io(dst->hf, bytes_in_buf, (void**)&dst->buf) != (ssize_t)bytes_in_buf)
|
||||
// ERREXIT(cinfo, JERR_FILE_WRITE);
|
||||
|
||||
// flush file, if necessary.
|
||||
// account for nbytes left in buffer
|
||||
da->pos += OUTPUT_BUF_SIZE - dst->pub.free_in_buffer;
|
||||
}
|
||||
|
||||
|
||||
@ -291,7 +280,7 @@ METHODDEF(void) dst_term(j_compress_ptr cinfo)
|
||||
* for closing it after finishing compression.
|
||||
*/
|
||||
|
||||
GLOBAL(void) dst_prepare(j_compress_ptr cinfo)
|
||||
GLOBAL(void) dst_prepare(j_compress_ptr cinfo, DynArray* da)
|
||||
{
|
||||
/* The destination object is made permanent so that multiple JPEG images
|
||||
* can be written to the same file without re-executing jpeg_stdio_dest.
|
||||
@ -308,6 +297,7 @@ GLOBAL(void) dst_prepare(j_compress_ptr cinfo)
|
||||
dst->pub.init_destination = dst_init;
|
||||
dst->pub.empty_output_buffer = dst_empty_output_buffer;
|
||||
dst->pub.term_destination = dst_term;
|
||||
dst->da = da;
|
||||
}
|
||||
|
||||
|
||||
@ -396,7 +386,7 @@ JpgErrorMgr::JpgErrorMgr(j_common_ptr cinfo)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
static int jpg_transform(Tex* t, int new_flags)
|
||||
static int jpg_transform(Tex* UNUSED(t), uint UNUSED(transforms))
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
@ -457,7 +447,7 @@ static int jpg_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
return ERR_NO_MEM;
|
||||
|
||||
// read rows
|
||||
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, rows));
|
||||
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
|
||||
// could use cinfo->output_scanline to keep track of progress,
|
||||
// but we need to count lines_left anyway (paranoia).
|
||||
JSAMPARRAY row = (JSAMPARRAY)rows;
|
||||
@ -495,9 +485,9 @@ static int jpg_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
|
||||
static int jpg_encode_impl(Tex* t,
|
||||
jpeg_compress_struct* cinfo,
|
||||
RowArray& rows, const char** perr_msg)
|
||||
RowArray& rows, DynArray* da, const char** perr_msg)
|
||||
{
|
||||
dst_prepare(cinfo);
|
||||
dst_prepare(cinfo, da);
|
||||
|
||||
// describe image format
|
||||
// required:
|
||||
@ -514,12 +504,12 @@ static int jpg_encode_impl(Tex* t,
|
||||
jpeg_start_compress(cinfo, TRUE);
|
||||
|
||||
// if BGR, convert to RGB.
|
||||
const int bgr_transform = t->flags & TEX_BGR; // JPG is native RGB.
|
||||
WARN_ERR(tex_transform(t, bgr_transform));
|
||||
const int transform_bgr = t->flags & TEX_BGR; // JPG is native RGB.
|
||||
WARN_ERR(tex_transform(t, transform_bgr));
|
||||
|
||||
const size_t pitch = t->w * t->bpp / 8;
|
||||
u8* data = tex_get_data(t);
|
||||
RETURN_ERR(tex_codec_alloc_rows(data, t->h, pitch, TEX_TOP_DOWN, rows));
|
||||
RETURN_ERR(tex_codec_alloc_rows(data, t->h, pitch, t->flags, TEX_TOP_DOWN, rows));
|
||||
|
||||
|
||||
// could use cinfo->output_scanline to keep track of progress,
|
||||
@ -551,6 +541,7 @@ static int jpg_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
if(file[0] != 0xff || file[1] == 0xd8)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
// freed when ret is reached:
|
||||
// .. contains the JPEG decompression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
@ -572,7 +563,7 @@ fail:
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
int err = jpg_decode_impl(t, file, file_size, &cinfo, img_hm, rows, perr_msg);
|
||||
err = jpg_decode_impl(t, file, file_size, &cinfo, img_hm, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
@ -584,11 +575,12 @@ ret:
|
||||
|
||||
|
||||
// limitation: palette images aren't supported
|
||||
static int jpg_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
static int jpg_encode(const char* ext, Tex* t, DynArray* da, const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "jpg") && stricmp(ext, "jpeg"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
// freed when ret is reached:
|
||||
// .. contains the JPEG compression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
@ -596,9 +588,6 @@ static int jpg_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
// .. array of pointers to scanlines (see rationale above)
|
||||
RowArray rows = 0;
|
||||
|
||||
// freed when fail is reached:
|
||||
// (mem buffer)
|
||||
|
||||
JpgErrorMgr jerr((j_common_ptr)&cinfo);
|
||||
if(setjmp(jerr.call_site))
|
||||
{
|
||||
@ -609,14 +598,13 @@ fail:
|
||||
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
int err = jpg_encode_impl(t, &cinfo, rows, perr_msg);
|
||||
err = jpg_encode_impl(t, &cinfo, rows, da, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
ret:
|
||||
jpeg_destroy_compress(&cinfo); // releases a "good deal" of memory
|
||||
free(rows);
|
||||
// (free mem buffer)
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -42,30 +42,22 @@
|
||||
// pass data from PNG file in memory to libpng
|
||||
static void io_read(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
MemSource* ms = (MemSource*)png_ptr->io_ptr;
|
||||
|
||||
void* src = (u8*)(ms->p + ms->pos);
|
||||
|
||||
// make sure there's enough new data remaining in the buffer
|
||||
ms->pos += length;
|
||||
if(ms->pos > ms->size)
|
||||
png_error(png_ptr, "png_read: not enough data to satisfy request!");
|
||||
|
||||
memcpy(data, src, length);
|
||||
DynArray* da = (DynArray*)png_ptr->io_ptr;
|
||||
if(da_read(da, data, length) != 0)
|
||||
png_error(png_ptr, "io_read failed");
|
||||
}
|
||||
|
||||
|
||||
// write libpng output to PNG file
|
||||
static void io_write(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
void* p = (void*)data;
|
||||
Handle hf = *(Handle*)png_ptr->io_ptr;
|
||||
if(vfs_io(hf, length, &p) != (ssize_t)length)
|
||||
png_error(png_ptr, "png_write: !");
|
||||
DynArray* da = (DynArray*)png_ptr->io_ptr;
|
||||
if(da_append(da, data, length) != 0)
|
||||
png_error(png_ptr, "io_write failed");
|
||||
}
|
||||
|
||||
|
||||
static void io_flush(png_structp)
|
||||
static void io_flush(png_structp UNUSED(png_ptr))
|
||||
{
|
||||
}
|
||||
|
||||
@ -73,7 +65,7 @@ static void io_flush(png_structp)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static int png_transform(Tex* t, int new_flags)
|
||||
static int png_transform(Tex* UNUSED(t), uint UNUSED(transforms))
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
@ -88,8 +80,13 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
png_structp png_ptr, png_infop info_ptr,
|
||||
Handle& img_hm, RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
MemSource ms = { file, file_size, 0 };
|
||||
png_set_read_fn(png_ptr, &ms, io_read);
|
||||
DynArray da;
|
||||
da.base = file;
|
||||
da.max_size_pa = round_up(file_size, 4096);
|
||||
da.cur_size = file_size;
|
||||
da.pos = 0;
|
||||
da.prot = PROT_READ|PROT_WRITE;
|
||||
png_set_read_fn(png_ptr, &da, io_read);
|
||||
|
||||
// read header and determine format
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
@ -122,13 +119,13 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
if(!img)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
CHECK_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, rows));
|
||||
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, 0, rows));
|
||||
|
||||
png_read_image(png_ptr, (png_bytepp)rows);
|
||||
png_read_end(png_ptr, info_ptr);
|
||||
|
||||
// success; make sure all data was consumed.
|
||||
debug_assert(ms.p == file && ms.size == file_size && ms.pos == ms.size);
|
||||
debug_assert(da.base == file && da.cur_size == file_size && da.pos == da.cur_size);
|
||||
|
||||
// store image info
|
||||
// .. transparently switch handles - free the old (compressed)
|
||||
@ -149,13 +146,10 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
// "dtor / setjmp interaction" warning.
|
||||
static int png_encode_impl(Tex* t,
|
||||
png_structp png_ptr, png_infop info_ptr,
|
||||
RowArray& rows, const char** perr_msg)
|
||||
RowArray& rows, DynArray* da, const char** perr_msg)
|
||||
{
|
||||
UNUSED2(perr_msg); // we don't produce any error messages ATM.
|
||||
|
||||
const int png_transforms = (t->flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
|
||||
// PNG is native RGB.
|
||||
|
||||
const png_uint_32 w = t->w, h = t->h;
|
||||
const size_t pitch = w * t->bpp / 8;
|
||||
|
||||
@ -175,16 +169,16 @@ static int png_encode_impl(Tex* t,
|
||||
colour_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
hf = vfs_open(fn, FILE_WRITE|FILE_NO_AIO);
|
||||
CHECK_ERR(hf);
|
||||
png_set_write_fn(png_ptr, &hf, io_write, io_flush);
|
||||
*/
|
||||
|
||||
png_set_write_fn(png_ptr, da, io_write, io_flush);
|
||||
png_set_IHDR(png_ptr, info_ptr, w, h, 8, colour_type,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
u8* data = tex_get_data(t);
|
||||
CHECK_ERR(tex_codec_alloc_rows(data, h, pitch, TEX_TOP_DOWN, rows));
|
||||
RETURN_ERR(tex_codec_alloc_rows(data, h, pitch, t->flags, TEX_TOP_DOWN, rows));
|
||||
|
||||
// PNG is native RGB.
|
||||
const int png_transforms = (t->flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
|
||||
|
||||
png_set_rows(png_ptr, info_ptr, (png_bytepp)rows);
|
||||
png_write_png(png_ptr, info_ptr, png_transforms, 0);
|
||||
@ -202,6 +196,7 @@ static int png_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
if(*(u32*)file != FOURCC('\x89','P','N','G'))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
// freed when ret is reached:
|
||||
png_structp png_ptr = 0;
|
||||
png_infop info_ptr = 0;
|
||||
@ -221,13 +216,13 @@ static int png_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
// setup error handling
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
// libpng longjmp-ed here after an error, or code below failed.
|
||||
// libpng longjmps here after an error
|
||||
fail:
|
||||
mem_free_h(img_hm);
|
||||
goto ret;
|
||||
}
|
||||
|
||||
int err = png_decode_impl(t, file, file_size, png_ptr, info_ptr, img_hm, rows, perr_msg);
|
||||
err = png_decode_impl(t, file, file_size, png_ptr, info_ptr, img_hm, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
@ -240,19 +235,17 @@ ret:
|
||||
|
||||
|
||||
// limitation: palette images aren't supported
|
||||
static int png_encode(const char* ext, Tex* t, u8** out, size_t* UNUSED(out_size), const char** perr_msg)
|
||||
static int png_encode(const char* ext, Tex* t, DynArray* da, const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "png"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
// freed when ret is reached:
|
||||
png_structp png_ptr = 0;
|
||||
png_infop info_ptr = 0;
|
||||
RowArray rows = 0;
|
||||
|
||||
// free when fail is reached:
|
||||
// (mem buffer)
|
||||
|
||||
// allocate PNG structures; use default stderr and longjmp error handlers
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
|
||||
if(!png_ptr)
|
||||
@ -264,21 +257,20 @@ static int png_encode(const char* ext, Tex* t, u8** out, size_t* UNUSED(out_size
|
||||
// setup error handling
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
// libpng longjmps here after an error
|
||||
fail:
|
||||
// libjpg longjmp-ed here after an error, or code below failed.
|
||||
// currently no cleanup to be done.
|
||||
goto ret;
|
||||
}
|
||||
|
||||
int err = png_encode_impl(t, png_ptr, info_ptr, rows, perr_msg);
|
||||
err = png_encode_impl(t, png_ptr, info_ptr, rows, da, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
// shared cleanup
|
||||
ret:
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
free(rows);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ TgaHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
static int tga_transform(Tex* t, int new_flags)
|
||||
static int tga_transform(Tex* UNUSED(t), uint UNUSED(transforms))
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
@ -78,9 +78,7 @@ fail:
|
||||
|
||||
const u8 alpha_bits = desc & 0x0f;
|
||||
const int orientation = (desc & TGA_TOP_DOWN)? TEX_TOP_DOWN : TEX_BOTTOM_UP;
|
||||
u8* const img = file + hdr_size;
|
||||
const size_t img_size = w * h * bpp/8;
|
||||
|
||||
const size_t img_size = tex_img_size(t);
|
||||
|
||||
int flags = 0;
|
||||
if(alpha_bits != 0)
|
||||
@ -118,7 +116,7 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
static int tga_encode(const char* ext, Tex* t, DynArray* da, const char** UNUSED(perr_msg))
|
||||
{
|
||||
if(stricmp(ext, "tga"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
@ -130,11 +128,9 @@ static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
img_desc |= 8; // size of alpha channel
|
||||
TgaImgType img_type = (t->flags & TEX_GREY)? TGA_GREY : TGA_TRUE_COLOUR;
|
||||
|
||||
// transform
|
||||
int transforms = t->flags;
|
||||
transforms &= ~TEX_ORIENTATION; // no flip needed - we can set top-down bit.
|
||||
transforms ^= TEX_BGR; // TGA is native BGR.
|
||||
tex_transform(t, transforms);
|
||||
|
||||
const TgaHeader hdr =
|
||||
{
|
||||
@ -148,8 +144,8 @@ static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
t->bpp,
|
||||
img_desc
|
||||
};
|
||||
|
||||
return 0;
|
||||
const size_t hdr_size = sizeof(hdr);
|
||||
return tex_codec_write(t, transforms, &hdr, hdr_size, da);
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(tga);
|
||||
|
@ -242,12 +242,15 @@ int debug_is_pointer_bogus(const void* p)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// note: we don't check alignment because nothing can be assumed about a
|
||||
// pointer to the middle of a string and we mustn't reject valid pointers.
|
||||
// nor do we bother checking the address against known stack/heap areas
|
||||
// because that doesn't cover everything (e.g. DLLs, VirtualAlloc, etc.).
|
||||
// notes:
|
||||
// - we don't check alignment because nothing can be assumed about a
|
||||
// string pointer and we mustn't reject any actually valid pointers.
|
||||
// - nor do we bother checking the address against known stack/heap areas
|
||||
// because that doesn't cover everything (e.g. DLLs, VirtualAlloc).
|
||||
// - cannot use IsBadReadPtr because it accesses the mem
|
||||
// (false alarm for reserved address space).
|
||||
|
||||
return IsBadReadPtr(p, 1) != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -244,8 +244,8 @@ static int dll_list_add(const char* name)
|
||||
|
||||
// didn't fit; complain
|
||||
sprintf(dll_list_pos, "..."); // (room was reserved above)
|
||||
debug_warn("dll_list_add: not enough room");
|
||||
dll_list_pos = 0; // poison pill, prevent further calls
|
||||
debug_warn("dll_list_add: not enough room");
|
||||
return ERR_BUF_SIZE;
|
||||
}
|
||||
|
||||
@ -376,7 +376,7 @@ int win_get_gfx_info()
|
||||
|
||||
// ensures each OpenAL DLL is only listed once (even if present in several
|
||||
// directories on our search path).
|
||||
typedef std::set<std::string> DllSet;
|
||||
typedef std::set<std::string> StringSet;
|
||||
|
||||
// if this directory entry is an OpenAL DLL, add it to our list.
|
||||
// (matches "*oal.dll" and "*OpenAL*", as with OpenAL router's search)
|
||||
@ -385,7 +385,7 @@ typedef std::set<std::string> DllSet;
|
||||
// note: we need the full DLL path for dll_list_add but DirEnt only gives us
|
||||
// the name. for efficiency, we append this in a PathPackage allocated by
|
||||
// add_oal_dlls_in_dir.
|
||||
static void add_if_oal_dll(const DirEnt* ent, PathPackage* pp, DllSet* dlls)
|
||||
static void add_if_oal_dll(const DirEnt* ent, PathPackage* pp, StringSet* dlls)
|
||||
{
|
||||
const char* fn = ent->name;
|
||||
|
||||
@ -400,13 +400,13 @@ static void add_if_oal_dll(const DirEnt* ent, PathPackage* pp, DllSet* dlls)
|
||||
if(!oal && !openal)
|
||||
return;
|
||||
|
||||
// skip if already in DllSet (i.e. has already been dll_list_add-ed)
|
||||
std::pair<DllSet::iterator, bool> ret = dlls->insert(fn);
|
||||
// skip if already in StringSet (i.e. has already been dll_list_add-ed)
|
||||
std::pair<StringSet::iterator, bool> ret = dlls->insert(fn);
|
||||
if(!ret.second) // insert failed - element already there
|
||||
return;
|
||||
|
||||
WARN_ERR_RETURN(pp_append_file(pp, fn));
|
||||
dll_list_add(pp->path);
|
||||
WARN_ERR_RETURN(pp_append_file(pp, fn)); // todo4
|
||||
(void)dll_list_add(pp->path);
|
||||
}
|
||||
|
||||
|
||||
@ -416,7 +416,7 @@ static void add_if_oal_dll(const DirEnt* ent, PathPackage* pp, DllSet* dlls)
|
||||
// same name in the system directory.
|
||||
//
|
||||
// <dir>: no trailing.
|
||||
static void add_oal_dlls_in_dir(const char* dir, DllSet* dlls)
|
||||
static void add_oal_dlls_in_dir(const char* dir, StringSet* dlls)
|
||||
{
|
||||
int err;
|
||||
|
||||
@ -424,7 +424,7 @@ static void add_oal_dlls_in_dir(const char* dir, DllSet* dlls)
|
||||
(void)pp_set_dir(&pp, dir);
|
||||
|
||||
DirIterator d;
|
||||
WARN_ERR_RETURN(dir_open(dir, &d));
|
||||
WARN_ERR_RETURN(dir_open(dir, &d)); // TODO4
|
||||
|
||||
DirEnt ent;
|
||||
for(;;) // instead of while to avoid warning
|
||||
@ -432,11 +432,10 @@ static void add_oal_dlls_in_dir(const char* dir, DllSet* dlls)
|
||||
err = dir_next_ent(&d, &ent);
|
||||
if(err == ERR_DIR_END)
|
||||
break;
|
||||
debug_assert(err == 0);
|
||||
add_if_oal_dll(&ent, &pp, dlls);
|
||||
}
|
||||
|
||||
WARN_ERR(dir_close(&d));
|
||||
(void)dir_close(&d);
|
||||
}
|
||||
|
||||
|
||||
@ -483,7 +482,7 @@ int win_get_snd_info()
|
||||
// and store in snd_drv_ver string.
|
||||
dll_list_init(snd_drv_ver, SND_DRV_VER_LEN);
|
||||
dll_list_add(ds_drv_path);
|
||||
std::set<std::string> dlls; // ensures uniqueness
|
||||
StringSet dlls; // ensures uniqueness
|
||||
add_oal_dlls_in_dir(win_exe_dir, &dlls);
|
||||
add_oal_dlls_in_dir(win_sys_dir, &dlls);
|
||||
return 0;
|
||||
|
@ -615,12 +615,17 @@ int mprotect(void* addr, size_t len, int prot)
|
||||
const DWORD flNewProtect = win32_prot(prot);
|
||||
DWORD flOldProtect; // required by VirtualProtect
|
||||
BOOL ok = VirtualProtect(addr, len, flNewProtect, &flOldProtect);
|
||||
return ok? 0 : -1;
|
||||
if(!ok)
|
||||
{
|
||||
debug_warn("mprotect failed"); // todo4
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// called when flags & MAP_ANONYMOUS
|
||||
static int map_mem(void* start, size_t len, int prot, int flags, int fd, void** pp)
|
||||
static int mmap_mem(void* start, size_t len, int prot, int flags, int fd, void** pp)
|
||||
{
|
||||
// sanity checks. we don't care about these but enforce them to
|
||||
// ensure callers are compatible with mmap.
|
||||
@ -631,9 +636,27 @@ static int map_mem(void* start, size_t len, int prot, int flags, int fd, void**
|
||||
debug_assert(flags & MAP_PRIVATE);
|
||||
|
||||
// see explanation at MAP_NORESERVE definition.
|
||||
DWORD flAllocationType = (flags & MAP_NORESERVE)? MEM_RESERVE : MEM_COMMIT;
|
||||
bool want_commit = (prot != PROT_NONE && !(flags & MAP_NORESERVE));
|
||||
|
||||
if(!want_commit && start != 0 && flags & MAP_FIXED)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
BOOL ok = VirtualQuery(start, &mbi, len);
|
||||
debug_assert(ok); // todo4
|
||||
DWORD state = mbi.State;
|
||||
if(state == MEM_COMMIT)
|
||||
{
|
||||
ok = VirtualFree(start, len, MEM_DECOMMIT);
|
||||
debug_assert(ok);
|
||||
*pp = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD op = want_commit? MEM_COMMIT : MEM_RESERVE;
|
||||
|
||||
DWORD flProtect = win32_prot(prot);
|
||||
void* p = VirtualAlloc(start, len, flAllocationType, flProtect);
|
||||
void* p = VirtualAlloc(start, len, op, flProtect);
|
||||
if(!p)
|
||||
return ERR_NO_MEM;
|
||||
*pp = p;
|
||||
@ -645,7 +668,7 @@ static int map_mem(void* start, size_t len, int prot, int flags, int fd, void**
|
||||
// CreateFileMapping / MapViewOfFile. they only support read-only,
|
||||
// read/write and copy-on-write, so we dumb it down to that and later
|
||||
// set the correct (and more restrictive) permission via mprotect.
|
||||
static int map_file_access(int prot, int flags, DWORD& flProtect, DWORD& dwAccess)
|
||||
static int mmap_file_access(int prot, int flags, DWORD& flProtect, DWORD& dwAccess)
|
||||
{
|
||||
// assume read-only; other cases handled below.
|
||||
flProtect = PAGE_READONLY;
|
||||
@ -678,7 +701,7 @@ static int map_file_access(int prot, int flags, DWORD& flProtect, DWORD& dwAcces
|
||||
}
|
||||
|
||||
|
||||
static int map_file(void* start, size_t len, int prot, int flags,
|
||||
static int mmap_file(void* start, size_t len, int prot, int flags,
|
||||
int fd, off_t ofs, void** pp)
|
||||
{
|
||||
debug_assert(fd != -1); // handled by mmap_mem
|
||||
@ -698,7 +721,7 @@ static int map_file(void* start, size_t len, int prot, int flags,
|
||||
// MapViewOfFile. these are weaker than what PROT_* allows and
|
||||
// are augmented below by subsequently mprotect-ing.
|
||||
DWORD flProtect; DWORD dwAccess;
|
||||
RETURN_ERR(map_file_access(prot, flags, flProtect, dwAccess));
|
||||
RETURN_ERR(mmap_file_access(prot, flags, flProtect, dwAccess));
|
||||
|
||||
// enough foreplay; now actually map.
|
||||
const HANDLE hMap = CreateFileMapping(hFile, 0, flProtect, 0, 0, (LPCSTR)0);
|
||||
@ -718,7 +741,7 @@ static int map_file(void* start, size_t len, int prot, int flags,
|
||||
return ERR_NO_MEM;
|
||||
|
||||
// slap on correct (more restrictive) permissions.
|
||||
WARN_ERR(mprotect(p, len, prot));
|
||||
(void)mprotect(p, len, prot);
|
||||
|
||||
WIN_RESTORE_LAST_ERROR;
|
||||
*pp = p;
|
||||
@ -731,9 +754,9 @@ void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t ofs)
|
||||
void* p;
|
||||
int err;
|
||||
if(flags & MAP_ANONYMOUS)
|
||||
err = map_mem(start, len, prot, flags, fd, &p);
|
||||
err = mmap_mem(start, len, prot, flags, fd, &p);
|
||||
else
|
||||
err = map_file(start, len, prot, flags, fd, ofs, &p);
|
||||
err = mmap_file(start, len, prot, flags, fd, ofs, &p);
|
||||
if(err < 0)
|
||||
{
|
||||
WARN_ERR(err);
|
||||
@ -756,7 +779,7 @@ int munmap(void* start, size_t UNUSED(len))
|
||||
// both failed
|
||||
if(!ok)
|
||||
{
|
||||
debug_warn("munmap failed");
|
||||
debug_warn("munmap failed"); // todo4
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@ -925,6 +948,8 @@ long sysconf(int name)
|
||||
switch(name)
|
||||
{
|
||||
case _SC_PAGESIZE:
|
||||
// note: don't add _SC_PAGE_SIZE - they are different names but
|
||||
// have the same value.
|
||||
return page_size;
|
||||
|
||||
case _SC_PHYS_PAGES:
|
||||
|
@ -625,8 +625,10 @@ void Shutdown()
|
||||
|
||||
delete &g_Mouseover;
|
||||
delete &g_Selection;
|
||||
delete &g_BuildingPlacer;
|
||||
|
||||
delete &g_Pathfinder;
|
||||
|
||||
// Managed by CWorld
|
||||
// delete &g_EntityManager;
|
||||
|
||||
|
@ -169,8 +169,8 @@ void WriteScreenshot(const char* extension)
|
||||
// could fix via enumerating all files, but it's not worth it ATM.
|
||||
char fn[VFS_MAX_PATH];
|
||||
|
||||
const char* file_format_string = "screenshots/screenshot%04d.%s";
|
||||
// %04d -> always 4 digits, so sorting by filename works correctly.
|
||||
const char* file_format_string = "screenshots/screenshot%04d.%s";
|
||||
|
||||
static int next_num = 1;
|
||||
do
|
||||
|
@ -744,7 +744,7 @@ JSBool getGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv,
|
||||
// Activates the building placement cursor for placing a building.
|
||||
// params: templateName - the name of the entity to place.
|
||||
// returns: true if cursor was activated, false if cursor was already active.
|
||||
JSBool startPlacing( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval )
|
||||
JSBool startPlacing( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
|
||||
{
|
||||
CStrW name;
|
||||
if(argc == 0) {
|
||||
|
Loading…
Reference in New Issue
Block a user