mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-09-21 20:06:40 +00:00
# add CppDoc comments; prepare for automated testing
ia32: prepend CPUCap enum names and rdtsc with ia32 to avoid conflicts. move all self tests into separate headers as required for Cxxtest. adts: remove some dead code. add CppDoc comments to debug, lib (with heavy cleanup), tex, tex_codec, snd_mgr slight improvements to path tex: refactor; split out tex_decode and encode to allow self-test This was SVN commit r3911.
This commit is contained in:
@@ -29,7 +29,7 @@ void ColorActivateFastImpl()
|
||||
{
|
||||
}
|
||||
#if CPU_IA32
|
||||
else if (ia32_cap(SSE))
|
||||
else if (ia32_cap(IA32_CAP_SSE))
|
||||
{
|
||||
ConvertRGBColorTo4ub = sse_ConvertRGBColorTo4ub;
|
||||
}
|
||||
|
||||
@@ -22,195 +22,4 @@
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "adts.h"
|
||||
#include "posix.h"
|
||||
#include "lib/timer.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_ringbuf()
|
||||
{
|
||||
const size_t N = 49; // RingBuf capacity
|
||||
const int S = 100; // number of test items
|
||||
|
||||
// insert and remove immediately
|
||||
{
|
||||
RingBuf<int, N> buf;
|
||||
for(int i = 1; i < S; i++)
|
||||
{
|
||||
buf.push_back(i);
|
||||
TEST(buf.front() == i);
|
||||
buf.pop_front();
|
||||
}
|
||||
TEST(buf.size() == 0 && buf.empty());
|
||||
}
|
||||
|
||||
// fill buffer and overwrite old items
|
||||
{
|
||||
RingBuf<int, N> buf;
|
||||
for(int i = 1; i < S; i++)
|
||||
buf.push_back(i);
|
||||
TEST(buf.size() == N);
|
||||
int first = buf.front();
|
||||
TEST(first == (int)(S-1 -N +1));
|
||||
for(size_t i = 0; i < N; i++)
|
||||
{
|
||||
TEST(buf.front() == first);
|
||||
first++;
|
||||
buf.pop_front();
|
||||
}
|
||||
TEST(buf.size() == 0 && buf.empty());
|
||||
}
|
||||
|
||||
// randomized insert/remove; must behave as does std::deque
|
||||
{
|
||||
srand(1);
|
||||
RingBuf<int, N> buf;
|
||||
std::deque<int> deq;
|
||||
for(uint rep = 0; rep < 1000; rep++)
|
||||
{
|
||||
uint rnd_op = rand(0, 10);
|
||||
// 70% - insert
|
||||
if(rnd_op >= 3)
|
||||
{
|
||||
int item = rand();
|
||||
buf.push_back(item);
|
||||
|
||||
deq.push_back(item);
|
||||
int excess_items = (int)deq.size() - N;
|
||||
if(excess_items > 0)
|
||||
{
|
||||
for(int i = 0; i < excess_items; i++)
|
||||
{
|
||||
deq.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 30% - pop front (only if not empty)
|
||||
else if(!deq.empty())
|
||||
{
|
||||
buf.pop_front();
|
||||
deq.pop_front();
|
||||
}
|
||||
}
|
||||
TEST(buf.size() == deq.size());
|
||||
RingBuf<int, N>::iterator begin = buf.begin(), end = buf.end();
|
||||
TEST(equal(begin, end, deq.begin()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ensures all 3 variants of Landlord<> behave the same
|
||||
static void test_cache_removal()
|
||||
{
|
||||
Cache<int, int, Landlord_Naive> c1;
|
||||
Cache<int, int, Landlord_Naive, Divider_Recip> c1r;
|
||||
Cache<int, int, Landlord_Cached> c2;
|
||||
Cache<int, int, Landlord_Cached, Divider_Recip> c2r;
|
||||
Cache<int, int, Landlord_Lazy> c3;
|
||||
Cache<int, int, Landlord_Lazy, Divider_Recip> c3r;
|
||||
|
||||
#if defined(ENABLE_CACHE_POLICY_BENCHMARK) || 0
|
||||
// set max priority, to reduce interference while measuring.
|
||||
int old_policy; static sched_param old_param; // (static => 0-init)
|
||||
pthread_getschedparam(pthread_self(), &old_policy, &old_param);
|
||||
static sched_param max_param;
|
||||
max_param.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
pthread_setschedparam(pthread_self(), SCHED_FIFO, &max_param);
|
||||
|
||||
#define MEASURE(c, desc)\
|
||||
{\
|
||||
srand(1);\
|
||||
int cnt = 1;\
|
||||
TIMER_BEGIN(desc);\
|
||||
for(int i = 0; i < 30000; i++)\
|
||||
{\
|
||||
/* 70% add (random objects) */\
|
||||
bool add = rand(1,10) < 7;\
|
||||
if(add)\
|
||||
{\
|
||||
int key = cnt++;\
|
||||
int val = cnt++;\
|
||||
size_t size = (size_t)rand(1,100);\
|
||||
uint cost = (uint)rand(1,100);\
|
||||
c.add(key, val, size, cost);\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
size_t size;\
|
||||
int value;\
|
||||
c.remove_least_valuable(&value, &size);\
|
||||
}\
|
||||
}\
|
||||
TIMER_END(desc);\
|
||||
}
|
||||
MEASURE(c1, "naive")
|
||||
MEASURE(c1r, "naiverecip")
|
||||
MEASURE(c2, "cached")
|
||||
MEASURE(c2r, "cachedrecip")
|
||||
MEASURE(c3, "lazy")
|
||||
MEASURE(c3r, "lazyrecip")
|
||||
|
||||
// restore previous policy and priority.
|
||||
pthread_setschedparam(pthread_self(), old_policy, &old_param);
|
||||
exit(1134);
|
||||
#endif
|
||||
|
||||
|
||||
srand(1);
|
||||
int cnt = 1;
|
||||
for(int i = 0; i < 1000; i++)
|
||||
{
|
||||
// 70% add (random objects)
|
||||
bool add = rand(1,10) < 7;
|
||||
if(add)
|
||||
{
|
||||
int key = cnt++;
|
||||
int val = cnt++;
|
||||
size_t size = (size_t)rand(1,100);
|
||||
uint cost = (uint)rand(1,100);
|
||||
c1.add(key, val, size, cost);
|
||||
c2.add(key, val, size, cost);
|
||||
c3.add(key, val, size, cost);
|
||||
}
|
||||
// 30% delete - make sure "least valuable" was same for all
|
||||
else
|
||||
{
|
||||
size_t size1, size2, size3;
|
||||
int value1, value2, value3;
|
||||
bool removed1, removed2, removed3;
|
||||
removed1 = c1.remove_least_valuable(&value1, &size1);
|
||||
removed2 = c2.remove_least_valuable(&value2, &size2);
|
||||
removed3 = c3.remove_least_valuable(&value3, &size3);
|
||||
TEST(removed1 == removed2);
|
||||
TEST(removed2 == removed3);
|
||||
if (removed1)
|
||||
{
|
||||
TEST(size1 == size2);
|
||||
TEST(value1 == value2);
|
||||
TEST(size2 == size3);
|
||||
TEST(value2 == value3);
|
||||
}
|
||||
} // else
|
||||
} // for i
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_ringbuf();
|
||||
test_cache_removal();
|
||||
}
|
||||
|
||||
SELF_TEST_REGISTER;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -1307,158 +1307,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// expansible hash table (linear probing)
|
||||
//
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// from VFS, not currently needed
|
||||
|
||||
#if 0
|
||||
template<class T> class StringMap
|
||||
{
|
||||
public:
|
||||
|
||||
T* add(const char* fn, T& t)
|
||||
{
|
||||
const FnHash fn_hash = fnv_hash(fn);
|
||||
|
||||
t.name = fn;
|
||||
|
||||
std::pair<FnHash, T> item = std::make_pair(fn_hash, t);
|
||||
std::pair<MapIt, bool> res;
|
||||
res = map.insert(item);
|
||||
|
||||
if(!res.second)
|
||||
{
|
||||
debug_warn("add: already in container");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// return address of user data (T) inserted into container.
|
||||
return &((res.first)->second);
|
||||
}
|
||||
|
||||
T* find(const char* fn)
|
||||
{
|
||||
const FnHash fn_hash = fnv_hash(fn);
|
||||
MapIt it = map.find(fn_hash);
|
||||
// O(log(size))
|
||||
if(it == map.end())
|
||||
return 0;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return map.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
map.clear();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
typedef std::map<FnHash, T> Map;
|
||||
typedef typename Map::iterator MapIt;
|
||||
Map map;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator()
|
||||
{}
|
||||
iterator(typename StringMap<T>::MapIt _it)
|
||||
{ it = _it; }
|
||||
T& operator*() const
|
||||
{ return it->second; }
|
||||
T* operator->() const
|
||||
{ return &**this; }
|
||||
iterator& operator++() // pre
|
||||
{ ++it; return (*this); }
|
||||
bool operator==(const iterator& rhs) const
|
||||
{ return it == rhs.it; }
|
||||
bool operator!=(const iterator& rhs) const
|
||||
{ return !(*this == rhs); }
|
||||
protected:
|
||||
typename StringMap<T>::MapIt it;
|
||||
};
|
||||
|
||||
iterator begin()
|
||||
{ return iterator(map.begin()); }
|
||||
|
||||
iterator end()
|
||||
{ return iterator(map.end()); }
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<class Key, class Data> class PriMap
|
||||
{
|
||||
public:
|
||||
|
||||
int add(Key key, uint pri, Data& data)
|
||||
{
|
||||
Item item = std::make_pair(pri, data);
|
||||
MapEntry ent = std::make_pair(key, item);
|
||||
std::pair<MapIt, bool> ret;
|
||||
ret = map.insert(ent);
|
||||
// already in map
|
||||
if(!ret.second)
|
||||
{
|
||||
MapIt it = ret.first;
|
||||
Item item = it->second;
|
||||
const uint old_pri = item.first;
|
||||
Data& old_data = item.second;
|
||||
|
||||
// new data is of higher priority; replace older data
|
||||
if(old_pri <= pri)
|
||||
{
|
||||
old_data = data;
|
||||
return 0;
|
||||
}
|
||||
// new data is of lower priority; don't add
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Data* find(Key key)
|
||||
{
|
||||
MapIt it = map.find(key);
|
||||
if(it == map.end())
|
||||
return 0;
|
||||
|
||||
return &it->second.second;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
map.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::pair<uint, Data> Item;
|
||||
typedef std::pair<Key, Item> MapEntry;
|
||||
typedef std::map<Key, Item> Map;
|
||||
typedef typename Map::iterator MapIt;
|
||||
Map map;
|
||||
};
|
||||
#endif // #if 0
|
||||
|
||||
|
||||
|
||||
#endif // #ifndef ADTS_H__
|
||||
|
||||
@@ -862,62 +862,3 @@ void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p)
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_da()
|
||||
{
|
||||
DynArray da;
|
||||
|
||||
// basic test of functionality (not really meaningful)
|
||||
TEST(da_alloc(&da, 1000) == 0);
|
||||
TEST(da_set_size(&da, 1000) == 0);
|
||||
TEST(da_set_prot(&da, PROT_NONE) == 0);
|
||||
TEST(da_free(&da) == 0);
|
||||
|
||||
// test wrapping existing mem blocks for use with da_read
|
||||
u8 data[4] = { 0x12, 0x34, 0x56, 0x78 };
|
||||
TEST(da_wrap_fixed(&da, data, sizeof(data)) == 0);
|
||||
u8 buf[4];
|
||||
TEST(da_read(&da, buf, 4) == 0); // success
|
||||
TEST(read_le32(buf) == 0x78563412); // read correct value
|
||||
TEST(da_read(&da, buf, 1) < 0); // no more data left
|
||||
TEST(da_free(&da) == 0);
|
||||
}
|
||||
|
||||
static void test_expand()
|
||||
{
|
||||
}
|
||||
|
||||
static void test_matrix()
|
||||
{
|
||||
// not much we can do here; allocate a matrix, write to it and
|
||||
// make sure it can be freed.
|
||||
// (note: can't check memory layout because "matrix" is int** -
|
||||
// array of pointers. the matrix interface doesn't guarantee
|
||||
// that data comes in row-major order after the row pointers)
|
||||
int** m = (int**)matrix_alloc(3, 3, sizeof(int));
|
||||
m[0][0] = 1;
|
||||
m[0][1] = 2;
|
||||
m[1][0] = 3;
|
||||
m[2][2] = 4;
|
||||
matrix_free((void**)m);
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_da();
|
||||
test_expand();
|
||||
test_matrix();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -540,8 +540,6 @@ ErrorReaction debug_display_error(const wchar_t* description,
|
||||
}
|
||||
|
||||
|
||||
// notify the user that an assertion failed; displays a stack trace with
|
||||
// local variables.
|
||||
ErrorReaction debug_assert_failed(const char* expr,
|
||||
const char* file, int line, const char* func)
|
||||
{
|
||||
@@ -561,7 +559,7 @@ ErrorReaction debug_assert_failed(const char* expr,
|
||||
uint skip = 1; void* context = 0;
|
||||
wchar_t buf[400];
|
||||
swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed at %hs:%d (%hs): \"%hs\"", fn_only, line, func, expr);
|
||||
return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, fn_only,line);
|
||||
return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip, context, fn_only, line);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+280
-161
@@ -30,9 +30,7 @@
|
||||
# include "sysdep/unix/udbg.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
[KEEP IN SYNC WITH WIKI]
|
||||
/**
|
||||
|
||||
overview
|
||||
--------
|
||||
@@ -70,31 +68,41 @@ motivation for this code is as follows:
|
||||
* mostly pertaining to Release mode - e.g. symbols cannot be resolved
|
||||
even if debug information is present and assert dialogs are useless.
|
||||
|
||||
*/
|
||||
**/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// debug memory allocator
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// check heap integrity (independently of mmgr).
|
||||
// errors are reported by the CRT or via debug_display_error.
|
||||
/**
|
||||
* check heap integrity (independently of mmgr).
|
||||
* errors are reported by the CRT or via debug_display_error.
|
||||
**/
|
||||
extern void debug_heap_check(void);
|
||||
|
||||
enum DebugHeapChecks
|
||||
{
|
||||
// no automatic checks. (default)
|
||||
/**
|
||||
* no automatic checks. (default)
|
||||
**/
|
||||
DEBUG_HEAP_NONE = 0,
|
||||
|
||||
// basic automatic checks when deallocating.
|
||||
/**
|
||||
* basic automatic checks when deallocating.
|
||||
**/
|
||||
DEBUG_HEAP_NORMAL = 1,
|
||||
|
||||
// all automatic checks on every memory API call. this is really
|
||||
// slow (x100), but reports errors closer to where they occurred.
|
||||
/**
|
||||
* all automatic checks on every memory API call. this is really
|
||||
* slow (x100), but reports errors closer to where they occurred.
|
||||
**/
|
||||
DEBUG_HEAP_ALL = 2
|
||||
};
|
||||
|
||||
// call at any time; from then on, the specified checks will be performed.
|
||||
// if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing.
|
||||
/**
|
||||
* call at any time; from then on, the specified checks will be performed.
|
||||
* if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing.
|
||||
**/
|
||||
extern void debug_heap_enable(DebugHeapChecks what);
|
||||
|
||||
|
||||
@@ -102,25 +110,27 @@ extern void debug_heap_enable(DebugHeapChecks what);
|
||||
// debug_assert
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// rationale: we call this "debug_assert" instead of "assert" for the
|
||||
// following reasons:
|
||||
// - consistency (everything here is prefixed with debug_) and
|
||||
// - to avoid inadvertent use of the much less helpful built-in CRT assert.
|
||||
// if we were to override assert, it would be difficult to tell whether
|
||||
// user source has included <assert.h> (possibly indirectly via other
|
||||
// headers) and thereby stomped on our definition.
|
||||
|
||||
// make sure the expression <expr> evaluates to non-zero. used to validate
|
||||
// invariants in the program during development and thus gives a
|
||||
// very helpful warning if something isn't going as expected.
|
||||
// sprinkle these liberally throughout your code!
|
||||
//
|
||||
// recommended use is debug_assert(expression && "descriptive string") -
|
||||
// the string can pass more information about the problem on to whomever
|
||||
// is seeing the error.
|
||||
//
|
||||
// rationale: 0x55 and 0xAA are distinctive values and thus
|
||||
// help debug the symbol engine.
|
||||
/**
|
||||
* make sure the expression <expr> evaluates to non-zero. used to validate
|
||||
* invariants in the program during development and thus gives a
|
||||
* very helpful warning if something isn't going as expected.
|
||||
* sprinkle these liberally throughout your code!
|
||||
*
|
||||
* recommended use is debug_assert(expression && "descriptive string") -
|
||||
* the string can pass more information about the problem on to whomever
|
||||
* is seeing the error.
|
||||
*
|
||||
* rationale: we call this "debug_assert" instead of "assert" for the
|
||||
* following reasons:
|
||||
* - consistency (everything here is prefixed with debug_) and
|
||||
* - to avoid inadvertent use of the much less helpful built-in CRT assert.
|
||||
* if we were to override assert, it would be difficult to tell whether
|
||||
* user source has included <assert.h> (possibly indirectly via other
|
||||
* headers) and thereby stomped on our definition.
|
||||
*
|
||||
* implementation rationale: 0x55 and 0xAA are distinctive values and
|
||||
* thus help debug the symbol engine.
|
||||
**/
|
||||
#define debug_assert(expr) \
|
||||
STMT(\
|
||||
static unsigned char suppress__ = 0x55;\
|
||||
@@ -141,19 +151,22 @@ STMT(\
|
||||
}\
|
||||
)
|
||||
|
||||
|
||||
// show a dialog to make sure unexpected states in the program are noticed.
|
||||
// this is less error-prone than "debug_assert(0 && "text");" and avoids
|
||||
// "conditional expression is constant" warnings. we'd really like to
|
||||
// completely eliminate the problem; replacing 0 literals with extern
|
||||
// volatile variables fools VC7 but isn't guaranteed to be free of overhead.
|
||||
// we therefore just squelch the warning (unfortunately non-portable).
|
||||
/**
|
||||
* show a dialog to make sure unexpected states in the program are noticed.
|
||||
* this is less error-prone than "debug_assert(0 && "text");" and avoids
|
||||
* "conditional expression is constant" warnings. we'd really like to
|
||||
* completely eliminate the problem; replacing 0 literals with extern
|
||||
* volatile variables fools VC7 but isn't guaranteed to be free of overhead.
|
||||
* we therefore just squelch the warning (unfortunately non-portable).
|
||||
**/
|
||||
#define debug_warn(str) debug_assert((str) && 0)
|
||||
|
||||
|
||||
// if (LibError)err indicates an function failed, display the error dialog.
|
||||
// used by CHECK_ERR et al., which wrap function calls and automatically
|
||||
// warn user and return to caller.
|
||||
/**
|
||||
* if (LibError)err indicates an function failed, display the error dialog.
|
||||
* used by CHECK_ERR et al., which wrap function calls and automatically
|
||||
* warn user and return to caller.
|
||||
**/
|
||||
#define DEBUG_WARN_ERR(err)\
|
||||
STMT(\
|
||||
static unsigned char suppress__ = 0x55;\
|
||||
@@ -175,12 +188,28 @@ STMT(\
|
||||
)
|
||||
|
||||
|
||||
// called when an assertion has failed; notifies the user via debug_display_error.
|
||||
/**
|
||||
* called when a debug_assert fails;
|
||||
* notifies the user via debug_display_error.
|
||||
*
|
||||
* @param assert_expr the expression that failed; typically passed as
|
||||
* #expr in the assert macro.
|
||||
* @param file, line source file name and line number of the spot that failed
|
||||
* @param func name of the function containing it
|
||||
* @return ErrorReaction (user's choice: continue running or stop?)
|
||||
**/
|
||||
extern ErrorReaction debug_assert_failed(const char* assert_expr,
|
||||
const char* file, int line, const char* func);
|
||||
|
||||
// called when a lib function wrapped in DEBUG_WARN_ERR failed;
|
||||
// notifies the user via debug_display_error.
|
||||
/**
|
||||
* called when a DEBUG_WARN_ERR indicates an error occurred;
|
||||
* notifies the user via debug_display_error.
|
||||
*
|
||||
* @param err LibError value indicating the error that occurred
|
||||
* @param file, line source file name and line number of the spot that failed
|
||||
* @param func name of the function containing it
|
||||
* @return ErrorReaction (user's choice: continue running or stop?)
|
||||
**/
|
||||
extern ErrorReaction debug_warn_err(LibError err,
|
||||
const char* file, int line, const char* func);
|
||||
|
||||
@@ -189,44 +218,65 @@ extern ErrorReaction debug_warn_err(LibError err,
|
||||
// output
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// write a formatted string to the debug channel, subject to filtering
|
||||
// (see below). implemented via debug_puts - see performance note there.
|
||||
/**
|
||||
* write a formatted string to the debug channel, subject to filtering
|
||||
* (see below). implemented via debug_puts - see performance note there.
|
||||
*
|
||||
* @param format string and varargs; see printf.
|
||||
**/
|
||||
extern void debug_printf(const char* fmt, ...);
|
||||
// note: this merely converts to a MBS and calls debug_printf.
|
||||
|
||||
/// note: this merely converts to a MBS and calls debug_printf.
|
||||
extern void debug_wprintf(const wchar_t* fmt, ...);
|
||||
|
||||
|
||||
// translates and displays the given strings in a dialog.
|
||||
// this is typically only used when debug_display_error has failed or
|
||||
// is unavailable because that function is much more capable.
|
||||
// implemented via sys_display_msgw; see documentation there.
|
||||
/**
|
||||
* translates and displays the given strings in a dialog.
|
||||
* this is typically only used when debug_display_error has failed or
|
||||
* is unavailable because that function is much more capable.
|
||||
* implemented via sys_display_msgw; see documentation there.
|
||||
**/
|
||||
extern void debug_display_msgw(const wchar_t* caption, const wchar_t* msg);
|
||||
|
||||
|
||||
/// flags to customize debug_display_error behavior
|
||||
enum DisplayErrorFlags
|
||||
{
|
||||
// allow the suppress button (requires calling via macro that
|
||||
// maintains a 'suppress' bool; see debug_assert)
|
||||
/**
|
||||
* allow the suppress button (requires calling via macro that
|
||||
* maintains a 'suppress' bool; see debug_assert)
|
||||
**/
|
||||
DE_ALLOW_SUPPRESS = 1,
|
||||
|
||||
// disallow the continue button. used e.g. if an exception is fatal.
|
||||
/**
|
||||
* disallow the continue button. used e.g. if an exception is fatal.
|
||||
**/
|
||||
DE_NO_CONTINUE = 2,
|
||||
|
||||
// do not trigger a breakpoint inside debug_display_error; caller
|
||||
// will take care of this if ER_BREAK is returned. this is so that the
|
||||
// debugger can jump directly into the offending function.
|
||||
/**
|
||||
* do not trigger a breakpoint inside debug_display_error; caller
|
||||
* will take care of this if ER_BREAK is returned. this is so that the
|
||||
* debugger can jump directly into the offending function.
|
||||
**/
|
||||
DE_MANUAL_BREAK = 4
|
||||
};
|
||||
|
||||
// display the error dialog. shows <description> along with a stack trace.
|
||||
// context and skip are as with debug_dump_stack.
|
||||
// flags: see DisplayErrorFlags. file and line indicate where the error
|
||||
// occurred and are typically passed as __FILE__, __LINE__.
|
||||
/**
|
||||
* display an error dialog with a message and stack trace.
|
||||
*
|
||||
* @param description text to show.
|
||||
* @param flags: see DisplayErrorFlags.
|
||||
* @param context, skip: see debug_dump_stack.
|
||||
* @param file, line: location of the error (typically passed as
|
||||
* __FILE__, __LINE__ from a macro)
|
||||
* @return ErrorReaction (user's choice: continue running or stop?)
|
||||
**/
|
||||
extern ErrorReaction debug_display_error(const wchar_t* description,
|
||||
int flags, uint skip, void* context, const char* file, int line);
|
||||
|
||||
// convenience version, in case the advanced parameters aren't needed.
|
||||
// macro instead of providing overload/default values for C compatibility.
|
||||
/**
|
||||
* convenience version, in case the advanced parameters aren't needed.
|
||||
* macro instead of providing overload/default values for C compatibility.
|
||||
**/
|
||||
#define DISPLAY_ERROR(text) debug_display_error(text, 0, 0,0, __FILE__,__LINE__)
|
||||
|
||||
|
||||
@@ -234,41 +284,55 @@ extern ErrorReaction debug_display_error(const wchar_t* description,
|
||||
// filtering
|
||||
//
|
||||
|
||||
// debug output is very useful, but "too much of a good thing can kill you".
|
||||
// we don't want to require different LOGn() macros that are enabled
|
||||
// depending on "debug level", because changing that entails lengthy
|
||||
// compiles and it's too coarse-grained. instead, we require all
|
||||
// strings to start with "tag_string:" (exact case and no quotes;
|
||||
// the alphanumeric-only <tag_string> identifies output type).
|
||||
// they are then subject to filtering: only if the tag has been
|
||||
// "added" via debug_filter_add is the appendant string displayed.
|
||||
//
|
||||
// this approach is easiest to implement and is fine because we control
|
||||
// all logging code. LIMODS falls from consideration since it's not
|
||||
// portable and too complex.
|
||||
//
|
||||
// notes:
|
||||
// - filter changes only affect subsequent debug_*printf calls;
|
||||
// output that didn't pass the filter is permanently discarded.
|
||||
// - strings not starting with a tag are always displayed.
|
||||
// - debug_filter_* can be called at any time and from the debugger.
|
||||
/**
|
||||
* debug output is very useful, but "too much of a good thing can kill you".
|
||||
* we don't want to require different LOGn() macros that are enabled
|
||||
* depending on "debug level", because changing that entails lengthy
|
||||
* compiles and it's too coarse-grained. instead, we require all
|
||||
* strings to start with "tag_string:" (exact case and no quotes;
|
||||
* the alphanumeric-only <tag_string> identifies output type).
|
||||
* they are then subject to filtering: only if the tag has been
|
||||
* "added" via debug_filter_add is the appendant string displayed.
|
||||
*
|
||||
* this approach is easiest to implement and is fine because we control
|
||||
* all logging code. LIMODS falls from consideration since it's not
|
||||
* portable and too complex.
|
||||
*
|
||||
* notes:
|
||||
* - filter changes only affect subsequent debug_*printf calls;
|
||||
* output that didn't pass the filter is permanently discarded.
|
||||
* - strings not starting with a tag are always displayed.
|
||||
* - debug_filter_* can be called at any time and from the debugger.
|
||||
|
||||
// in future, allow output with the given tag to proceed.
|
||||
// no effect if already added.
|
||||
* in future, allow output with the given tag to proceed.
|
||||
* no effect if already added.
|
||||
**/
|
||||
extern void debug_filter_add(const char* tag);
|
||||
// in future, discard output with the given tag.
|
||||
// no effect if not currently added.
|
||||
|
||||
/**
|
||||
* in future, discard output with the given tag.
|
||||
* no effect if not currently added.
|
||||
**/
|
||||
extern void debug_filter_remove(const char* tag);
|
||||
// clear all filter state; equivalent to debug_filter_remove for
|
||||
// each tag that was debug_filter_add-ed.
|
||||
|
||||
/**
|
||||
* clear all filter state; equivalent to debug_filter_remove for
|
||||
* each tag that was debug_filter_add-ed.
|
||||
**/
|
||||
extern void debug_filter_clear();
|
||||
|
||||
|
||||
// write to memory buffer (fast)
|
||||
// used for "last activity" reporting in the crashlog.
|
||||
/**
|
||||
* write to memory buffer (fast)
|
||||
* used for "last activity" reporting in the crashlog.
|
||||
*
|
||||
* @param format string and varags; see printf.
|
||||
**/
|
||||
extern void debug_wprintf_mem(const wchar_t* fmt, ...);
|
||||
|
||||
// write all logs and <text> out to crashlog.txt (unicode format).
|
||||
/**
|
||||
* write all logs and <text> out to crashlog.txt (unicode format).
|
||||
**/
|
||||
extern LibError debug_write_crashlog(const wchar_t* text);
|
||||
|
||||
|
||||
@@ -276,41 +340,52 @@ extern LibError debug_write_crashlog(const wchar_t* text);
|
||||
// breakpoints
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// trigger a breakpoint when reached/"called".
|
||||
// defined as a macro by the platform-specific header above; this allows
|
||||
// breaking directly into the target function, instead of one frame
|
||||
// below it as with a conventional call-based implementation.
|
||||
//#define debug_break()
|
||||
/**
|
||||
* trigger a breakpoint when reached/"called".
|
||||
* defined as a macro by the platform-specific header above; this allows
|
||||
* breaking directly into the target function, instead of one frame
|
||||
* below it as with a conventional call-based implementation.
|
||||
**/
|
||||
//#define debug_break() // not defined here; see above
|
||||
|
||||
|
||||
// sometimes mmgr's 'fences' (making sure padding before and after the
|
||||
// allocation remains intact) aren't enough to catch hard-to-find
|
||||
// memory corruption bugs. another tool is to trigger a debug exception
|
||||
// when the later to be corrupted variable is accessed; the problem should
|
||||
// then become apparent.
|
||||
// the VC++ IDE provides such 'breakpoints', but can only detect write access.
|
||||
// additionally, it can't resolve symbols in Release mode (where this would
|
||||
// be most useful), so we provide a breakpoint API.
|
||||
/**
|
||||
* sometimes mmgr's 'fences' (making sure padding before and after the
|
||||
* allocation remains intact) aren't enough to catch hard-to-find
|
||||
* memory corruption bugs. another tool is to trigger a debug exception
|
||||
* when the later to be corrupted variable is accessed; the problem should
|
||||
* then become apparent.
|
||||
* the VC++ IDE provides such 'breakpoints', but can only detect write access.
|
||||
* additionally, it can't resolve symbols in Release mode (where this would
|
||||
* be most useful), so we provide a breakpoint API.
|
||||
|
||||
// (values chosen to match IA-32 bit defs, so compiler can optimize.
|
||||
// this isn't required, it'll work regardless.)
|
||||
* (values chosen to match IA-32 bit defs, so compiler can optimize.
|
||||
* this isn't required; it'll work regardless.)
|
||||
**/
|
||||
enum DbgBreakType
|
||||
{
|
||||
DBG_BREAK_CODE = 0, // execute
|
||||
DBG_BREAK_DATA_WRITE = 1, // write
|
||||
DBG_BREAK_DATA = 3 // read or write
|
||||
DBG_BREAK_CODE = 0, /// execute
|
||||
DBG_BREAK_DATA_WRITE = 1, /// write
|
||||
DBG_BREAK_DATA = 3 /// read or write
|
||||
};
|
||||
|
||||
// arrange for a debug exception to be raised when <addr> is accessed
|
||||
// according to <type>.
|
||||
// for simplicity, the length (range of bytes to be checked) is derived
|
||||
// from addr's alignment, and is typically 1 machine word.
|
||||
// breakpoints are a limited resource (4 on IA-32); if none are
|
||||
// available, we return ERR_LIMIT.
|
||||
/**
|
||||
* arrange for a debug exception to be raised when the
|
||||
* indicated memory is accessed.
|
||||
*
|
||||
* @param addr memory address
|
||||
* for simplicity, the length (range of bytes to be checked) is derived
|
||||
* from addr's alignment, and is typically 1 machine word.
|
||||
* @param type the type of access to watch for (see DbgBreakType)
|
||||
* @return LibError; ERR_LIMIT if no more breakpoints are available
|
||||
* (they are a limited resource - only 4 on IA-32).
|
||||
**/
|
||||
extern LibError debug_set_break(void* addr, DbgBreakType type);
|
||||
|
||||
// remove all breakpoints that were set by debug_set_break.
|
||||
// important, since these are a limited resource.
|
||||
/**
|
||||
* remove all breakpoints that were set by debug_set_break.
|
||||
* important, since these are a limited resource.
|
||||
**/
|
||||
extern LibError debug_remove_all_breaks();
|
||||
|
||||
|
||||
@@ -318,28 +393,48 @@ extern LibError debug_remove_all_breaks();
|
||||
// symbol access
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// maximum number of characters (including trailing \0) written to
|
||||
// user's buffers by debug_resolve_symbol.
|
||||
/**
|
||||
* maximum number of characters (including trailing \0) written to
|
||||
* user's buffers by debug_resolve_symbol.
|
||||
**/
|
||||
const size_t DBG_SYMBOL_LEN = 1000;
|
||||
const size_t DBG_FILE_LEN = 100;
|
||||
|
||||
// read and return symbol information for the given address. all of the
|
||||
// output parameters are optional; we pass back as much information as is
|
||||
// available and desired. return 0 iff any information was successfully
|
||||
// retrieved and stored.
|
||||
// sym_name and file must hold at least the number of chars above;
|
||||
// file is the base name only, not path (see rationale in wdbg_sym).
|
||||
// the PDB implementation is rather slow (~500us).
|
||||
/**
|
||||
* read and return symbol information for the given address.
|
||||
*
|
||||
* NOTE: the PDB implementation is rather slow (~500us).
|
||||
*
|
||||
* @param ptr_of_interest address of symbol (e.g. function, variable)
|
||||
* @param sym_name optional out; size >= DBG_SYMBOL_LEN chars;
|
||||
* receives symbol name returned via debug info.
|
||||
* @param file optional out; size >= DBG_FILE_LEN chars; receives
|
||||
* base name only (no path; see rationale in wdbg_sym) of
|
||||
* source file containing the symbol.
|
||||
* @param line optional out; receives source file line number of symbol.
|
||||
*
|
||||
* note: all of the output parameters are optional; we pass back as much
|
||||
* information as is available and desired.
|
||||
* @return LibError; ERR_OK iff any information was successfully
|
||||
* retrieved and stored.
|
||||
**/
|
||||
extern LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line);
|
||||
|
||||
// write a complete stack trace (including values of local variables) into
|
||||
// the specified buffer. if <context> is nonzero, it is assumed to be a
|
||||
// platform-specific representation of execution state (e.g. Win32 CONTEXT)
|
||||
// and tracing starts there; this is useful for exceptions.
|
||||
// otherwise, tracing starts at the current stack position, and the given
|
||||
// number of stack frames (i.e. functions) above the caller are skipped.
|
||||
// this prevents functions like debug_assert_failed from
|
||||
// cluttering up the trace. returns the buffer for convenience.
|
||||
/**
|
||||
* write a complete stack trace (including values of local variables) into
|
||||
* the specified buffer.
|
||||
*
|
||||
* @param buf target buffer
|
||||
* @param max_chars of buffer (should be several thousand)
|
||||
* @param skip number of stack frames (i.e. functions on call stack) to skip.
|
||||
* this prevents error-reporting functions like debug_assert_failed from
|
||||
* cluttering up the trace.
|
||||
* @param context platform-specific representation of execution state
|
||||
* (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful
|
||||
* for exceptions. otherwise, tracing starts from the current call stack.
|
||||
* @return buf for convenience; writes an error string into it if
|
||||
* something goes wrong.
|
||||
**/
|
||||
extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* context);
|
||||
|
||||
|
||||
@@ -347,50 +442,74 @@ extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip
|
||||
// helper functions (used by implementation)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// [system-dependent] write a string to the debug channel.
|
||||
// this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString
|
||||
// (entails context switch), otherwise stdout+fflush (waits for IO).
|
||||
/**
|
||||
* [system-dependent] write a string to the debug channel.
|
||||
* this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString
|
||||
* (entails context switch), otherwise stdout+fflush (waits for IO).
|
||||
**/
|
||||
extern void debug_puts(const char* text);
|
||||
|
||||
// abstraction of all STL iterators used by debug_stl.
|
||||
/// abstraction of all STL iterators used by debug_stl.
|
||||
typedef const u8* (*DebugIterator)(void* internal, size_t el_size);
|
||||
|
||||
// return address of the Nth function on the call stack.
|
||||
// if <context> is nonzero, it is assumed to be a platform-specific
|
||||
// representation of execution state (e.g. Win32 CONTEXT) and tracing
|
||||
// starts there; this is useful for exceptions.
|
||||
// otherwise, tracing starts at the current stack position, and the given
|
||||
// number of stack frames (i.e. functions) above the caller are skipped.
|
||||
// used by mmgr to determine what function requested each allocation;
|
||||
// this is fast enough to allow that.
|
||||
/**
|
||||
* return address of the Nth function on the call stack.
|
||||
*
|
||||
* used by mmgr to determine what function requested each allocation;
|
||||
* this is fast enough to allow that.
|
||||
*
|
||||
* @param skip number of stack frames (i.e. functions on call stack) to skip.
|
||||
* @param context platform-specific representation of execution state
|
||||
* (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful
|
||||
* for exceptions. otherwise, tracing starts from the current call stack.
|
||||
* @return address of Nth function
|
||||
**/
|
||||
extern void* debug_get_nth_caller(uint skip, void* context);
|
||||
|
||||
// return 1 if the pointer appears to be totally bogus, otherwise 0.
|
||||
// this check is not authoritative (the pointer may be "valid" but incorrect)
|
||||
// but can be used to filter out obviously wrong values in a portable manner.
|
||||
/**
|
||||
* check if a pointer appears to be totally invalid.
|
||||
*
|
||||
* this check is not authoritative (the pointer may be "valid" but incorrect)
|
||||
* but can be used to filter out obviously wrong values in a portable manner.
|
||||
*
|
||||
* @param p pointer
|
||||
* @return 1 if totally bogus, otherwise 0.
|
||||
**/
|
||||
extern int debug_is_pointer_bogus(const void* p);
|
||||
|
||||
/// does the given pointer appear to point to code?
|
||||
extern bool debug_is_code_ptr(void* p);
|
||||
|
||||
/// does the given pointer appear to point to the stack?
|
||||
extern bool debug_is_stack_ptr(void* p);
|
||||
|
||||
|
||||
// set the current thread's name; it will be returned by subsequent calls to
|
||||
// debug_get_thread_name.
|
||||
//
|
||||
// the string pointed to by <name> MUST remain valid throughout the
|
||||
// entire program; best to pass a string literal. allocating a copy
|
||||
// would be quite a bit more work due to cleanup issues.
|
||||
//
|
||||
// if supported on this platform, the debugger is notified of the new name;
|
||||
// it will be displayed there instead of just the handle.
|
||||
/**
|
||||
* set the current thread's name; it will be returned by subsequent calls to
|
||||
* debug_get_thread_name.
|
||||
*
|
||||
* if supported on this platform, the debugger is notified of the new name;
|
||||
* it will be displayed there instead of just the handle.
|
||||
*
|
||||
* @param name identifier string for thread. MUST remain valid throughout
|
||||
* the entire program; best to pass a string literal. allocating a copy
|
||||
* would be quite a bit more work due to cleanup issues.
|
||||
**/
|
||||
extern void debug_set_thread_name(const char* name);
|
||||
|
||||
// return the pointer assigned by debug_set_thread_name or 0 if
|
||||
// that hasn't been done yet for this thread.
|
||||
/**
|
||||
* return current thread's name.
|
||||
*
|
||||
* @return thread name, or NULL if one hasn't been assigned yet
|
||||
* via debug_set_thread_name.
|
||||
**/
|
||||
extern const char* debug_get_thread_name();
|
||||
|
||||
|
||||
// call at exit to avoid leaks (not strictly necessary).
|
||||
/**
|
||||
* call at exit to avoid some leaks.
|
||||
* not strictly necessary.
|
||||
**/
|
||||
extern void debug_shutdown();
|
||||
|
||||
#endif // #ifndef DEBUG_H_INCLUDED
|
||||
|
||||
+197
-264
@@ -33,119 +33,9 @@
|
||||
|
||||
#include "sysdep/sysdep.h"
|
||||
|
||||
#ifndef SELF_TEST_ENABLED
|
||||
#define SELF_TEST_ENABLED 0
|
||||
#endif
|
||||
|
||||
|
||||
// FNV1-A hash - good for strings.
|
||||
// if len = 0 (default), treat buf as a C-string;
|
||||
// otherwise, hash <len> bytes of buf.
|
||||
u32 fnv_hash(const void* buf, size_t len)
|
||||
{
|
||||
u32 h = 0x811c9dc5u;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
const u8* p = (const u8*)buf;
|
||||
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
while(*p)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x01000193u;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x01000193u;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
// FNV1-A hash - good for strings.
|
||||
// if len = 0 (default), treat buf as a C-string;
|
||||
// otherwise, hash <len> bytes of buf.
|
||||
u64 fnv_hash64(const void* buf, size_t len)
|
||||
{
|
||||
u64 h = 0xCBF29CE484222325ull;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
const u8* p = (const u8*)buf;
|
||||
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
while(*p)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x100000001B3ull;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x100000001B3ull;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
// special version for strings: first converts to lowercase
|
||||
// (useful for comparing mixed-case filenames).
|
||||
// note: still need <len>, e.g. to support non-0-terminated strings
|
||||
u32 fnv_lc_hash(const char* str, size_t len)
|
||||
{
|
||||
u32 h = 0x811c9dc5u;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
while(*str)
|
||||
{
|
||||
h ^= tolower(*str++);
|
||||
h *= 0x01000193u;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= tolower(*str++);
|
||||
h *= 0x01000193u;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// bit bashing
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool is_pow2(uint n)
|
||||
{
|
||||
@@ -196,7 +86,6 @@ int ilog2(uint n)
|
||||
return bit_index;
|
||||
}
|
||||
|
||||
|
||||
// return log base 2, rounded up.
|
||||
uint log2(uint x)
|
||||
{
|
||||
@@ -211,8 +100,17 @@ uint log2(uint x)
|
||||
return l;
|
||||
}
|
||||
|
||||
int ilog2(const float x)
|
||||
{
|
||||
const u32 i = *(u32*)&x;
|
||||
u32 biased_exp = (i >> 23) & 0xff;
|
||||
return (int)biased_exp - 127;
|
||||
}
|
||||
|
||||
cassert(sizeof(int)*CHAR_BIT == 32); // otherwise change round_up_to_pow2
|
||||
|
||||
// round_up_to_pow2 implementation assumes 32-bit int.
|
||||
// if 64, add "x |= (x >> 32);"
|
||||
cassert(sizeof(int)*CHAR_BIT == 32);
|
||||
|
||||
uint round_up_to_pow2(uint x)
|
||||
{
|
||||
@@ -227,13 +125,8 @@ uint round_up_to_pow2(uint x)
|
||||
}
|
||||
|
||||
|
||||
int ilog2(const float x)
|
||||
{
|
||||
const u32 i = *(u32*)&x;
|
||||
u32 biased_exp = (i >> 23) & 0xff;
|
||||
return (int)biased_exp - 127;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// misc arithmetic
|
||||
|
||||
|
||||
// multiple must be a power of two.
|
||||
@@ -261,57 +154,71 @@ u16 addusw(u16 x, u16 y)
|
||||
return (u16)MIN(t+y, 0xffffu);
|
||||
}
|
||||
|
||||
|
||||
u16 subusw(u16 x, u16 y)
|
||||
{
|
||||
long t = x;
|
||||
return (u16)(MAX(t-y, 0));
|
||||
}
|
||||
|
||||
// zero-extend <size> (truncated to 8) bytes of little-endian data to u64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
u64 movzx_64le(const u8* p, size_t size)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// rand
|
||||
|
||||
// return random integer in [min, max).
|
||||
// avoids several common pitfalls; see discussion at
|
||||
// http://www.azillionmonkeys.com/qed/random.html
|
||||
|
||||
// rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits;
|
||||
// double that amount by concatenating 2 random numbers.
|
||||
// this is not to fix poor rand() randomness - the number returned will be
|
||||
// folded down to a much smaller interval anyway. instead, a larger XRAND_MAX
|
||||
// decreases the probability of having to repeat the loop.
|
||||
#if RAND_MAX < 65536
|
||||
static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1;
|
||||
static uint xrand()
|
||||
{
|
||||
if(size > 8)
|
||||
size = 8;
|
||||
|
||||
u64 data = 0;
|
||||
for(u64 i = 0; i < MIN(size,8); i++)
|
||||
data |= ((u64)p[i]) << (i*8);
|
||||
|
||||
return data;
|
||||
return rand()*(RAND_MAX+1) + rand();
|
||||
}
|
||||
|
||||
|
||||
// sign-extend <size> (truncated to 8) bytes of little-endian data to i64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
i64 movsx_64le(const u8* p, size_t size)
|
||||
// rand() is already ok; no need to do anything.
|
||||
#else
|
||||
static const uint XRAND_MAX = RAND_MAX;
|
||||
static uint xrand()
|
||||
{
|
||||
if(size > 8)
|
||||
size = 8;
|
||||
return rand();
|
||||
}
|
||||
#endif
|
||||
|
||||
u64 data = movzx_64le(p, size);
|
||||
|
||||
// no point in sign-extending if >= 8 bytes were requested
|
||||
if(size < 8)
|
||||
uint rand(uint min_inclusive, uint max_exclusive)
|
||||
{
|
||||
const uint range = (max_exclusive-min_inclusive);
|
||||
// huge interval or min >= max
|
||||
if(range == 0 || range > XRAND_MAX)
|
||||
{
|
||||
u64 sign_bit = 1;
|
||||
sign_bit <<= (size*8)-1;
|
||||
// be sure that we don't shift more than variable's bit width
|
||||
|
||||
// number would be negative in the smaller type,
|
||||
// so sign-extend, i.e. set all more significant bits.
|
||||
if(data & sign_bit)
|
||||
{
|
||||
const u64 size_mask = (sign_bit+sign_bit)-1;
|
||||
data |= ~size_mask;
|
||||
}
|
||||
WARN_ERR(ERR_INVALID_PARAM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (i64)data;
|
||||
const uint inv_range = XRAND_MAX / range;
|
||||
|
||||
// generate random number in [0, range)
|
||||
// idea: avoid skewed distributions when <range> doesn't evenly divide
|
||||
// XRAND_MAX by simply discarding values in the "remainder".
|
||||
// not expected to run often since XRAND_MAX is large.
|
||||
uint x;
|
||||
do
|
||||
x = xrand();
|
||||
while(x >= range * inv_range);
|
||||
x /= inv_range;
|
||||
|
||||
x += min_inclusive;
|
||||
debug_assert(x < max_exclusive);
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// type conversion
|
||||
|
||||
// these avoid a common mistake in using >> (ANSI requires shift count be
|
||||
// less than the bit width of the type).
|
||||
|
||||
@@ -336,7 +243,6 @@ u16 u32_lo(u32 x)
|
||||
}
|
||||
|
||||
|
||||
|
||||
u64 u64_from_u32(u32 hi, u32 lo)
|
||||
{
|
||||
u64 x = (u64)hi;
|
||||
@@ -354,6 +260,45 @@ u32 u32_from_u16(u16 hi, u16 lo)
|
||||
}
|
||||
|
||||
|
||||
// zero-extend <size> (truncated to 8) bytes of little-endian data to u64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
u64 movzx_64le(const u8* p, size_t size)
|
||||
{
|
||||
size = MIN(size, 8);
|
||||
|
||||
u64 data = 0;
|
||||
for(u64 i = 0; i < size; i++)
|
||||
data |= ((u64)p[i]) << (i*8);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// sign-extend <size> (truncated to 8) bytes of little-endian data to i64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
i64 movsx_64le(const u8* p, size_t size)
|
||||
{
|
||||
size = MIN(size, 8);
|
||||
|
||||
u64 data = movzx_64le(p, size);
|
||||
|
||||
// no point in sign-extending if >= 8 bytes were requested
|
||||
if(size < 8)
|
||||
{
|
||||
u64 sign_bit = 1;
|
||||
sign_bit <<= (size*8)-1;
|
||||
// be sure that we don't shift more than variable's bit width
|
||||
|
||||
// number would be negative in the smaller type,
|
||||
// so sign-extend, i.e. set all more significant bits.
|
||||
if(data & sign_bit)
|
||||
{
|
||||
const u64 size_mask = (sign_bit+sign_bit)-1;
|
||||
data |= ~size_mask;
|
||||
}
|
||||
}
|
||||
|
||||
return (i64)data;
|
||||
}
|
||||
|
||||
|
||||
// input in [0, 1); convert to u8 range
|
||||
@@ -370,7 +315,6 @@ u8 fp_to_u8(double in)
|
||||
return (u8)l;
|
||||
}
|
||||
|
||||
|
||||
// input in [0, 1); convert to u16 range
|
||||
u16 fp_to_u16(double in)
|
||||
{
|
||||
@@ -386,18 +330,18 @@ u16 fp_to_u16(double in)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// string processing
|
||||
|
||||
// big endian!
|
||||
void base32(const int len, const u8* in, u8* out)
|
||||
void base32(const size_t len, const u8* in, u8* out)
|
||||
{
|
||||
int bits = 0;
|
||||
u32 pool = 0;
|
||||
u32 pool = 0; // of bits from buffer
|
||||
uint bits = 0; // # bits currently in buffer
|
||||
|
||||
static u8 tbl[33] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
static const u8 tbl[33] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
for(int i = 0; i < len; i++)
|
||||
for(size_t i = 0; i < len; i++)
|
||||
{
|
||||
if(bits < 5)
|
||||
{
|
||||
@@ -407,20 +351,12 @@ void base32(const int len, const u8* in, u8* out)
|
||||
}
|
||||
|
||||
bits -= 5;
|
||||
int c = (pool >> bits) & 31;
|
||||
uint c = (pool >> bits) & 31;
|
||||
*out++ = tbl[c];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// case-insensitive check if string <s> matches the pattern <w>,
|
||||
// which may contain '?' or '*' wildcards. if so, return 1, otherwise 0.
|
||||
// idea from http://www.codeproject.com/string/wildcmp.asp .
|
||||
// note: NULL wildcard pattern matches everything!
|
||||
int match_wildcard(const char* s, const char* w)
|
||||
{
|
||||
if(!w)
|
||||
@@ -520,110 +456,107 @@ int match_wildcardw(const wchar_t* s, const wchar_t* w)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// return random integer in [min, max).
|
||||
// avoids several common pitfalls; see discussion at
|
||||
// http://www.azillionmonkeys.com/qed/random.html
|
||||
|
||||
// rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits;
|
||||
// double that amount by concatenating 2 random numbers.
|
||||
// this is not to fix poor rand() randomness - the number returned will be
|
||||
// folded down to a much smaller interval anyway. instead, a larger XRAND_MAX
|
||||
// decreases the probability of having to repeat the loop.
|
||||
#if RAND_MAX < 65536
|
||||
static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1;
|
||||
static uint xrand()
|
||||
// FNV1-A hash - good for strings.
|
||||
// if len = 0 (default), treat buf as a C-string;
|
||||
// otherwise, hash <len> bytes of buf.
|
||||
u32 fnv_hash(const void* buf, size_t len)
|
||||
{
|
||||
return rand()*(RAND_MAX+1) + rand();
|
||||
}
|
||||
// rand() is already ok; no need to do anything.
|
||||
#else
|
||||
static const uint XRAND_MAX = RAND_MAX;
|
||||
static uint xrand()
|
||||
{
|
||||
return rand();
|
||||
}
|
||||
#endif
|
||||
u32 h = 0x811c9dc5u;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
uint rand(uint min_inclusive, uint max_exclusive)
|
||||
{
|
||||
const uint range = (max_exclusive-min_inclusive);
|
||||
// huge interval or min >= max
|
||||
if(range == 0 || range > XRAND_MAX)
|
||||
const u8* p = (const u8*)buf;
|
||||
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
WARN_ERR(ERR_INVALID_PARAM);
|
||||
return 0;
|
||||
while(*p)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x01000193u;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x01000193u;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
const uint inv_range = XRAND_MAX / range;
|
||||
|
||||
// generate random number in [0, range)
|
||||
// idea: avoid skewed distributions when <range> doesn't evenly divide
|
||||
// XRAND_MAX by simply discarding values in the "remainder".
|
||||
// not expected to run often since XRAND_MAX is large.
|
||||
uint x;
|
||||
do
|
||||
x = xrand();
|
||||
while(x >= range * inv_range);
|
||||
x /= inv_range;
|
||||
|
||||
x += min_inclusive;
|
||||
debug_assert(x < max_exclusive);
|
||||
return x;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_log2()
|
||||
// FNV1-A hash - good for strings.
|
||||
// if len = 0 (default), treat buf as a C-string;
|
||||
// otherwise, hash <len> bytes of buf.
|
||||
u64 fnv_hash64(const void* buf, size_t len)
|
||||
{
|
||||
TEST(ilog2(0u) == -1);
|
||||
TEST(ilog2(3u) == -1);
|
||||
TEST(ilog2(0xffffffffu) == -1);
|
||||
TEST(ilog2(1u) == 0);
|
||||
TEST(ilog2(256u) == 8);
|
||||
TEST(ilog2(0x80000000u) == 31);
|
||||
}
|
||||
u64 h = 0xCBF29CE484222325ull;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
static void test_rand()
|
||||
{
|
||||
// complain if huge interval or min > max
|
||||
TEST(rand(1, 0) == 0);
|
||||
TEST(rand(2, ~0u) == 0);
|
||||
const u8* p = (const u8*)buf;
|
||||
|
||||
// returned number must be in [min, max)
|
||||
for(int i = 0; i < 100; i++)
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
uint min = rand(), max = min+rand();
|
||||
uint x = rand(min, max);
|
||||
TEST(min <= x && x < max);
|
||||
while(*p)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x100000001B3ull;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= *p++;
|
||||
h *= 0x100000001B3ull;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure both possible values are hit
|
||||
uint ones = 0, twos = 0;
|
||||
for(int i = 0; i < 100; i++)
|
||||
{
|
||||
uint x = rand(1, 3);
|
||||
// paranoia: don't use array (x might not be 1 or 2 - checked below)
|
||||
if(x == 1) ones++; if(x == 2) twos++;
|
||||
}
|
||||
TEST(ones+twos == 100);
|
||||
TEST(ones > 10 && twos > 10);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
|
||||
// special version for strings: first converts to lowercase
|
||||
// (useful for comparing mixed-case filenames).
|
||||
// note: still need <len>, e.g. to support non-0-terminated strings
|
||||
u32 fnv_lc_hash(const char* str, size_t len)
|
||||
{
|
||||
test_log2();
|
||||
test_rand();
|
||||
u32 h = 0x811c9dc5u;
|
||||
// give distinct values for different length 0 buffers.
|
||||
// value taken from FNV; it has no special significance.
|
||||
|
||||
// expected case: string
|
||||
if(!len)
|
||||
{
|
||||
while(*str)
|
||||
{
|
||||
h ^= tolower(*str++);
|
||||
h *= 0x01000193u;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t bytes_left = len;
|
||||
while(bytes_left != 0)
|
||||
{
|
||||
h ^= tolower(*str++);
|
||||
h *= 0x01000193u;
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
+298
-129
@@ -20,9 +20,7 @@
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
[KEEP IN SYNC WITH WIKI]
|
||||
/**
|
||||
|
||||
low-level aka "lib"
|
||||
-------------------
|
||||
@@ -53,7 +51,7 @@ scope
|
||||
- low-level helper functions, e.g. ADTs, endian conversion and timing
|
||||
- platform-dependent system/feature detection
|
||||
|
||||
*/
|
||||
**/
|
||||
|
||||
#ifndef LIB_H__
|
||||
#define LIB_H__
|
||||
@@ -66,31 +64,51 @@ scope
|
||||
#include "lib/types.h"
|
||||
#include "sysdep/sysdep.h"
|
||||
#include "sysdep/cpu.h" // CAS
|
||||
//#include "sysdep/sysdep.h" // moved down; see below.
|
||||
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define EXTERN_C extern "C"
|
||||
# define EXTERN_C extern "C"
|
||||
#else
|
||||
#define EXTERN_C extern
|
||||
# define EXTERN_C extern
|
||||
#endif
|
||||
|
||||
// package code into a single statement.
|
||||
// notes:
|
||||
// - for(;;) { break; } and {} don't work because invocations of macros
|
||||
// implemented with STMT often end with ";", thus breaking if() expressions.
|
||||
// - we'd really like to eliminate "conditional expression is constant"
|
||||
// warnings. replacing 0 literals with extern volatile variables fools
|
||||
// VC7 but isn't guaranteed to be free of overhead. we will just
|
||||
// squelch the warning (unfortunately non-portable).
|
||||
|
||||
const size_t KiB = 1ul << 10;
|
||||
const size_t MiB = 1ul << 20;
|
||||
const size_t GiB = 1ul << 30;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// code-generating macros
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* package code into a single statement.
|
||||
*
|
||||
* @param STMT_code__ code to be bundled. (must be interpretable as
|
||||
* a macro argument, i.e. sequence of tokens).
|
||||
* the argument name is chosen to avoid conflicts.
|
||||
*
|
||||
* notes:
|
||||
* - for(;;) { break; } and {} don't work because invocations of macros
|
||||
* implemented with STMT often end with ";", thus breaking if() expressions.
|
||||
* - we'd really like to eliminate "conditional expression is constant"
|
||||
* warnings. replacing 0 literals with extern volatile variables fools
|
||||
* VC7 but isn't guaranteed to be free of overhead. we will just
|
||||
* squelch the warning (unfortunately non-portable).
|
||||
**/
|
||||
#define STMT(STMT_code__) do { STMT_code__; } while(false)
|
||||
|
||||
// must come after definition of STMT
|
||||
#include "lib/lib_errors.h"
|
||||
|
||||
// execute the code passed as a parameter only the first time this is
|
||||
// reached.
|
||||
// may be called at any time (in particular before main), but is not
|
||||
// thread-safe. if that's important, use pthread_once() instead.
|
||||
/**
|
||||
* execute the code passed as a parameter only the first time this is
|
||||
* reached.
|
||||
* may be called at any time (in particular before main), but is not
|
||||
* thread-safe. if that's important, use pthread_once() instead.
|
||||
**/
|
||||
#define ONCE(ONCE_code__)\
|
||||
STMT(\
|
||||
static bool ONCE_done__ = false;\
|
||||
@@ -101,10 +119,12 @@ STMT(\
|
||||
}\
|
||||
)
|
||||
|
||||
// execute the code passed as a parameter except the first time this is
|
||||
// reached.
|
||||
// may be called at any time (in particular before main), but is not
|
||||
// thread-safe.
|
||||
/**
|
||||
* execute the code passed as a parameter except the first time this is
|
||||
* reached.
|
||||
* may be called at any time (in particular before main), but is not
|
||||
* thread-safe.
|
||||
**/
|
||||
#define ONCE_NOT(ONCE_code__)\
|
||||
STMT(\
|
||||
static bool ONCE_done__ = false;\
|
||||
@@ -115,8 +135,13 @@ STMT(\
|
||||
)
|
||||
|
||||
|
||||
// useful because VC6 may return 0 on failure, instead of throwing.
|
||||
// this wraps the exception handling, and creates a NULL pointer on failure.
|
||||
/**
|
||||
* C++ new wrapper: allocates an instance of the given type and stores a
|
||||
* pointer to it. sets pointer to 0 on allocation failure.
|
||||
*
|
||||
* this simplifies application code when on VC6, which may or
|
||||
* may not throw/return 0 on failure.
|
||||
**/
|
||||
#define SAFE_NEW(type, ptr)\
|
||||
type* ptr;\
|
||||
try\
|
||||
@@ -128,13 +153,30 @@ STMT(\
|
||||
ptr = 0;\
|
||||
}
|
||||
|
||||
/**
|
||||
* delete memory ensuing from new and set the pointer to zero
|
||||
* (thus making double-frees safe / a no-op)
|
||||
**/
|
||||
#define SAFE_DELETE(p)\
|
||||
STMT(\
|
||||
delete (p); /* if p == 0, delete is a no-op */ \
|
||||
(p) = 0;\
|
||||
)
|
||||
|
||||
/**
|
||||
* delete memory ensuing from new[] and set the pointer to zero
|
||||
* (thus making double-frees safe / a no-op)
|
||||
**/
|
||||
#define SAFE_ARRAY_DELETE(p)\
|
||||
STMT(\
|
||||
delete[] (p); /* if p == 0, delete is a no-op */ \
|
||||
(p) = 0;\
|
||||
)
|
||||
|
||||
/**
|
||||
* free memory ensuing from malloc and set the pointer to zero
|
||||
* (thus making double-frees safe / a no-op)
|
||||
**/
|
||||
#define SAFE_FREE(p)\
|
||||
STMT(\
|
||||
free(p); /* if p == 0, free is a no-op */ \
|
||||
@@ -142,33 +184,37 @@ STMT(\
|
||||
)
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// source code annotation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) (((a) < (b))? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) (((a) > (b))? (a) : (b))
|
||||
#endif
|
||||
|
||||
|
||||
// 2 ways of avoiding "unreferenced formal parameter" warnings:
|
||||
// .. inside the function body, e.g. void f(int x) { UNUSED2(x); }
|
||||
/**
|
||||
* mark a function local variable or parameter as unused and avoid
|
||||
* the corresponding compiler warning.
|
||||
* use inside the function body, e.g. void f(int x) { UNUSED2(x); }
|
||||
**/
|
||||
#define UNUSED2(param) (void)param;
|
||||
// .. wrapped around the parameter name, e.g. void f(int UNUSED(x))
|
||||
|
||||
/**
|
||||
* mark a function parameter as unused and avoid
|
||||
* the corresponding compiler warning.
|
||||
* wrap around the parameter name, e.g. void f(int UNUSED(x))
|
||||
**/
|
||||
#define UNUSED(param)
|
||||
|
||||
// mark the copy constructor as inaccessible. this squelches
|
||||
// "cannot be generated" warnings for classes with const members.
|
||||
//
|
||||
// intended to be used at end of class definition.
|
||||
// must be followed by semicolon.
|
||||
/**
|
||||
* mark the copy constructor as inaccessible. this squelches
|
||||
* "cannot be generated" warnings for classes with const members.
|
||||
*
|
||||
* intended to be used at end of class definition.
|
||||
* must be followed by semicolon.
|
||||
**/
|
||||
#define NO_COPY_CTOR(class_name)\
|
||||
private:\
|
||||
class_name& operator=(const class_name&)
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
"unreachable code" helpers
|
||||
|
||||
unreachable lines of code are often the source or symptom of subtle bugs.
|
||||
@@ -200,7 +246,9 @@ our implementation of UNREACHABLE solves this dilemna as follows:
|
||||
|
||||
this approach still allows for the possiblity of automated
|
||||
checking, but does not cause any compiler warnings.
|
||||
*/
|
||||
**/
|
||||
#define UNREACHABLE // actually defined below.. this is for
|
||||
# undef UNREACHABLE // CppDoc's benefit only.
|
||||
|
||||
// 1) final build: optimize assuming this location cannot be reached.
|
||||
// may crash if that turns out to be untrue, but removes checking overhead.
|
||||
@@ -223,7 +271,7 @@ checking, but does not cause any compiler warnings.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
/**
|
||||
convenient specialization of UNREACHABLE for switch statements whose
|
||||
default can never be reached. example usage:
|
||||
int x;
|
||||
@@ -233,17 +281,11 @@ switch(x % 2)
|
||||
case 1: break;
|
||||
NODEFAULT;
|
||||
}
|
||||
*/
|
||||
**/
|
||||
#define NODEFAULT default: UNREACHABLE
|
||||
|
||||
|
||||
#define ARRAY_SIZE(name) (sizeof(name) / sizeof(name[0]))
|
||||
|
||||
|
||||
//
|
||||
// compile-time debug_assert, especially useful for testing sizeof().
|
||||
// no runtime overhead; may be used anywhere, including file scope.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// cassert
|
||||
|
||||
// generate a symbol containing the line number of the macro invocation.
|
||||
// used to give a unique name (per file) to types made by cassert.
|
||||
@@ -254,37 +296,75 @@ switch(x % 2)
|
||||
#define MAKE_UID1__(l) MAKE_UID2__(l)
|
||||
#define UID__ MAKE_UID1__(__LINE__)
|
||||
|
||||
// more descriptive error message, but may cause a struct redefinition
|
||||
// warning if used from the same line in different files.
|
||||
/**
|
||||
* compile-time debug_assert. causes a compile error if the expression
|
||||
* evaluates to zero/false.
|
||||
*
|
||||
* no runtime overhead; may be used anywhere, including file scope.
|
||||
* especially useful for testing sizeof types.
|
||||
*
|
||||
* this version has a more descriptive error message, but may cause a
|
||||
* struct redefinition warning if used from the same line in different files.
|
||||
*
|
||||
* note: alternative method in C++: specialize a struct only for true;
|
||||
* using it will raise 'incomplete type' errors if instantiated with false.
|
||||
*
|
||||
* @param expression that is expected to evaluate to non-zero at compile-time.
|
||||
**/
|
||||
#define cassert(expr) struct UID__ { int CASSERT_FAILURE: (expr); }
|
||||
|
||||
// less helpful error message, but redefinition doesn't trigger warnings.
|
||||
/**
|
||||
* compile-time debug_assert. causes a compile error if the expression
|
||||
* evaluates to zero/false.
|
||||
*
|
||||
* no runtime overhead; may be used anywhere, including file scope.
|
||||
* especially useful for testing sizeof types.
|
||||
*
|
||||
* this version has a less helpful error message, but redefinition doesn't
|
||||
* trigger warnings.
|
||||
*
|
||||
* @param expression that is expected to evaluate to non-zero at compile-time.
|
||||
**/
|
||||
#define cassert2(expr) extern char CASSERT_FAILURE[1][(expr)]
|
||||
|
||||
// note: alternative method in C++: specialize a struct only for true;
|
||||
// using it will raise 'incomplete type' errors if instantiated with false.
|
||||
|
||||
|
||||
|
||||
|
||||
const size_t KiB = 1ul << 10;
|
||||
const size_t MiB = 1ul << 20;
|
||||
const size_t GiB = 1ul << 30;
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// bit bashing
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* value of bit number <n>.
|
||||
*
|
||||
* @param n bit index (0..CHAR_BIT*sizeof(int)-1)
|
||||
**/
|
||||
#define BIT(n) (1ul << (n))
|
||||
|
||||
|
||||
// these are declared in the header and inlined to aid compiler optimizations
|
||||
// (they can easily end up being time-critical).
|
||||
// note: GCC can't inline extern functions, while VC's "Whole Program
|
||||
// Optimization" can.
|
||||
|
||||
/**
|
||||
* a mask that includes the lowest N bits
|
||||
*
|
||||
* @param num_bits number of bits in mask
|
||||
**/
|
||||
inline uint bit_mask(uint num_bits)
|
||||
{
|
||||
return (1u << num_bits)-1;
|
||||
}
|
||||
|
||||
/**
|
||||
* extract the value of bits hi_idx:lo_idx within num
|
||||
*
|
||||
* example: bits(0x69, 2, 5) == 0x0A
|
||||
*
|
||||
* @param num number whose bits are to be extracted
|
||||
* @param lo_idx bit index of lowest bit to include
|
||||
* @param hi_idx bit index of highest bit to include
|
||||
* @return value of extracted bits.
|
||||
**/
|
||||
inline uint bits(uint num, uint lo_idx, uint hi_idx)
|
||||
{
|
||||
const uint count = (hi_idx - lo_idx)+1; // # bits to return
|
||||
@@ -293,93 +373,182 @@ inline uint bits(uint num, uint lo_idx, uint hi_idx)
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// FNV1-A hash - good for strings.
|
||||
// if len = 0 (default), treat buf as a C-string;
|
||||
// otherwise, hash <len> bytes of buf.
|
||||
extern u32 fnv_hash(const void* buf, size_t len = 0);
|
||||
extern u64 fnv_hash64(const void* buf, size_t len = 0);
|
||||
|
||||
// special version for strings: first converts to lowercase
|
||||
// (useful for comparing mixed-case filenames)
|
||||
extern u32 fnv_lc_hash(const char* str, size_t len = 0);
|
||||
|
||||
// hash (currently FNV) of a filename
|
||||
typedef u32 FnHash;
|
||||
|
||||
|
||||
extern u16 addusw(u16 x, u16 y);
|
||||
extern u16 subusw(u16 x, u16 y);
|
||||
|
||||
// zero-extend <size> (truncated to 8) bytes of little-endian data to u64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
extern u64 movzx_64le(const u8* p, size_t size);
|
||||
|
||||
// sign-extend <size> (truncated to 8) bytes of little-endian data to i64,
|
||||
// starting at address <p> (need not be aligned).
|
||||
extern i64 movsx_64le(const u8* p, size_t size);
|
||||
|
||||
|
||||
/// is the given number a power of two?
|
||||
extern bool is_pow2(uint n);
|
||||
|
||||
// return -1 if not an integral power of 2,
|
||||
// otherwise the base2 logarithm
|
||||
/**
|
||||
* @return -1 if not an integral power of 2,
|
||||
* otherwise the base2 logarithm.
|
||||
**/
|
||||
extern int ilog2(uint n);
|
||||
|
||||
// return log base 2, rounded up.
|
||||
/**
|
||||
* @return log base 2, rounded up.
|
||||
**/
|
||||
extern uint log2(uint x);
|
||||
|
||||
/**
|
||||
* another implementation; uses the FPU normalization hardware.
|
||||
*
|
||||
* @return log base 2, rounded up.
|
||||
**/
|
||||
extern int ilog2(const float x);
|
||||
|
||||
/**
|
||||
* round up to nearest power of two; no change if already POT.
|
||||
**/
|
||||
extern uint round_up_to_pow2(uint x);
|
||||
|
||||
|
||||
// multiple must be a power of two.
|
||||
//-----------------------------------------------------------------------------
|
||||
// misc arithmetic
|
||||
|
||||
/// canonical minimum macro
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) (((a) < (b))? (a) : (b))
|
||||
#endif
|
||||
|
||||
/// canonical maximum macro
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) (((a) > (b))? (a) : (b))
|
||||
#endif
|
||||
|
||||
/// number of array elements
|
||||
#define ARRAY_SIZE(name) (sizeof(name) / sizeof(name[0]))
|
||||
|
||||
/**
|
||||
* round number up/down to the next given multiple.
|
||||
*
|
||||
* @param multiple: must be a power of two.
|
||||
**/
|
||||
extern uintptr_t round_up (uintptr_t n, uintptr_t multiple);
|
||||
extern uintptr_t round_down(uintptr_t n, uintptr_t multiple);
|
||||
|
||||
// these avoid a common mistake in using >> (ANSI requires shift count be
|
||||
// less than the bit width of the type).
|
||||
extern u32 u64_hi(u64 x);
|
||||
extern u32 u64_lo(u64 x);
|
||||
extern u16 u32_hi(u32 x);
|
||||
extern u16 u32_lo(u32 x);
|
||||
|
||||
|
||||
extern u64 u64_from_u32(u32 hi, u32 lo);
|
||||
extern u32 u32_from_u16(u16 hi, u16 lo);
|
||||
|
||||
|
||||
|
||||
/// 16-bit saturating (does not overflow) addition.
|
||||
extern u16 addusw(u16 x, u16 y);
|
||||
/// 16-bit saturating (does not underflow) subtraction.
|
||||
extern u16 subusw(u16 x, u16 y);
|
||||
|
||||
/**
|
||||
* are the given floats nearly "equal"?
|
||||
*
|
||||
* @return whether the numbers are within "epsilon" of each other.
|
||||
*
|
||||
* notes:
|
||||
* - the epsilon magic number varies with the magnitude of the inputs.
|
||||
* we use a sane default, but don't use this routine for very
|
||||
* large/small comparands.
|
||||
* - floating-point numbers don't magically lose precision. addition,
|
||||
* subtraction and multiplication results are precise up to the mantissa's
|
||||
* least-significant bit. only division, sqrt, sin/cos and other
|
||||
* trancendental operations introduce error.
|
||||
**/
|
||||
inline bool feq(float f1, float f2)
|
||||
{
|
||||
// the requisite value will change with the magnitude of f1 and f2!
|
||||
// this is a sane default, but don't use this routine for very
|
||||
// large/small comparands.
|
||||
const float epsilon = 0.00001f;
|
||||
return fabsf(f1 - f2) < epsilon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return random integer in [min, max).
|
||||
* avoids several common pitfalls; see discussion at
|
||||
* http://www.azillionmonkeys.com/qed/random.html
|
||||
**/
|
||||
extern uint rand(uint min_inclusive, uint max_exclusive);
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// type conversion
|
||||
|
||||
|
||||
// note: these avoid a common mistake in using >> (ANSI requires
|
||||
// shift count be less than the bit width of the type).
|
||||
|
||||
extern u32 u64_hi(u64 x); /// return upper 32-bits
|
||||
extern u32 u64_lo(u64 x); /// return lower 32-bits
|
||||
extern u16 u32_hi(u32 x); /// return upper 16-bits
|
||||
extern u16 u32_lo(u32 x); /// return lower 16-bits
|
||||
|
||||
extern u64 u64_from_u32(u32 hi, u32 lo); /// assemble u64 from u32
|
||||
extern u32 u32_from_u16(u16 hi, u16 lo); /// assemble u32 from u16
|
||||
|
||||
/**
|
||||
* zero-extend <size> (truncated to 8) bytes of little-endian data to u64,
|
||||
* starting at address <p> (need not be aligned).
|
||||
**/
|
||||
extern u64 movzx_64le(const u8* p, size_t size);
|
||||
|
||||
/**
|
||||
* sign-extend <size> (truncated to 8) bytes of little-endian data to i64,
|
||||
* starting at address <p> (need not be aligned).
|
||||
**/
|
||||
extern i64 movsx_64le(const u8* p, size_t size);
|
||||
|
||||
/// convert double to u8; verifies number is in range.
|
||||
extern u8 fp_to_u8 (double in);
|
||||
/// convert double to u16; verifies number is in range.
|
||||
extern u16 fp_to_u16(double in);
|
||||
|
||||
|
||||
// big endian!
|
||||
extern void base32(const int len, const u8* in, u8* out);
|
||||
//-----------------------------------------------------------------------------
|
||||
// string processing
|
||||
|
||||
|
||||
// case-insensitive check if string <s> matches the pattern <w>,
|
||||
// which may contain '?' or '*' wildcards. if so, return 1, otherwise 0.
|
||||
// note: NULL wildcard pattern matches everything!
|
||||
extern int match_wildcard(const char* s, const char* w);
|
||||
extern int match_wildcardw(const wchar_t* s, const wchar_t* w);
|
||||
|
||||
// this is strcpy, but indicates that the programmer checked usage and
|
||||
// promises it is safe.
|
||||
/**
|
||||
* this is strcpy, but indicates that the programmer checked usage and
|
||||
* promises it is safe.
|
||||
**/
|
||||
#define SAFE_STRCPY strcpy
|
||||
|
||||
// return random integer in [min, max).
|
||||
// avoids several common pitfalls; see discussion at
|
||||
// http://www.azillionmonkeys.com/qed/random.html
|
||||
extern uint rand(uint min_inclusive, uint max_exclusive);
|
||||
|
||||
/**
|
||||
* generate the base32 textual representation of a buffer.
|
||||
*
|
||||
* @param len size [bytes] of input
|
||||
* @param big-endian input data (assumed to be integral number of bytes)
|
||||
* @param output string; zero-terminated. must be big enough
|
||||
* (i.e. at least ceil(len*CHAR_BIT/5) + 1 chars)
|
||||
**/
|
||||
extern void base32(const size_t len, const u8* in, u8* out);
|
||||
|
||||
|
||||
/**
|
||||
* partial regex implementation: see if string matches pattern.
|
||||
*
|
||||
* @param s input string
|
||||
* @param w pseudo-regex to match against. case-insensitive;
|
||||
* may contain '?' and/or '*' wildcards. if NULL, matches everything.
|
||||
*
|
||||
* @return 1 if they match, otherwise 0.
|
||||
*
|
||||
* algorithmfrom http://www.codeproject.com/string/wildcmp.asp.
|
||||
**/
|
||||
extern int match_wildcard(const char* s, const char* w);
|
||||
/// unicode version of match_wildcard.
|
||||
extern int match_wildcardw(const wchar_t* s, const wchar_t* w);
|
||||
|
||||
|
||||
/**
|
||||
* calculate FNV1-A hash.
|
||||
*
|
||||
* @param buf input buffer.
|
||||
* @param len if 0 (default), treat buf as a C-string; otherwise,
|
||||
* indicates how many bytes of buffer to hash.
|
||||
* @return hash result. note: results are distinct for buffers containing
|
||||
* differing amounts of zero bytes because the hash value is seeded.
|
||||
*
|
||||
* rationale: this algorithm was chosen because it delivers 'good' results
|
||||
* for string data and is relatively simple. other good alternatives exist;
|
||||
* see Ozan Yigit's hash roundup.
|
||||
**/
|
||||
extern u32 fnv_hash(const void* buf, size_t len = 0);
|
||||
/// 64-bit version of fnv_hash.
|
||||
extern u64 fnv_hash64(const void* buf, size_t len = 0);
|
||||
|
||||
/**
|
||||
* special version of fnv_hash for strings: first converts to lowercase
|
||||
* (useful for comparing mixed-case filenames)
|
||||
**/
|
||||
extern u32 fnv_lc_hash(const char* str, size_t len = 0);
|
||||
|
||||
#endif // #ifndef LIB_H__
|
||||
|
||||
@@ -31,10 +31,6 @@
|
||||
#include "lockfree.h"
|
||||
#include "timer.h"
|
||||
|
||||
// known to fail on P4 due to mem reordering and lack of membars.
|
||||
#undef SELF_TEST_ENABLED
|
||||
#define SELF_TEST_ENABLED 0
|
||||
|
||||
/*
|
||||
liberties taken:
|
||||
- R(H) will remain constant
|
||||
@@ -743,226 +739,3 @@ LibError lfh_erase(LFHash* hash, uintptr_t key)
|
||||
{
|
||||
return lfl_erase(chain(hash,key), key);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// built-in self test
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
#define TEST_CALL(expr) TEST(expr == 0)
|
||||
|
||||
// make sure the data structures work at all; doesn't test thread-safety.
|
||||
static void basic_single_threaded_test()
|
||||
{
|
||||
void* user_data;
|
||||
|
||||
const uint ENTRIES = 50;
|
||||
// should be more than max # retired nodes to test release..() code
|
||||
uintptr_t key = 0x1000;
|
||||
uint sig = 10;
|
||||
|
||||
LFList list;
|
||||
TEST_CALL(lfl_init(&list));
|
||||
|
||||
LFHash hash;
|
||||
TEST_CALL(lfh_init(&hash, 8));
|
||||
|
||||
// add some entries; store "signatures" (ascending int values)
|
||||
for(uint i = 0; i < ENTRIES; i++)
|
||||
{
|
||||
int was_inserted;
|
||||
|
||||
user_data = lfl_insert(&list, key+i, sizeof(int), &was_inserted);
|
||||
TEST(user_data != 0 && was_inserted);
|
||||
*(uint*)user_data = sig+i;
|
||||
|
||||
user_data = lfh_insert(&hash, key+i, sizeof(int), &was_inserted);
|
||||
TEST(user_data != 0 && was_inserted);
|
||||
*(uint*)user_data = sig+i;
|
||||
}
|
||||
|
||||
// make sure all "signatures" are present in list
|
||||
for(uint i = 0; i < ENTRIES; i++)
|
||||
{
|
||||
user_data = lfl_find(&list, key+i);
|
||||
TEST(user_data != 0);
|
||||
TEST(*(uint*)user_data == sig+i);
|
||||
|
||||
user_data = lfh_find(&hash, key+i);
|
||||
TEST(user_data != 0);
|
||||
TEST(*(uint*)user_data == sig+i);
|
||||
|
||||
}
|
||||
|
||||
lfl_free(&list);
|
||||
lfh_free(&hash);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// multithreaded torture test
|
||||
//
|
||||
|
||||
// poor man's synchronization "barrier"
|
||||
static bool is_complete;
|
||||
static intptr_t num_active_threads;
|
||||
|
||||
static LFList list;
|
||||
static LFHash hash;
|
||||
|
||||
typedef std::set<uintptr_t> KeySet;
|
||||
typedef KeySet::const_iterator KeySetIt;
|
||||
static KeySet keys;
|
||||
static pthread_mutex_t mutex; // protects <keys>
|
||||
|
||||
|
||||
static void* thread_func(void* arg)
|
||||
{
|
||||
debug_set_thread_name("LF_test");
|
||||
|
||||
const uintptr_t thread_number = (uintptr_t)arg;
|
||||
|
||||
atomic_add(&num_active_threads, 1);
|
||||
|
||||
// chosen randomly every iteration (int_value % 4)
|
||||
enum TestAction
|
||||
{
|
||||
TA_FIND = 0,
|
||||
TA_INSERT = 1,
|
||||
TA_ERASE = 2,
|
||||
TA_SLEEP = 3
|
||||
};
|
||||
static const char* const action_strings[] =
|
||||
{
|
||||
"find", "insert", "erase", "sleep"
|
||||
};
|
||||
|
||||
while(!is_complete)
|
||||
{
|
||||
void* user_data;
|
||||
|
||||
const int action = rand(0, 4);
|
||||
const uintptr_t key = rand(0, 100);
|
||||
const int sleep_duration_ms = rand(0, 100);
|
||||
debug_printf("thread %d: %s\n", thread_number, action_strings[action]);
|
||||
|
||||
//
|
||||
pthread_mutex_lock(&mutex);
|
||||
const bool was_in_set = keys.find(key) != keys.end();
|
||||
if(action == TA_INSERT)
|
||||
keys.insert(key);
|
||||
else if(action == TA_ERASE)
|
||||
keys.erase(key);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
switch(action)
|
||||
{
|
||||
case TA_FIND:
|
||||
{
|
||||
user_data = lfl_find(&list, key);
|
||||
TEST(was_in_set == (user_data != 0));
|
||||
if(user_data)
|
||||
TEST(*(uintptr_t*)user_data == ~key);
|
||||
|
||||
user_data = lfh_find(&hash, key);
|
||||
// typical failure site if lockfree data structure has bugs.
|
||||
TEST(was_in_set == (user_data != 0));
|
||||
if(user_data)
|
||||
TEST(*(uintptr_t*)user_data == ~key);
|
||||
}
|
||||
break;
|
||||
|
||||
case TA_INSERT:
|
||||
{
|
||||
int was_inserted;
|
||||
|
||||
user_data = lfl_insert(&list, key, sizeof(uintptr_t), &was_inserted);
|
||||
TEST(user_data != 0); // only triggers if out of memory
|
||||
*(uintptr_t*)user_data = ~key; // checked above
|
||||
TEST(was_in_set == !was_inserted);
|
||||
|
||||
user_data = lfh_insert(&hash, key, sizeof(uintptr_t), &was_inserted);
|
||||
TEST(user_data != 0); // only triggers if out of memory
|
||||
*(uintptr_t*)user_data = ~key; // checked above
|
||||
TEST(was_in_set == !was_inserted);
|
||||
}
|
||||
break;
|
||||
|
||||
case TA_ERASE:
|
||||
{
|
||||
int err;
|
||||
|
||||
err = lfl_erase(&list, key);
|
||||
TEST(was_in_set == (err == ERR_OK));
|
||||
|
||||
err = lfh_erase(&hash, key);
|
||||
TEST(was_in_set == (err == ERR_OK));
|
||||
}
|
||||
break;
|
||||
|
||||
case TA_SLEEP:
|
||||
usleep(sleep_duration_ms*1000);
|
||||
break;
|
||||
|
||||
default:
|
||||
DISPLAY_ERROR(L"invalid TA_* action");
|
||||
break;
|
||||
} // switch
|
||||
} // while !is_complete
|
||||
|
||||
atomic_add(&num_active_threads, -1);
|
||||
TEST(num_active_threads >= 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void multithreaded_torture_test()
|
||||
{
|
||||
// this test is randomized; we need deterministic results.
|
||||
srand(1);
|
||||
|
||||
static const double TEST_LENGTH = 30.; // [seconds]
|
||||
const double end_time = get_time() + TEST_LENGTH;
|
||||
is_complete = false;
|
||||
|
||||
WARN_ERR(lfl_init(&list));
|
||||
WARN_ERR(lfh_init(&hash, 128));
|
||||
WARN_ERR(pthread_mutex_init(&mutex, 0));
|
||||
|
||||
// spin off test threads (many, to force preemption)
|
||||
const uint NUM_THREADS = 16;
|
||||
for(uintptr_t i = 0; i < NUM_THREADS; i++)
|
||||
pthread_create(0, 0, thread_func, (void*)i);
|
||||
|
||||
// wait until time interval elapsed (if we get that far, all is well).
|
||||
while(get_time() < end_time)
|
||||
usleep(10*1000);
|
||||
|
||||
// signal and wait for all threads to complete (poor man's barrier -
|
||||
// those aren't currently implemented in wpthread).
|
||||
is_complete = true;
|
||||
while(num_active_threads > 0)
|
||||
usleep(5*1000);
|
||||
|
||||
lfl_free(&list);
|
||||
lfh_free(&hash);
|
||||
WARN_ERR(pthread_mutex_destroy(&mutex));
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
basic_single_threaded_test();
|
||||
multithreaded_torture_test();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -80,7 +80,7 @@ useful "payload" in the data structures is allocated when inserting each
|
||||
item: additional_bytes are appended. rationale: see struct Node definition.
|
||||
|
||||
since lock-free algorithms are subtle and easy to get wrong, an extensive
|
||||
self-test is included; #define SELF_TEST_ENABLED 1 to activate.
|
||||
self-test is included.
|
||||
|
||||
|
||||
terminology
|
||||
|
||||
@@ -38,6 +38,7 @@ static inline bool is_dir_sep(char c)
|
||||
|
||||
|
||||
// is s2 a subpath of s1, or vice versa?
|
||||
// (equal counts as subpath)
|
||||
bool path_is_subpath(const char* s1, const char* s2)
|
||||
{
|
||||
// make sure s1 is the shorter string
|
||||
@@ -233,6 +234,8 @@ const char* path_name_only(const char* path)
|
||||
return path;
|
||||
}
|
||||
|
||||
// TODO: take max of portableslash, nonportableslash
|
||||
|
||||
const char* name = slash+1;
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ extern LibError path_component_validate(const char* name);
|
||||
|
||||
|
||||
// is s2 a subpath of s1, or vice versa?
|
||||
// (equal counts as subpath)
|
||||
extern bool path_is_subpath(const char* s1, const char* s2);
|
||||
|
||||
// if path is invalid, return a descriptive error code, otherwise ERR_OK.
|
||||
|
||||
@@ -55,7 +55,7 @@ extern void comp_set_output(uintptr_t ctx, void* out, size_t out_size);
|
||||
// reliably estimate how much output space is needed.
|
||||
// raises a warning for decompression contexts because this operation
|
||||
// does not make sense there:
|
||||
// - decompression ratio is quite large - ballpark 1000x;
|
||||
// - worst-case decompression ratio is quite large - ballpark 1000x;
|
||||
// - exact uncompressed size is known to caller (via archive file header).
|
||||
// note: buffer is held until comp_free; it can be reused after a
|
||||
// comp_reset. this reduces malloc/free calls.
|
||||
|
||||
@@ -1286,78 +1286,3 @@ void file_cache_shutdown()
|
||||
cache_allocator.shutdown();
|
||||
block_mgr.shutdown();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_cache_allocator()
|
||||
{
|
||||
// allocated address -> its size
|
||||
typedef std::map<void*, size_t> AllocMap;
|
||||
AllocMap allocations;
|
||||
|
||||
// put allocator through its paces by allocating several times
|
||||
// its capacity (this ensures memory is reused)
|
||||
srand(1);
|
||||
size_t total_size_used = 0;
|
||||
while(total_size_used < 4*MAX_CACHE_SIZE)
|
||||
{
|
||||
size_t size = rand(1, MAX_CACHE_SIZE/4);
|
||||
total_size_used += size;
|
||||
void* p;
|
||||
// until successful alloc:
|
||||
for(;;)
|
||||
{
|
||||
p = cache_allocator.alloc(size);
|
||||
if(p)
|
||||
break;
|
||||
// out of room - remove a previous allocation
|
||||
// .. choose one at random
|
||||
size_t chosen_idx = (size_t)rand(0, (uint)allocations.size());
|
||||
AllocMap::iterator it = allocations.begin();
|
||||
for(; chosen_idx != 0; chosen_idx--)
|
||||
++it;
|
||||
cache_allocator.dealloc((u8*)it->first, it->second);
|
||||
allocations.erase(it);
|
||||
}
|
||||
|
||||
// must not already have been allocated
|
||||
TEST(allocations.find(p) == allocations.end());
|
||||
allocations[p] = size;
|
||||
}
|
||||
|
||||
// reset to virginal state
|
||||
cache_allocator.reset();
|
||||
}
|
||||
|
||||
static void test_file_cache()
|
||||
{
|
||||
// we need a unique address for file_cache_add, but don't want to
|
||||
// actually put it in the atom_fn storage (permanently clutters it).
|
||||
// just increment this pointer (evil but works since it's not used).
|
||||
// const char* atom_fn = (const char*)1;
|
||||
// give to file_cache
|
||||
// file_cache_add((FileIOBuf)p, size, atom_fn++);
|
||||
|
||||
file_cache_reset();
|
||||
TEST(file_cache.empty());
|
||||
|
||||
// note: even though everything has now been freed,
|
||||
// the freelists may be a bit scattered already.
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_cache_allocator();
|
||||
test_file_cache();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -199,6 +199,11 @@ LibError file_set_root_dir(const char* argv0, const char* rel_path)
|
||||
// arena, which is also more memory-efficient than the heap (no headers).
|
||||
static Pool atom_pool;
|
||||
|
||||
bool path_is_atom_fn(const char* fn)
|
||||
{
|
||||
return pool_contains(&atom_pool, (void*)fn);
|
||||
}
|
||||
|
||||
// allocate a copy of P_fn in our string pool. strings are equal iff
|
||||
// their addresses are equal, thus allowing fast comparison.
|
||||
//
|
||||
@@ -208,7 +213,7 @@ static Pool atom_pool;
|
||||
const char* file_make_unique_fn_copy(const char* P_fn)
|
||||
{
|
||||
// early out: if already an atom, return immediately.
|
||||
if(pool_contains(&atom_pool, (void*)P_fn))
|
||||
if(path_is_atom_fn(P_fn))
|
||||
return P_fn;
|
||||
|
||||
const size_t fn_len = strlen(P_fn);
|
||||
@@ -241,12 +246,6 @@ const char* file_make_unique_fn_copy(const char* P_fn)
|
||||
}
|
||||
|
||||
|
||||
bool path_is_atom_fn(const char* fn)
|
||||
{
|
||||
return pool_contains(&atom_pool, (void*)fn);
|
||||
}
|
||||
|
||||
|
||||
void path_init()
|
||||
{
|
||||
pool_create(&atom_pool, 8*MiB, POOL_VARIABLE_ALLOCS);
|
||||
|
||||
@@ -671,40 +671,3 @@ LibError zip_archive_finish(ZipArchive* za)
|
||||
za_mgr.release(za);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_fat_timedate_conversion()
|
||||
{
|
||||
// note: FAT time stores second/2, which means converting may
|
||||
// end up off by 1 second.
|
||||
|
||||
time_t t, converted_t;
|
||||
long dt;
|
||||
|
||||
t = time(0);
|
||||
converted_t = time_t_from_FAT(FAT_from_time_t(t));
|
||||
dt = converted_t-t; // disambiguate abs() parameter
|
||||
TEST(abs(dt) < 2);
|
||||
|
||||
t++;
|
||||
converted_t = time_t_from_FAT(FAT_from_time_t(t));
|
||||
dt = converted_t-t; // disambiguate abs() parameter
|
||||
TEST(abs(dt) < 2);
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_fat_timedate_conversion();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -875,7 +875,7 @@ LibError ogl_tex_get_size(Handle ht, uint* w, uint* h, uint* bpp)
|
||||
}
|
||||
|
||||
|
||||
// retrieve Tex.flags and the corresponding OpenGL format.
|
||||
// retrieve TexFlags and the corresponding OpenGL format.
|
||||
// the latter is determined during ogl_tex_upload and is 0 before that.
|
||||
// all params are optional and filled if non-NULL.
|
||||
LibError ogl_tex_get_format(Handle ht, uint* flags, GLenum* fmt)
|
||||
|
||||
+226
-194
@@ -34,6 +34,10 @@
|
||||
#include "tex_codec.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// validation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// be careful not to use other tex_* APIs here because they call us.
|
||||
LibError tex_validate(const Tex* t)
|
||||
{
|
||||
@@ -103,6 +107,54 @@ LibError tex_validate_plain_format(uint bpp, uint flags)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// mipmaps
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
|
||||
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx)
|
||||
{
|
||||
uint level_w = w, level_h = h;
|
||||
const u8* level_data = data;
|
||||
|
||||
// we iterate through the loop (necessary to skip over image data),
|
||||
// but do not actually call back until the requisite number of
|
||||
// levels have been skipped (i.e. level == 0).
|
||||
int level = -(int)levels_to_skip;
|
||||
if(levels_to_skip == -1)
|
||||
level = 0;
|
||||
|
||||
// until at level 1x1:
|
||||
for(;;)
|
||||
{
|
||||
// used to skip past this mip level in <data>
|
||||
const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
|
||||
|
||||
if(level >= 0)
|
||||
cb((uint)level, level_w, level_h, level_data, level_data_size, ctx);
|
||||
|
||||
level_data += level_data_size;
|
||||
|
||||
// 1x1 reached - done
|
||||
if(level_w == 1 && level_h == 1)
|
||||
break;
|
||||
level_w /= 2;
|
||||
level_h /= 2;
|
||||
// if the texture is non-square, one of the dimensions will become
|
||||
// 0 before the other. to satisfy OpenGL's expectations, change it
|
||||
// back to 1.
|
||||
if(level_w == 0) level_w = 1;
|
||||
if(level_h == 0) level_h = 1;
|
||||
level++;
|
||||
|
||||
// special case: no mipmaps, we were only supposed to call for
|
||||
// the base level
|
||||
if(levels_to_skip == TEX_BASE_LEVEL_ONLY)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct CreateLevelData
|
||||
{
|
||||
uint num_components;
|
||||
@@ -182,6 +234,34 @@ static void create_level(uint level, uint level_w, uint level_h,
|
||||
}
|
||||
|
||||
|
||||
static LibError add_mipmaps(Tex* t, uint w, uint h, uint bpp,
|
||||
void* new_data, size_t data_size)
|
||||
{
|
||||
// this code assumes the image is of POT dimension; we don't
|
||||
// go to the trouble of implementing image scaling because
|
||||
// the only place this is used (ogl_tex_upload) requires POT anyway.
|
||||
if(!is_pow2(w) || !is_pow2(h))
|
||||
WARN_RETURN(ERR_TEX_INVALID_SIZE);
|
||||
t->flags |= TEX_MIPMAPS; // must come before tex_img_size!
|
||||
const size_t mipmap_size = tex_img_size(t);
|
||||
Handle hm;
|
||||
const u8* mipmap_data = (const u8*)mem_alloc(mipmap_size, 4*KiB, 0, &hm);
|
||||
if(!mipmap_data)
|
||||
WARN_RETURN(ERR_NO_MEM);
|
||||
CreateLevelData cld = { bpp/8, w, h, (const u8*)new_data, data_size };
|
||||
tex_util_foreach_mipmap(w, h, bpp, mipmap_data, 0, 1, create_level, &cld);
|
||||
mem_free_h(t->hm);
|
||||
t->hm = hm;
|
||||
t->ofs = 0;
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// pixel format conversion (transformation)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
TIMER_ADD_CLIENT(tc_plain_transform);
|
||||
|
||||
// handles BGR and row flipping in "plain" format (see below).
|
||||
@@ -289,30 +369,52 @@ TIMER_ACCRUE(tc_plain_transform);
|
||||
t->ofs = 0;
|
||||
|
||||
if(!(t->flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS)
|
||||
{
|
||||
// this code assumes the image is of POT dimension; we don't
|
||||
// go to the trouble of implementing image scaling because
|
||||
// the only place this is used (ogl_tex_upload) requires POT anyway.
|
||||
if(!is_pow2(w) || !is_pow2(h))
|
||||
WARN_RETURN(ERR_TEX_INVALID_SIZE);
|
||||
t->flags |= TEX_MIPMAPS; // must come before tex_img_size!
|
||||
const size_t mipmap_size = tex_img_size(t);
|
||||
Handle hm;
|
||||
const u8* mipmap_data = (const u8*)mem_alloc(mipmap_size, 4*KiB, 0, &hm);
|
||||
if(!mipmap_data)
|
||||
WARN_RETURN(ERR_NO_MEM);
|
||||
CreateLevelData cld = { bpp/8, w, h, (const u8*)new_data, data_size };
|
||||
tex_util_foreach_mipmap(w, h, bpp, mipmap_data, 0, 1, create_level, &cld);
|
||||
mem_free_h(t->hm);
|
||||
t->hm = hm;
|
||||
t->ofs = 0;
|
||||
}
|
||||
RETURN_ERR(add_mipmaps(t, w, h, bpp, new_data, data_size));
|
||||
|
||||
CHECK_TEX(t);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
TIMER_ADD_CLIENT(tc_transform);
|
||||
|
||||
// change <t>'s pixel format by flipping the state of all TEX_* flags
|
||||
// that are set in transforms.
|
||||
LibError tex_transform(Tex* t, uint transforms)
|
||||
{
|
||||
TIMER_ACCRUE(tc_transform);
|
||||
CHECK_TEX(t);
|
||||
|
||||
const uint target_flags = t->flags ^ transforms;
|
||||
uint remaining_transforms;
|
||||
for(;;)
|
||||
{
|
||||
remaining_transforms = target_flags ^ t->flags;
|
||||
// we're finished (all required transforms have been done)
|
||||
if(remaining_transforms == 0)
|
||||
return ERR_OK;
|
||||
|
||||
LibError ret = tex_codec_transform(t, remaining_transforms);
|
||||
if(ret != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// last chance
|
||||
RETURN_ERR(plain_transform(t, remaining_transforms));
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
// change <t>'s pixel format to the new format specified by <new_flags>.
|
||||
// (note: this is equivalent to tex_transform(t, t->flags^new_flags).
|
||||
LibError tex_transform_to(Tex* t, uint new_flags)
|
||||
{
|
||||
// tex_transform takes care of validating <t>
|
||||
const uint transforms = t->flags ^ new_flags;
|
||||
return tex_transform(t, transforms);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// image orientation
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -369,55 +471,7 @@ bool tex_orientations_match(uint src_flags, uint dst_orientation)
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// util
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
|
||||
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx)
|
||||
{
|
||||
uint level_w = w, level_h = h;
|
||||
const u8* level_data = data;
|
||||
|
||||
// we iterate through the loop (necessary to skip over image data),
|
||||
// but do not actually call back until the requisite number of
|
||||
// levels have been skipped (i.e. level == 0).
|
||||
int level = -(int)levels_to_skip;
|
||||
if(levels_to_skip == -1)
|
||||
level = 0;
|
||||
|
||||
// until at level 1x1:
|
||||
for(;;)
|
||||
{
|
||||
// used to skip past this mip level in <data>
|
||||
const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8);
|
||||
|
||||
if(level >= 0)
|
||||
cb((uint)level, level_w, level_h, level_data, level_data_size, ctx);
|
||||
|
||||
level_data += level_data_size;
|
||||
|
||||
// 1x1 reached - done
|
||||
if(level_w == 1 && level_h == 1)
|
||||
break;
|
||||
level_w /= 2;
|
||||
level_h /= 2;
|
||||
// if the texture is non-square, one of the dimensions will become
|
||||
// 0 before the other. to satisfy OpenGL's expectations, change it
|
||||
// back to 1.
|
||||
if(level_w == 0) level_w = 1;
|
||||
if(level_h == 0) level_h = 1;
|
||||
level++;
|
||||
|
||||
// special case: no mipmaps, we were only supposed to call for
|
||||
// the base level
|
||||
if(levels_to_skip == -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// API
|
||||
// misc. API
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// indicate if <filename>'s extension is that of a texture format
|
||||
@@ -440,78 +494,6 @@ bool tex_is_known_extension(const char* filename)
|
||||
}
|
||||
|
||||
|
||||
// split out of tex_load to ease resource cleanup
|
||||
static LibError tex_load_impl(FileIOBuf file_, size_t file_size, Tex* t)
|
||||
{
|
||||
u8* file = (u8*)file_;
|
||||
const TexCodecVTbl* c;
|
||||
RETURN_ERR(tex_codec_for_header(file, file_size, &c));
|
||||
|
||||
// make sure the entire header has been read
|
||||
const size_t min_hdr_size = c->hdr_size(0);
|
||||
if(file_size < min_hdr_size)
|
||||
WARN_RETURN(ERR_INCOMPLETE_HEADER);
|
||||
const size_t hdr_size = c->hdr_size(file);
|
||||
if(file_size < hdr_size)
|
||||
WARN_RETURN(ERR_INCOMPLETE_HEADER);
|
||||
t->ofs = hdr_size;
|
||||
|
||||
DynArray da;
|
||||
RETURN_ERR(da_wrap_fixed(&da, file, file_size));
|
||||
|
||||
RETURN_ERR(c->decode(&da, t));
|
||||
|
||||
(void)da_free(&da); // for completeness only; just zeros <da>
|
||||
|
||||
// sanity checks
|
||||
if(!t->w || !t->h || t->bpp > 32)
|
||||
WARN_RETURN(ERR_TEX_FMT_INVALID);
|
||||
// .. note: decode() may have decompressed the image; cannot use file_size.
|
||||
size_t hm_size;
|
||||
(void)mem_get_ptr(t->hm, &hm_size);
|
||||
if(hm_size < t->ofs + tex_img_size(t))
|
||||
WARN_RETURN(ERR_TEX_INVALID_SIZE);
|
||||
|
||||
flip_to_global_orientation(t);
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
// MEM_DTOR -> file_buf_free adapter (used for mem_wrap-ping FileIOBuf)
|
||||
static void file_buf_dtor(void* p, size_t UNUSED(size), uintptr_t UNUSED(ctx))
|
||||
{
|
||||
(void)file_buf_free((FileIOBuf)p);
|
||||
}
|
||||
|
||||
// load the specified image from file into the given Tex object.
|
||||
// currently supports BMP, TGA, JPG, JP2, PNG, DDS.
|
||||
LibError tex_load(const char* fn, Tex* t, uint file_flags)
|
||||
{
|
||||
// load file
|
||||
FileIOBuf file; size_t file_size;
|
||||
// rationale: we need the Handle return value for Tex.hm - the data pointer
|
||||
// must be protected against being accidentally free-d in that case.
|
||||
|
||||
RETURN_ERR(vfs_load(fn, file, file_size, file_flags));
|
||||
Handle hm = mem_wrap((void*)file, file_size, 0, 0, 0, file_buf_dtor, 0, (void*)tex_load);
|
||||
t->hm = hm;
|
||||
LibError ret = tex_load_impl(file, file_size, t);
|
||||
if(ret < 0)
|
||||
{
|
||||
(void)tex_free(t);
|
||||
debug_warn("failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
CHECK_TEX(t);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
// store the given image data into a Tex object; this will be as if
|
||||
// it had been loaded via tex_load.
|
||||
//
|
||||
@@ -566,46 +548,7 @@ LibError tex_free(Tex* t)
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
TIMER_ADD_CLIENT(tc_transform);
|
||||
|
||||
// change <t>'s pixel format by flipping the state of all TEX_* flags
|
||||
// that are set in transforms.
|
||||
LibError tex_transform(Tex* t, uint transforms)
|
||||
{
|
||||
TIMER_ACCRUE(tc_transform);
|
||||
CHECK_TEX(t);
|
||||
|
||||
const uint target_flags = t->flags ^ transforms;
|
||||
uint remaining_transforms;
|
||||
for(;;)
|
||||
{
|
||||
remaining_transforms = target_flags ^ t->flags;
|
||||
// we're finished (all required transforms have been done)
|
||||
if(remaining_transforms == 0)
|
||||
return ERR_OK;
|
||||
|
||||
LibError ret = tex_codec_transform(t, remaining_transforms);
|
||||
if(ret != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// last chance
|
||||
RETURN_ERR(plain_transform(t, remaining_transforms));
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
// change <t>'s pixel format to the new format specified by <new_flags>.
|
||||
// (note: this is equivalent to tex_transform(t, t->flags^new_flags).
|
||||
LibError tex_transform_to(Tex* t, uint new_flags)
|
||||
{
|
||||
// tex_transform takes care of validating <t>
|
||||
const uint transforms = t->flags ^ new_flags;
|
||||
return tex_transform(t, transforms);
|
||||
}
|
||||
|
||||
|
||||
// getters
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// returns a pointer to the image data (pixels), taking into account any
|
||||
@@ -646,8 +589,6 @@ size_t tex_img_size(const Tex* t)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// return the minimum header size (i.e. offset to pixel data) of the
|
||||
// file format indicated by <fn>'s extension (that is all it need contain:
|
||||
// e.g. ".bmp"). returns 0 on error (i.e. no codec found).
|
||||
@@ -663,10 +604,58 @@ 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.
|
||||
LibError tex_write(Tex* t, const char* fn)
|
||||
//-----------------------------------------------------------------------------
|
||||
// read/write from memory and disk
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
LibError tex_decode(const u8* data, size_t data_size, MEM_DTOR dtor, Tex* t)
|
||||
{
|
||||
const TexCodecVTbl* c;
|
||||
RETURN_ERR(tex_codec_for_header(data, data_size, &c));
|
||||
|
||||
// make sure the entire header is available
|
||||
const size_t min_hdr_size = c->hdr_size(0);
|
||||
if(data_size < min_hdr_size)
|
||||
WARN_RETURN(ERR_INCOMPLETE_HEADER);
|
||||
const size_t hdr_size = c->hdr_size(data);
|
||||
if(data_size < hdr_size)
|
||||
WARN_RETURN(ERR_INCOMPLETE_HEADER);
|
||||
|
||||
// wrap pointer into a Handle; required for Tex.hm.
|
||||
// rationale: a Handle protects the texture memory from being
|
||||
// accidentally free-d.
|
||||
Handle hm = mem_wrap((void*)data, data_size, 0, 0, 0, dtor, 0, (void*)tex_decode);
|
||||
|
||||
t->hm = hm;
|
||||
t->ofs = hdr_size;
|
||||
|
||||
// for orthogonality, encode and decode both receive the memory as a
|
||||
// DynArray. package data into one and free it again after decoding:
|
||||
DynArray da;
|
||||
RETURN_ERR(da_wrap_fixed(&da, (u8*)data, data_size));
|
||||
|
||||
RETURN_ERR(c->decode(&da, t));
|
||||
|
||||
// note: not reached if decode fails. that's not a problem;
|
||||
// this call just zeroes <da> and could be left out.
|
||||
(void)da_free(&da);
|
||||
|
||||
// sanity checks
|
||||
if(!t->w || !t->h || t->bpp > 32)
|
||||
WARN_RETURN(ERR_TEX_FMT_INVALID);
|
||||
// .. note: can't use data_size - decode may have decompressed the image.
|
||||
size_t hm_size;
|
||||
(void)mem_get_ptr(t->hm, &hm_size);
|
||||
if(hm_size < t->ofs + tex_img_size(t))
|
||||
WARN_RETURN(ERR_TEX_INVALID_SIZE);
|
||||
|
||||
flip_to_global_orientation(t);
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
LibError tex_encode(Tex* t, const char* fn, DynArray* da)
|
||||
{
|
||||
CHECK_TEX(t);
|
||||
CHECK_ERR(tex_validate_plain_format(t->bpp, t->flags));
|
||||
@@ -676,31 +665,74 @@ LibError tex_write(Tex* t, const char* fn)
|
||||
// most likely the case if in_img == <hm's user pointer> + c->hdr_size(0).
|
||||
// this would make for zero-copy IO.
|
||||
|
||||
DynArray da;
|
||||
const size_t max_out_size = tex_img_size(t)*4 + 256*KiB;
|
||||
RETURN_ERR(da_alloc(&da, max_out_size));
|
||||
RETURN_ERR(da_alloc(da, max_out_size));
|
||||
|
||||
const TexCodecVTbl* c;
|
||||
CHECK_ERR(tex_codec_for_filename(fn, &c));
|
||||
|
||||
// encode into <da>
|
||||
LibError err = c->encode(t, &da);
|
||||
LibError err = c->encode(t, da);
|
||||
if(err < 0)
|
||||
{
|
||||
debug_printf("%s (%s) failed: %d", __func__, c->name, err);
|
||||
debug_warn("failed");
|
||||
goto fail;
|
||||
(void)da_free(da);
|
||||
WARN_RETURN(err);
|
||||
}
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MEM_DTOR -> file_buf_free adapter (used for mem_wrap-ping FileIOBuf)
|
||||
static void file_buf_dtor(void* p, size_t UNUSED(size), uintptr_t UNUSED(ctx))
|
||||
{
|
||||
(void)file_buf_free((FileIOBuf)p);
|
||||
}
|
||||
|
||||
// load the specified image from file into the given Tex object.
|
||||
// currently supports BMP, TGA, JPG, JP2, PNG, DDS.
|
||||
LibError tex_load(const char* fn, Tex* t, uint file_flags)
|
||||
{
|
||||
// load file
|
||||
FileIOBuf file; size_t file_size;
|
||||
RETURN_ERR(vfs_load(fn, file, file_size, file_flags));
|
||||
|
||||
LibError ret = tex_decode(file, file_size, file_buf_dtor, t);
|
||||
if(ret < 0)
|
||||
{
|
||||
(void)tex_free(t);
|
||||
WARN_RETURN(ret);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
CHECK_TEX(t);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
LibError tex_write(Tex* t, const char* fn)
|
||||
{
|
||||
DynArray da;
|
||||
RETURN_ERR(tex_encode(t, fn, &da));
|
||||
|
||||
// write to disk
|
||||
LibError ret = ERR_OK;
|
||||
{
|
||||
const size_t sector_aligned_size = round_up(da.cur_size, file_sector_size);
|
||||
(void)da_set_size(&da, sector_aligned_size);
|
||||
const ssize_t bytes_written = vfs_store(fn, da.base, da.pos);
|
||||
debug_assert(bytes_written == (ssize_t)da.pos);
|
||||
if(bytes_written > 0)
|
||||
debug_assert(bytes_written == (ssize_t)da.pos);
|
||||
else
|
||||
ret = (LibError)bytes_written;
|
||||
}
|
||||
|
||||
fail:
|
||||
(void)da_free(&da);
|
||||
return err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
+236
-132
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2004 Jan Wassenberg
|
||||
* Copyright (c) 2004-2005 Jan Wassenberg
|
||||
*
|
||||
* Redistribution and/or modification are also permitted under the
|
||||
* terms of the GNU General Public License as published by the
|
||||
@@ -21,7 +21,7 @@
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
/**
|
||||
|
||||
Introduction
|
||||
------------
|
||||
@@ -55,7 +55,7 @@ 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
|
||||
counterproductive. In this and similar cases, TexFlags indicates such
|
||||
deviations from the plain format.
|
||||
|
||||
|
||||
@@ -95,92 +95,124 @@ 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 describing the pixel format. these are to be interpreted as
|
||||
// deviations from "plain" format, i.e. uncompressed RGB.
|
||||
/**
|
||||
* flags describing the pixel format. these are to be interpreted as
|
||||
* deviations from "plain" format, i.e. uncompressed RGB.
|
||||
**/
|
||||
enum TexFlags
|
||||
{
|
||||
// flags & TEX_DXT is a field indicating compression.
|
||||
// if 0, the texture is uncompressed;
|
||||
// otherwise, it holds the S3TC type: 1,3,5 or DXT1A.
|
||||
// not converted by default - glCompressedTexImage2D receives
|
||||
// the compressed data.
|
||||
TEX_DXT = 0x7, // mask
|
||||
// we need a special value for DXT1a to avoid having to consider
|
||||
// flags & TEX_ALPHA to determine S3TC type.
|
||||
// the value is arbitrary; do not rely on it!
|
||||
/**
|
||||
* 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 receives
|
||||
* the compressed data.
|
||||
**/
|
||||
TEX_DXT = 0x7, // mask
|
||||
|
||||
/**
|
||||
* we need a special value for DXT1a to avoid having to consider
|
||||
* flags & TEX_ALPHA to determine S3TC type.
|
||||
* 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.
|
||||
/**
|
||||
* 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)
|
||||
/**
|
||||
* 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,
|
||||
|
||||
// indicates the image is 8bpp greyscale. this is required to
|
||||
// differentiate between alpha-only and intensity formats.
|
||||
// not converted by default - it's an acceptable format for OpenGL.
|
||||
/**
|
||||
* indicates the image is 8bpp greyscale. this is required to
|
||||
* differentiate between alpha-only and intensity formats.
|
||||
* not converted by default - it's an acceptable format for OpenGL.
|
||||
**/
|
||||
TEX_GREY = 0x20,
|
||||
|
||||
// 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.
|
||||
/**
|
||||
* 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 to match).
|
||||
* 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
|
||||
TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, /// mask
|
||||
|
||||
// indicates the image data includes mipmaps. they are stored from lowest
|
||||
// to highest (1x1), one after the other.
|
||||
// (conversion is not applicable here)
|
||||
/**
|
||||
* 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
|
||||
};
|
||||
|
||||
|
||||
// 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).
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
// 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).
|
||||
/**
|
||||
* 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.
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// see TexFlags and "Format Conversion" in docs.
|
||||
/// see TexFlags and "Format Conversion" in docs.
|
||||
uint flags : 16;
|
||||
};
|
||||
|
||||
|
||||
// set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which
|
||||
// all loaded images will automatically be converted
|
||||
// (excepting file formats that don't specify their orientation, i.e. DDS).
|
||||
// see "Default Orientation" in docs.
|
||||
/**
|
||||
* is the texture object valid and self-consistent?
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_validate(const Tex* t);
|
||||
|
||||
|
||||
/**
|
||||
* set the orientation to which all loaded images will
|
||||
* automatically be converted (excepting file formats that don't specify
|
||||
* their orientation, i.e. DDS). see "Default Orientation" in docs.
|
||||
* @param orientation either TEX_BOTTOM_UP or TEX_TOP_DOWN
|
||||
**/
|
||||
extern void tex_set_global_orientation(int orientation);
|
||||
|
||||
|
||||
@@ -188,38 +220,51 @@ extern void tex_set_global_orientation(int orientation);
|
||||
// open/close
|
||||
//
|
||||
|
||||
// indicate if <filename>'s extension is that of a texture format
|
||||
// supported by tex_load. case-insensitive.
|
||||
//
|
||||
// rationale: tex_load complains if the given file is of an
|
||||
// unsupported type. this API allows users to preempt that warning
|
||||
// (by checking the filename themselves), and also provides for e.g.
|
||||
// enumerating only images in a file picker.
|
||||
// an alternative might be a flag to suppress warning about invalid files,
|
||||
// but this is open to misuse.
|
||||
extern bool tex_is_known_extension(const char* filename);
|
||||
|
||||
// load the specified image from file into the given Tex object.
|
||||
// currently supports BMP, TGA, JPG, JP2, PNG, DDS.
|
||||
/**
|
||||
* load the specified image from file into a Tex object.
|
||||
*
|
||||
* FYI, currently BMP, TGA, JPG, JP2, PNG, DDS are supported - but don't
|
||||
* rely on this (not all codecs may be included).
|
||||
*
|
||||
* @param fn filename
|
||||
* @param t output texture object
|
||||
* @param file_flags additional flags for vfs_load
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_load(const char* fn, Tex* t, uint file_flags = 0);
|
||||
|
||||
// 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.
|
||||
/**
|
||||
* 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 free
|
||||
* it themselves (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.
|
||||
*
|
||||
* @param w, h pixel dimensions
|
||||
* @param bpp bits per pixel
|
||||
* @param flags TexFlags
|
||||
* @param img texture data. note: size is calculated from other params.
|
||||
* @param t output texture object.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t);
|
||||
|
||||
// free all resources associated with the image and make further
|
||||
// use of it impossible.
|
||||
/**
|
||||
* free all resources associated with the image and make further
|
||||
* use of it impossible.
|
||||
*
|
||||
* @param t texture object (note: not zeroed afterwards; see impl)
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_free(Tex* t);
|
||||
|
||||
|
||||
@@ -227,12 +272,21 @@ extern LibError tex_free(Tex* t);
|
||||
// modify image
|
||||
//
|
||||
|
||||
// change <t>'s pixel format by flipping the state of all TEX_* flags
|
||||
// that are set in transforms.
|
||||
/**
|
||||
* change <t>'s pixel format.
|
||||
*
|
||||
* @param transforms TexFlags that are to be flipped.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_transform(Tex* t, uint transforms);
|
||||
|
||||
// change <t>'s pixel format to the new format specified by <new_flags>.
|
||||
// (note: this is equivalent to tex_transform(t, t->flags^new_flags).
|
||||
/**
|
||||
* change <t>'s pixel format (2nd version)
|
||||
* (note: this is equivalent to tex_transform(t, t->flags^new_flags).
|
||||
*
|
||||
* @param new_flags desired new value of TexFlags.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_transform_to(Tex* t, uint new_flags);
|
||||
|
||||
|
||||
@@ -240,65 +294,115 @@ extern LibError tex_transform_to(Tex* t, uint new_flags);
|
||||
// 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.
|
||||
/**
|
||||
* rationale: 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.
|
||||
/**
|
||||
* return a pointer to the image data (pixels), taking into account any
|
||||
* header(s) that may come before it. see Tex.hm comment above.
|
||||
*
|
||||
* @param t input texture object
|
||||
* @return pointer to data returned by mem_get_ptr (holds reference)!
|
||||
**/
|
||||
extern u8* tex_get_data(const Tex* t);
|
||||
|
||||
// return total byte size of the image pixels. (including mipmaps!)
|
||||
// this is preferable to calculating manually because it's
|
||||
// less error-prone (e.g. confusing bits_per_pixel with bytes).
|
||||
/**
|
||||
* return total byte size of the image pixels. (including mipmaps!)
|
||||
* rationale: this is preferable to calculating manually because it's
|
||||
* less error-prone (e.g. confusing bits_per_pixel with bytes).
|
||||
*
|
||||
* @param t input texture object
|
||||
* @return size [bytes]
|
||||
**/
|
||||
extern size_t tex_img_size(const Tex* t);
|
||||
|
||||
|
||||
/**
|
||||
* special value for levels_to_skip: the callback will only be called
|
||||
* for the base mipmap level (i.e. 100%)
|
||||
**/
|
||||
const int TEX_BASE_LEVEL_ONLY = -1;
|
||||
|
||||
/**
|
||||
* callback function for each mipmap level.
|
||||
*
|
||||
* @param level number; 0 for base level (i.e. 100%), or the first one
|
||||
* in case some were skipped.
|
||||
* @param level_w, level_h pixel dimensions (powers of 2, never 0)
|
||||
* @param level_data the level's texels
|
||||
* @param level_data_size [bytes]
|
||||
* @param ctx passed through from tex_util_foreach_mipmap.
|
||||
**/
|
||||
typedef void (*MipmapCB)(uint level, uint level_w, uint level_h,
|
||||
const u8* level_data, size_t level_data_size, void* ctx);
|
||||
|
||||
/**
|
||||
* for a series of mipmaps stored from base to highest, call back for
|
||||
* each level.
|
||||
*
|
||||
* @param w, h pixel dimensions
|
||||
* @param bpp bits per pixel
|
||||
* @param data series of mipmaps
|
||||
* @param levels_to_skip number of levels (counting from base) to skip, or
|
||||
* TEX_BASE_LEVEL_ONLY to only call back for the base image.
|
||||
* rationale: this avoids needing to special case for images with or
|
||||
* without mipmaps.
|
||||
* @param data_padding minimum pixel dimensions of mipmap levels.
|
||||
* this is used in S3TC images, where each level is actually stored in
|
||||
* 4x4 blocks. usually 1 to indicate levels are consecutive.
|
||||
* @param cb MipmapCB to call
|
||||
* @param ctx extra data to pass to cb
|
||||
**/
|
||||
extern void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
|
||||
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx);
|
||||
|
||||
|
||||
//
|
||||
// image writing
|
||||
//
|
||||
|
||||
// return the minimum header size (i.e. offset to pixel data) of the
|
||||
// file format indicated by <fn>'s extension (that is all it need contain:
|
||||
// e.g. ".bmp"). returns 0 on error (i.e. no codec found).
|
||||
// this can be used to optimize calls to tex_write: when allocating the
|
||||
// buffer that will hold the image, allocate this much extra and
|
||||
// pass the pointer as base+hdr_size. this allows writing the header
|
||||
// directly into the output buffer and makes for zero-copy IO.
|
||||
/**
|
||||
* is the file's extension that of a texture format supported by tex_load?
|
||||
*
|
||||
* rationale: tex_load complains if the given file is of an
|
||||
* unsupported type. this API allows users to preempt that warning
|
||||
* (by checking the filename themselves), and also provides for e.g.
|
||||
* enumerating only images in a file picker.
|
||||
* an alternative might be a flag to suppress warning about invalid files,
|
||||
* but this is open to misuse.
|
||||
*
|
||||
* @param filename only the extension (that after '.') is used. case-insensitive.
|
||||
* @return bool
|
||||
**/
|
||||
extern bool tex_is_known_extension(const char* filename);
|
||||
|
||||
/**
|
||||
* return the minimum header size (i.e. offset to pixel data) of the
|
||||
* file format corresponding to the filename.
|
||||
*
|
||||
* rationale: 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.
|
||||
*
|
||||
* @param fn filename; only the extension (that after '.') is used.
|
||||
* case-insensitive.
|
||||
* @return size [bytes] or 0 on error (i.e. no codec found).
|
||||
**/
|
||||
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.
|
||||
/**
|
||||
* write the specified texture to disk.
|
||||
*
|
||||
* @param t input texture object. note: cannot be made const because the
|
||||
* image may have to be transformed to write it out in the format
|
||||
* determined by <fn>'s extension.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_write(Tex* t, const char* fn);
|
||||
|
||||
|
||||
// internal use only:
|
||||
extern LibError tex_validate(const Tex* t);
|
||||
|
||||
// check if the given texture format is acceptable: 8bpp grey,
|
||||
// 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
|
||||
// basically, this is the "plain" format understood by all codecs and
|
||||
// tex_codec_plain_transform.
|
||||
extern LibError tex_validate_plain_format(uint bpp, uint flags);
|
||||
|
||||
|
||||
// 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)
|
||||
extern bool tex_orientations_match(uint src_flags, uint dst_orientation);
|
||||
|
||||
typedef void (*MipmapCB)(uint level, uint level_w, uint level_h,
|
||||
const u8* level_data, size_t level_data_size, void* ctx);
|
||||
|
||||
// special value for levels_to_skip: the callback will only be called
|
||||
// for the base mipmap level (i.e. 100%)
|
||||
const int TEX_BASE_LEVEL_ONLY = -1;
|
||||
|
||||
extern void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data,
|
||||
int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx);
|
||||
|
||||
|
||||
#endif // TEX_H__
|
||||
#endif // TEX_H__
|
||||
|
||||
@@ -85,6 +85,17 @@ LibError tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVT
|
||||
}
|
||||
|
||||
|
||||
const TexCodecVTbl* tex_codec_next(const TexCodecVTbl* prev_codec)
|
||||
{
|
||||
// first time
|
||||
if(!prev_codec)
|
||||
return codecs;
|
||||
// middle of list: return next (can be 0 to indicate end of list)
|
||||
else
|
||||
return prev_codec->next;
|
||||
}
|
||||
|
||||
|
||||
LibError tex_codec_transform(Tex* t, uint transforms)
|
||||
{
|
||||
LibError ret = INFO_TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
@@ -24,89 +24,219 @@
|
||||
#define TEX_CODEC_H__
|
||||
|
||||
#include "tex.h"
|
||||
#include "tex_internal.h" // for codec's convenience
|
||||
#include "lib/allocators.h"
|
||||
|
||||
// rationale: no C++ to allow us to store const char* name in vtbl.
|
||||
|
||||
/**
|
||||
* virtual method table for TexCodecs.
|
||||
* rationale: this works in C and also allows storing name and next in vtbl.
|
||||
* 'template method'-style interface to increase code reuse and
|
||||
* simplify writing new codecs.
|
||||
**/
|
||||
struct TexCodecVTbl
|
||||
{
|
||||
// 'template method' to increase code reuse and simplify writing new codecs
|
||||
/**
|
||||
* decode the file into a Tex structure.
|
||||
*
|
||||
* @param da input data array (not const, because the texture
|
||||
* may have to be flipped in-place - see "texture orientation").
|
||||
* its size is guaranteed to be >= 4.
|
||||
* (usually enough to compare the header's "magic" field;
|
||||
* anyway, no legitimate file will be smaller)
|
||||
* @param t output texture object
|
||||
* @return LibError
|
||||
**/
|
||||
LibError (*decode)(DynArray * restrict da, Tex * restrict t);
|
||||
|
||||
// pointers aren't const, because the textures
|
||||
// may have to be flipped in-place - see "texture orientation".
|
||||
// size is guaranteed to be >= 4.
|
||||
// (usually enough to compare the header's "magic" field;
|
||||
// anyway, no legitimate file will be smaller)
|
||||
LibError (*decode)(DynArray* restrict da, Tex* restrict t);
|
||||
|
||||
// rationale: some codecs cannot calculate the output size beforehand
|
||||
// (e.g. PNG output via libpng); we therefore require each one to
|
||||
// allocate memory itself and return the pointer.
|
||||
//
|
||||
// note: <t> cannot be made const because encoding may require a
|
||||
// tex_transform.
|
||||
LibError (*encode)(Tex* restrict t, DynArray* restrict da);
|
||||
/**
|
||||
* encode the texture data into the codec's file format (in memory).
|
||||
*
|
||||
* @param t input texture object. note: non-const because encoding may
|
||||
* require a tex_transform.
|
||||
* @param da output data array, allocated by codec.
|
||||
* rationale: some codecs cannot calculate the output size beforehand
|
||||
* (e.g. PNG output via libpng), so the output memory cannot be allocated
|
||||
* by the caller.
|
||||
* @return LibError
|
||||
**/
|
||||
LibError (*encode)(Tex * restrict t, DynArray * restrict da);
|
||||
|
||||
/**
|
||||
* transform the texture's pixel format.
|
||||
*
|
||||
* @param t texture object
|
||||
* @param transforms: OR-ed combination of TEX_* flags that are to
|
||||
* be changed. note: the codec needs only handle situations specific
|
||||
* to its format; generic pixel format transforms are handled by
|
||||
* the caller.
|
||||
**/
|
||||
LibError (*transform)(Tex* t, uint transforms);
|
||||
|
||||
// only guaranteed 4 bytes!
|
||||
bool (*is_hdr)(const u8* file);
|
||||
/**
|
||||
* indicate if the data appears to be an instance of this codec's header,
|
||||
* i.e. can this codec decode it?
|
||||
*
|
||||
* @param file input data; only guaranteed to be 4 bytes!
|
||||
* (this should be enough to examine the header's 'magic' field)
|
||||
* @return bool
|
||||
**/
|
||||
bool (*is_hdr)(const u8 * file);
|
||||
|
||||
// precondition: ext is valid string
|
||||
// ext doesn't include '.'; just compare against e.g. "png"
|
||||
// must compare case-insensitive!
|
||||
/**
|
||||
* is the extension that of a file format supported by this codec?
|
||||
*
|
||||
* rationale: cannot just return the extension string and have
|
||||
* caller compare it (-> smaller code) because a codec's file format
|
||||
* may have several valid extensions (e.g. jpg and jpeg).
|
||||
*
|
||||
* @param ext non-NULL extension string; does not contain '.'.
|
||||
* must be compared as case-insensitive.
|
||||
* @return bool
|
||||
**/
|
||||
bool (*is_ext)(const char* ext);
|
||||
|
||||
/**
|
||||
* return size of the file header supported by this codec.
|
||||
*
|
||||
* @param file the specific header to return length of (taking its
|
||||
* variable-length fields into account). if NULL, return minimum
|
||||
* guaranteed header size, i.e. the header without any
|
||||
* variable-length fields.
|
||||
* @return size [bytes]
|
||||
**/
|
||||
size_t (*hdr_size)(const u8* file);
|
||||
|
||||
/**
|
||||
* name of codec for debug purposes. typically set via TEX_CODEC_REGISTER.
|
||||
**/
|
||||
const char* name;
|
||||
|
||||
// intrusive linked-list of codecs: more convenient than fixed-size
|
||||
// static storage.
|
||||
/**
|
||||
* intrusive linked-list of codecs: more convenient than fixed-size
|
||||
* static storage.
|
||||
* set by caller; should be initialized to NULL.
|
||||
**/
|
||||
const TexCodecVTbl* next;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* build codec vtbl and register it. the codec will be queried for future
|
||||
* texture load requests. call order is undefined, but since each codec
|
||||
* only steps up if it can handle the given format, this is not a problem.
|
||||
*
|
||||
* @param name identifier of codec (not string!). used to bind 'member'
|
||||
* functions prefixed with it to the vtbl, and as the TexCodecVTbl name.
|
||||
* it should also mirror the default file extension (e.g. dds) -
|
||||
* this is relied upon (but verified) in the self-test.
|
||||
*
|
||||
* usage: at file scope within the source file containing the codec's methods.
|
||||
**/
|
||||
#define TEX_CODEC_REGISTER(name)\
|
||||
static TexCodecVTbl vtbl = { name##_decode, name##_encode, name##_transform, name##_is_hdr, name##_is_ext, name##_hdr_size, #name};\
|
||||
static TexCodecVTbl vtbl = \
|
||||
{\
|
||||
name##_decode, name##_encode, name##_transform,\
|
||||
name##_is_hdr, name##_is_ext, name##_hdr_size,\
|
||||
#name\
|
||||
};\
|
||||
static int dummy = tex_codec_register(&vtbl);
|
||||
|
||||
|
||||
// add this vtbl to the codec list. called at NLSO init time by the
|
||||
// TEX_CODEC_REGISTER in each codec file. note that call order and therefore
|
||||
// order in the list is undefined, but since each codec only steps up if it
|
||||
// can handle the given format, this is not a problem.
|
||||
//
|
||||
// returns int to alloc calling from a macro at file scope.
|
||||
/**
|
||||
* add this vtbl to the codec list. called at NLSO init time by the
|
||||
* TEX_CODEC_REGISTER in each codec file.
|
||||
* order in list is unspecified; see TEX_CODEC_REGISTER.
|
||||
*
|
||||
* @param c pointer to vtbl.
|
||||
* @return int (allows calling from a macro at file scope; value is not used)
|
||||
**/
|
||||
extern int tex_codec_register(TexCodecVTbl* c);
|
||||
|
||||
|
||||
// find codec that recognizes the desired output file extension,
|
||||
// or return ERR_UNKNOWN_FORMAT if unknown.
|
||||
// note: does not raise a warning because it is used by
|
||||
// tex_is_known_extension.
|
||||
/**
|
||||
* find codec that recognizes the desired output file extension.
|
||||
*
|
||||
* @param fn filename; only the extension (that after '.') is used.
|
||||
* case-insensitive.
|
||||
* @param c (out) vtbl of responsible codec
|
||||
* @return LibError; ERR_UNKNOWN_FORMAT (without warning, because this is
|
||||
* called by tex_is_known_extension) if no codec indicates they can
|
||||
* handle the given extension.
|
||||
**/
|
||||
extern LibError tex_codec_for_filename(const char* fn, const TexCodecVTbl** c);
|
||||
|
||||
// find codec that recognizes the header's magic field
|
||||
extern LibError tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl** c);
|
||||
/**
|
||||
* find codec that recognizes the header's magic field.
|
||||
*
|
||||
* @param data typically contents of file, but need only include the
|
||||
* (first 4 bytes of) header.
|
||||
* @param data_size [bytes]
|
||||
* @param c (out) vtbl of responsible codec
|
||||
* @return LibError; ERR_UNKNOWN_FORMAT if no codec indicates they can
|
||||
* handle the given format (header).
|
||||
**/
|
||||
extern LibError tex_codec_for_header(const u8* data, size_t data_size, const TexCodecVTbl** c);
|
||||
|
||||
/**
|
||||
* enumerate all registered codecs.
|
||||
*
|
||||
* used by self-test to test each one of them in turn.
|
||||
*
|
||||
* @param prev_codec the last codec returned by this function.
|
||||
* pass 0 the first time.
|
||||
* note: this routine is stateless and therefore reentrant.
|
||||
* @return the next codec, or 0 if all have been returned.
|
||||
**/
|
||||
extern const TexCodecVTbl* tex_codec_next(const TexCodecVTbl* prev_codec);
|
||||
|
||||
/**
|
||||
* transform the texture's pixel format.
|
||||
* tries each codec's transform method once, or until one indicates success.
|
||||
*
|
||||
* @param t texture object
|
||||
* @param transforms: OR-ed combination of TEX_* flags that are to
|
||||
* be changed.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_codec_transform(Tex* t, uint transforms);
|
||||
|
||||
|
||||
// allocate an array of row pointers that point into the given texture data.
|
||||
// <file_orientation> indicates whether the file format is top-down or
|
||||
// bottom-up; the row array is inverted if necessary to match global
|
||||
// orienatation. (this is more efficient than "transforming" later)
|
||||
//
|
||||
// used by PNG and JPG codecs; caller must free() rows when done.
|
||||
//
|
||||
// note: we don't allocate the data param ourselves because this function is
|
||||
// needed for encoding, too (where data is already present).
|
||||
/**
|
||||
* allocate an array of row pointers that point into the given texture data.
|
||||
* for texture decoders that support output via row pointers (e.g. PNG),
|
||||
* this allows flipping the image vertically (useful when matching bottom-up
|
||||
* textures to a global orientation) directly, which is much more
|
||||
* efficient than transforming later via copying all pixels.
|
||||
*
|
||||
* @param data the texture data into which row pointers will point.
|
||||
* note: we don't allocate it here because this function is
|
||||
* needed for encoding, too (where data is already present).
|
||||
* @param h height [pixels] of texture.
|
||||
* @param pitch size [bytes] of one texture row, i.e. width*bytes_per_pixel.
|
||||
* @param src_flags TexFlags of source texture. used to extract its
|
||||
* orientation.
|
||||
* @param dst_orientation desired orientation of the output data.
|
||||
* can be one of TEX_BOTTOM_UP, TEX_TOP_DOWN, or 0 for the
|
||||
* "global orientation".
|
||||
* depending on src and dst, the row array is flipped if necessary.
|
||||
* @param rows (out) array of row pointers; caller must free() it when done.
|
||||
* @return LibError
|
||||
**/
|
||||
typedef const u8* RowPtr;
|
||||
typedef RowPtr* RowArray;
|
||||
extern LibError tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
uint src_flags, uint dst_orientation, RowArray& rows);
|
||||
|
||||
/**
|
||||
* apply transforms and then copy header and image into output buffer.
|
||||
*
|
||||
* @param t input texture object
|
||||
* @param transforms transformations to be applied to pixel format
|
||||
* @param hdr header data
|
||||
* @param hdr_size [bytes]
|
||||
* @param da output data array (will be expanded as necessary)
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da);
|
||||
|
||||
#endif // #ifndef TEX_CODEC_H__
|
||||
#endif // #ifndef TEX_CODEC_H__
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : tex_internal.h
|
||||
* Project : 0 A.D.
|
||||
* Description : private texture loader helper functions
|
||||
*
|
||||
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2006 Jan Wassenberg
|
||||
*
|
||||
* Redistribution and/or modification are also permitted under the
|
||||
* terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation (version 2 or later, at your option).
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
#ifndef TEX_INTERNAL_H__
|
||||
#define TEX_INTERNAL_H__
|
||||
|
||||
#include "../mem.h" // MEM_DTOR
|
||||
#include "lib/allocators.h" // DynArray
|
||||
|
||||
/**
|
||||
* check if the given texture format is acceptable: 8bpp grey,
|
||||
* 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
|
||||
* basically, this is the "plain" format understood by all codecs and
|
||||
* tex_codec_plain_transform.
|
||||
* @param bpp bits per pixel
|
||||
* @param flags TexFlags
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_validate_plain_format(uint bpp, uint flags);
|
||||
|
||||
|
||||
/**
|
||||
* indicate if the two vertical orientations match.
|
||||
*
|
||||
* used by tex_codec.
|
||||
*
|
||||
* @param src_flags TexFlags, used to extract the orientation.
|
||||
* we ask for this instead of src_orientation so callers don't have to
|
||||
* mask off TEX_ORIENTATION.
|
||||
* @param dst_orientation orientation to compare against.
|
||||
* can be one of TEX_BOTTOM_UP, TEX_TOP_DOWN, or 0 for the
|
||||
* "global orientation".
|
||||
* @return bool
|
||||
**/
|
||||
extern bool tex_orientations_match(uint src_flags, uint dst_orientation);
|
||||
|
||||
|
||||
/**
|
||||
* decode an in-memory texture file into texture object.
|
||||
*
|
||||
* split out of tex_load to ease resource cleanup and allow
|
||||
* decoding images without needing to write out to disk.
|
||||
*
|
||||
* @param data input data
|
||||
* @param data_size its size [bytes]
|
||||
* @param dtor the function used to release it when the texture object is
|
||||
* freed (can be NULL). note: this is necessary because the Tex object
|
||||
* assumes ownership (necessary due to Tex.hm).
|
||||
* @param t output texture object.
|
||||
* @return LibError.
|
||||
**/
|
||||
extern LibError tex_decode(const u8* data, size_t data_size, MEM_DTOR dtor, Tex* t);
|
||||
|
||||
/**
|
||||
* encode a texture into a memory buffer in the desired file format.
|
||||
*
|
||||
* @param t input texture object
|
||||
* @param fn filename; only used to determine the desired file format
|
||||
* (via extension)
|
||||
* @param da output memory array. allocated here; caller must free it
|
||||
* when no longer needed. invalid unless function succeeds.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError tex_encode(Tex* t, const char* fn, DynArray* da);
|
||||
|
||||
#endif // #ifndef TEX_INTERNAL_H__
|
||||
+208
-131
@@ -26,9 +26,7 @@
|
||||
|
||||
#include "../handle.h"
|
||||
|
||||
/*
|
||||
|
||||
[KEEP IN SYNC WITH WIKI]
|
||||
/**
|
||||
|
||||
overview
|
||||
--------
|
||||
@@ -84,24 +82,32 @@ terminology
|
||||
"sound instances" store playback parameters (e.g. position), and
|
||||
reference the (centrally cached) "sound data" that will be played.
|
||||
|
||||
*/
|
||||
**/
|
||||
|
||||
|
||||
//
|
||||
// device enumeration
|
||||
//
|
||||
|
||||
// prepare to enumerate all device names (this resets the list returned by
|
||||
// snd_dev_next). return 0 on success, otherwise -1 (only if the requisite
|
||||
// OpenAL extension isn't available). on failure, a "cannot enum device"
|
||||
// message should be presented to the user, and snd_dev_set need not be
|
||||
// called; OpenAL will use its default device.
|
||||
// may be called each time the device list is needed.
|
||||
/**
|
||||
* prepare to enumerate all device names (this resets the list returned by
|
||||
* snd_dev_next).
|
||||
* may be called each time the device list is needed.
|
||||
*
|
||||
* @return LibError; fails iff the requisite OpenAL extension isn't available.
|
||||
* in that case, a "cannot enum device" message should be displayed, but
|
||||
* snd_dev_set need not be called; OpenAL will use its default device.
|
||||
**/
|
||||
extern LibError snd_dev_prepare_enum();
|
||||
|
||||
// return the next device name, or 0 if all have been returned.
|
||||
// do not call unless snd_dev_prepare_enum succeeded!
|
||||
// not thread-safe! (static data from snd_dev_prepare_enum is used)
|
||||
/**
|
||||
* get next device name in list.
|
||||
*
|
||||
* do not call unless snd_dev_prepare_enum succeeded!
|
||||
* not thread-safe! (static data from snd_dev_prepare_enum is used)
|
||||
*
|
||||
* @return device name string, or 0 if all have been returned.
|
||||
**/
|
||||
extern const char* snd_dev_next();
|
||||
|
||||
|
||||
@@ -109,32 +115,45 @@ extern const char* snd_dev_next();
|
||||
// sound system setup
|
||||
//
|
||||
|
||||
// tell OpenAL to use the specified device in future.
|
||||
// name = 0 reverts to OpenAL's default choice, which will also
|
||||
// be used if this routine is never called.
|
||||
//
|
||||
// the device name is typically taken from a config file at init-time;
|
||||
// the snd_dev* enumeration routines below are used to present a list
|
||||
// of choices to the user in the options screen.
|
||||
//
|
||||
// if OpenAL hasn't yet been initialized (i.e. no sounds have been opened),
|
||||
// this just stores the device name for use when init does occur.
|
||||
// note: we can't check now if it's invalid (if so, init will fail).
|
||||
// otherwise, we shut OpenAL down (thereby stopping all sounds) and
|
||||
// re-initialize with the new device. that's fairly time-consuming,
|
||||
// so preferably call this routine before sounds are loaded.
|
||||
//
|
||||
// return 0 on success, or the status returned by OpenAL re-init.
|
||||
/**
|
||||
* tell OpenAL to use the specified device in future.
|
||||
*
|
||||
* @param alc_new_dev_name device name string. if 0, revert to
|
||||
* OpenAL's default choice, which will also be used if
|
||||
* this routine is never called.
|
||||
* the device name is typically taken from a config file at init-time;
|
||||
* the snd_dev* enumeration routines above are used to present a list
|
||||
* of choices to the user in the options screen.
|
||||
*
|
||||
* if OpenAL hasn't yet been initialized (i.e. no sounds have been opened),
|
||||
* this just stores the device name for use when init does occur.
|
||||
* note: we can't check now if it's invalid (if so, init will fail).
|
||||
* otherwise, we shut OpenAL down (thereby stopping all sounds) and
|
||||
* re-initialize with the new device. that's fairly time-consuming,
|
||||
* so preferably call this routine before sounds are loaded.
|
||||
*
|
||||
* @return LibError (the status returned by OpenAL re-init)
|
||||
**/
|
||||
extern LibError snd_dev_set(const char* alc_new_dev_name);
|
||||
|
||||
// set maximum number of voices to play simultaneously,
|
||||
// to reduce mixing cost on low-end systems.
|
||||
// return 0 on success, or 1 if limit was ignored
|
||||
// (e.g. if higher than an implementation-defined limit anyway).
|
||||
/**
|
||||
* set maximum number of voices to play simultaneously;
|
||||
* this can be used to reduce mixing cost on low-end systems.
|
||||
*
|
||||
* @param cap maximum number of voices. ignored if higher than
|
||||
* an implementation-defined limit anyway.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_max_voices(uint cap);
|
||||
|
||||
// set amplitude modifier, which is effectively applied to all sounds.
|
||||
// must be non-negative; 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
|
||||
/**
|
||||
* set amplitude modifier, which is effectively applied to all sounds.
|
||||
* this is akin to a global "volume" control.
|
||||
*
|
||||
* @param gain amplitude modifier. must be non-negative;
|
||||
* 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_master_gain(float gain);
|
||||
|
||||
|
||||
@@ -142,104 +161,148 @@ extern LibError snd_set_master_gain(float gain);
|
||||
// sound instance
|
||||
//
|
||||
|
||||
// open and return a handle to a sound instance.
|
||||
//
|
||||
// if <snd_fn> is a text file (extension "txt"), it is assumed
|
||||
// to be a definition file containing the sound file name and
|
||||
// its gain (0.0 .. 1.0).
|
||||
// otherwise, <snd_fn> is taken to be the sound file name and
|
||||
// gain is set to the default of 1.0 (no attenuation).
|
||||
//
|
||||
// is_stream (default false) forces the sound to be opened as a stream:
|
||||
// opening is faster, it won't be kept in memory, but only one instance
|
||||
// can be open at a time.
|
||||
/**
|
||||
* open and return a handle to a sound instance.
|
||||
* this loads the sound data and makes it ready for other snd_* APIs.
|
||||
*
|
||||
* @param snd_fn input filename. if a text file (extension "txt"), it is
|
||||
* assumed to be a definition file containing the sound file name and
|
||||
* its gain (0.0 .. 1.0).
|
||||
* otherwise, it is taken to be the sound file name and
|
||||
* gain is set to the default of 1.0 (no attenuation).
|
||||
*
|
||||
* @param is_stream (default false) forces the sound to be opened as a
|
||||
* stream: opening is faster, it won't be kept in memory, but
|
||||
* only one instance can be open at a time.
|
||||
* @return Handle or LibError
|
||||
**/
|
||||
extern Handle snd_open(const char* snd_fn, bool stream = false);
|
||||
|
||||
// close the sound <hs> and set hs to 0. if it was playing,
|
||||
// it will be stopped. sounds are closed automatically when done
|
||||
// playing; this is provided for completeness only.
|
||||
/**
|
||||
* close the sound instance. if it was playing, it will be stopped.
|
||||
*
|
||||
* rationale: sounds are already closed automatically when done playing;
|
||||
* this API is provided for completeness only.
|
||||
*
|
||||
* @param hs Handle to sound instance. zeroed afterwards.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_free(Handle& hs);
|
||||
|
||||
// request the sound <hs> be played. once done playing, the sound is
|
||||
// automatically closed (allows fire-and-forget play code).
|
||||
// if no hardware voice is available, this sound may not be played at all,
|
||||
// or in the case of looped sounds, start later.
|
||||
// priority (min 0 .. max 1, default 0) indicates which sounds are
|
||||
// considered more important; this is attenuated by distance to the
|
||||
// listener (see snd_update).
|
||||
/**
|
||||
* start playing the sound.
|
||||
*
|
||||
* Notes:
|
||||
* <UL>
|
||||
* <LI> once done playing, the sound is automatically closed (allows
|
||||
* fire-and-forget play code).
|
||||
* <LI> if no hardware voice is available, this sound may not be
|
||||
* played at all, or in the case of looped sounds, start later.
|
||||
* </UL>
|
||||
*
|
||||
* @param priority (min 0 .. max 1, default 0) indicates which sounds are
|
||||
* considered more important (i.e. will override others when no hardware
|
||||
* voices are available). the static priority is attenuated by
|
||||
* distance to the listener; see snd_update.
|
||||
*
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_play(Handle hs, float priority = 0.0f);
|
||||
|
||||
// change 3d position of the sound source.
|
||||
// if relative (default false), (x,y,z) is treated as relative to the
|
||||
// listener; otherwise, it is the position in world coordinates.
|
||||
//
|
||||
// may be called at any time; fails with invalid handle return if
|
||||
// the sound has already been closed (e.g. it never played).
|
||||
/**
|
||||
* change 3d position of the sound source.
|
||||
*
|
||||
* may be called at any time; fails with invalid handle return if
|
||||
* the sound has already been closed (e.g. it never played).
|
||||
*
|
||||
* @param relative treat (x,y,z) as relative to the listener;
|
||||
* if false (the default), it is the position in world coordinates.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_pos(Handle hs, float x, float y, float z, bool relative = false);
|
||||
|
||||
// change gain (amplitude modifier) of the sound source.
|
||||
// must be non-negative; 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
|
||||
//
|
||||
// should not be called during a fade (see note in implementation);
|
||||
// fails with invalid handle return if the sound has already been
|
||||
// closed (e.g. it never played).
|
||||
/**
|
||||
* change gain (amplitude modifier) of the sound source.
|
||||
*
|
||||
* should not be called during a fade (see note in implementation);
|
||||
* fails with invalid handle return if the sound has already been
|
||||
* closed (e.g. it never played).
|
||||
*
|
||||
* @param gain amplitude modifier. must be non-negative;
|
||||
* 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_gain(Handle hs, float gain);
|
||||
|
||||
// change pitch shift of the sound source.
|
||||
// 1.0 means no change; each reduction by 50% equals a pitch shift of
|
||||
// -12 semitones (one octave). zero is invalid.
|
||||
//
|
||||
// may be called at any time; fails with invalid handle return if
|
||||
// the sound has already been closed (e.g. it never played).
|
||||
/**
|
||||
* change pitch shift of the sound source.
|
||||
*
|
||||
* may be called at any time; fails with invalid handle return if
|
||||
* the sound has already been closed (e.g. it never played).
|
||||
*
|
||||
* @param pitch 1.0 means no change; each reduction by 50% equals a
|
||||
* pitch shift of -12 semitones (one octave). zero is invalid.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_pitch(Handle hs, float pitch);
|
||||
|
||||
// enable/disable looping on the sound source.
|
||||
// used to implement variable-length sounds (e.g. while building).
|
||||
//
|
||||
// may be called at any time; fails with invalid handle return if
|
||||
// the sound has already been closed (e.g. it never played).
|
||||
//
|
||||
// notes:
|
||||
// - looping sounds are not discarded if they cannot be played for lack of
|
||||
// a hardware voice at the moment play was requested.
|
||||
// - once looping is again disabled and the sound has reached its end,
|
||||
// the sound instance is freed automatically (as if never looped).
|
||||
/**
|
||||
* enable/disable looping on the sound source.
|
||||
* used to implement variable-length sounds (e.g. while building).
|
||||
*
|
||||
* may be called at any time; fails with invalid handle return if
|
||||
* the sound has already been closed (e.g. it never played).
|
||||
*
|
||||
* Notes:
|
||||
* <UL>
|
||||
* <LI> looping sounds are not discarded if they cannot be played for
|
||||
* lack of a hardware voice at the moment play was requested.
|
||||
* <LI> once looping is again disabled and the sound has reached its end,
|
||||
* the sound instance is freed automatically (as if never looped).
|
||||
* </UL>
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_set_loop(Handle hs, bool loop);
|
||||
|
||||
|
||||
/// types of fade in/out operations
|
||||
enum FadeType
|
||||
{
|
||||
FT_NONE,
|
||||
FT_LINEAR,
|
||||
FT_EXPONENTIAL,
|
||||
FT_S_CURVE,
|
||||
FT_NONE, /// currently no fade in progres
|
||||
FT_LINEAR, /// f(t) = t
|
||||
FT_EXPONENTIAL, /// f(t) = t**3
|
||||
FT_S_CURVE, /// cosine curve
|
||||
|
||||
FT_ABORT
|
||||
FT_ABORT /// abort and mark pending fade as complete
|
||||
};
|
||||
|
||||
// fade the sound source in or out over time.
|
||||
// its gain starts at <initial_gain> (immediately) and is moved toward
|
||||
// <final_gain> over <length> seconds. <type> determines the fade curve:
|
||||
// linear, exponential or S-curve. for guidance on which to use, see
|
||||
// http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html
|
||||
// you can also pass FT_ABORT to stop fading (if in progress) and
|
||||
// set gain to the current <final_gain> parameter.
|
||||
// special cases:
|
||||
// - if <initial_gain> < 0 (an otherwise illegal value), the sound's
|
||||
// current gain is used as the start value (useful for fading out).
|
||||
// - if <final_gain> is 0, the sound is freed when the fade completes or
|
||||
// is aborted, thus allowing fire-and-forget fadeouts. no cases are
|
||||
// foreseen where this is undesirable, and it is easier to implement
|
||||
// than an extra set-free-after-fade-flag function.
|
||||
//
|
||||
// may be called at any time; fails with invalid handle return if
|
||||
// the sound has already been closed (e.g. it never played).
|
||||
//
|
||||
// note that this function doesn't busy-wait until the fade is complete;
|
||||
// any number of fades may be active at a time (allows cross-fading).
|
||||
// each snd_update calculates a new gain value for all pending fades.
|
||||
// it is safe to start another fade on the same sound source while
|
||||
// one is already in progress; the old one will be discarded.
|
||||
/**
|
||||
* fade the sound source in or out over time.
|
||||
*
|
||||
* may be called at any time; fails with invalid handle return if
|
||||
* the sound has already been closed (e.g. it never played).
|
||||
*
|
||||
* gain starts at <initial_gain> (immediately) and is moved toward
|
||||
* <final_gain> over <length> seconds.
|
||||
* @param type of fade curve: linear, exponential or S-curve.
|
||||
* for guidance on which to use, see
|
||||
* http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html
|
||||
* you can also pass FT_ABORT to stop fading (if in progress) and
|
||||
* set gain to the current <final_gain> parameter.
|
||||
* special cases:
|
||||
* - if <initial_gain> < 0 (an otherwise illegal value), the sound's
|
||||
* current gain is used as the start value (useful for fading out).
|
||||
* - if <final_gain> is 0, the sound is freed when the fade completes or
|
||||
* is aborted, thus allowing fire-and-forget fadeouts. no cases are
|
||||
* foreseen where this is undesirable, and it is easier to implement
|
||||
* than an extra set-free-after-fade-flag function.
|
||||
*
|
||||
* note that this function doesn't busy-wait until the fade is complete;
|
||||
* any number of fades may be active at a time (allows cross-fading).
|
||||
* each snd_update calculates a new gain value for all pending fades.
|
||||
* it is safe to start another fade on the same sound source while
|
||||
* one is already in progress; the old one will be discarded.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_fade(Handle hvs, float initial_gain, float final_gain,
|
||||
float length, FadeType type);
|
||||
|
||||
@@ -248,27 +311,41 @@ extern LibError snd_fade(Handle hvs, float initial_gain, float final_gain,
|
||||
// sound engine
|
||||
//
|
||||
|
||||
// (temporarily) disable all sound output. because it causes future snd_open
|
||||
// calls to immediately abort before they demand-initialize OpenAL,
|
||||
// startup is sped up considerably (500..1000ms). therefore, this must be
|
||||
// called before the first snd_open to have any effect; otherwise, the
|
||||
// cat will already be out of the bag and we debug_warn of it.
|
||||
//
|
||||
// rationale: this is a quick'n dirty way of speeding up startup during
|
||||
// development without having to change the game's sound code.
|
||||
//
|
||||
// can later be called to reactivate sound; all settings ever changed
|
||||
// will be applied and subsequent sound load / play requests will work.
|
||||
/**
|
||||
* (temporarily) disable all sound output.
|
||||
*
|
||||
* because it causes future snd_open calls to immediately abort before they
|
||||
* demand-initialize OpenAL, startup is sped up considerably (500..1000ms).
|
||||
* therefore, this must be called before the first snd_open to have
|
||||
* any effect; otherwise, the cat will already be out of the bag and
|
||||
* we debug_warn of it.
|
||||
*
|
||||
* rationale: this is a quick'n dirty way of speeding up startup during
|
||||
* development without having to change the game's sound code.
|
||||
*
|
||||
* can later be called to reactivate sound; all settings ever changed
|
||||
* will be applied and subsequent sound load / play requests will work.
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_disable(bool disabled);
|
||||
|
||||
// perform housekeeping (e.g. streaming); call once a frame.
|
||||
//
|
||||
// additionally, if any parameter is non-NULL, we set the listener
|
||||
// position, look direction, and up vector (in world coordinates).
|
||||
/**
|
||||
* perform housekeeping (e.g. streaming); call once a frame.
|
||||
*
|
||||
* all parameters are expressed in world coordinates. they can all be NULL
|
||||
* to avoid updating the listener data; this is useful when the game world
|
||||
* has not been initialized yet.
|
||||
* @param pos listener's position
|
||||
* @param dir listener view direction
|
||||
* @param up listener's local up vector
|
||||
* @return LibError
|
||||
**/
|
||||
extern LibError snd_update(const float* pos, const float* dir, const float* up);
|
||||
|
||||
// free all resources and shut down the sound system.
|
||||
// call before h_mgr_shutdown.
|
||||
/**
|
||||
* free all resources and shut down the sound system.
|
||||
* call before h_mgr_shutdown.
|
||||
**/
|
||||
extern void snd_shutdown();
|
||||
|
||||
#endif // #ifndef SND_MGR_H__
|
||||
|
||||
@@ -172,4 +172,7 @@ extern int self_test_register(SelfTestRecord* r);
|
||||
// set/cleared by run_self_test.
|
||||
extern bool self_test_active;
|
||||
|
||||
#define TS_ASSERT_OK(expr) TS_ASSERT_EQUAL((expr), ERR_OK)
|
||||
#define TS_ASSERT_STR_EQUAL(str1, str2) TS_ASSERT(!strcmp((str1), (str2)))
|
||||
|
||||
#endif // #ifndef SELF_TEST_H__
|
||||
|
||||
@@ -203,200 +203,3 @@ int tcat_s(tchar* dst, size_t max_dst_chars, const tchar* src)
|
||||
}
|
||||
|
||||
#endif // #if !HAVE_STRING_S
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// built-in self test
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace test {
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
|
||||
// note: avoid 4-byte strings - they would trigger WARN_IF_PTR_LEN.
|
||||
|
||||
static const tchar* s0 = T("");
|
||||
static const tchar* s1 = T("a");
|
||||
static const tchar* s5 = T("abcde");
|
||||
static const tchar* s10 = T("abcdefghij");
|
||||
|
||||
static tchar d1[1];
|
||||
static tchar d2[2];
|
||||
static tchar d3[3];
|
||||
static tchar d5[5];
|
||||
static tchar d6[6];
|
||||
static tchar d10[10];
|
||||
static tchar d11[11];
|
||||
|
||||
static tchar no_null[] = { 'n','o','_','n','u','l','l'};
|
||||
|
||||
|
||||
|
||||
#define TEST_LEN(string, limit, expected) \
|
||||
TEST(tnlen((string), (limit)) == (expected));
|
||||
|
||||
#define TEST_CPY(dst, dst_max, src, expected_ret, expected_dst) \
|
||||
STMT( \
|
||||
int ret = tcpy_s((dst), dst_max, (src)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
#define TEST_CPY2(dst, src, expected_ret, expected_dst) \
|
||||
STMT( \
|
||||
int ret = tcpy_s((dst), ARRAY_SIZE(dst), (src)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
#define TEST_NCPY(dst, src, max_src_chars, expected_ret, expected_dst) \
|
||||
STMT( \
|
||||
int ret = tncpy_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
|
||||
#define TEST_CAT(dst, dst_max, src, expected_ret, expected_dst) \
|
||||
STMT( \
|
||||
int ret = tcat_s((dst), dst_max, (src)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
#define TEST_CAT2(dst, dst_val, src, expected_ret, expected_dst) \
|
||||
STMT( \
|
||||
tcpy(dst, T(dst_val)); \
|
||||
int ret = tcat_s((dst), ARRAY_SIZE(dst), (src)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
#define TEST_NCAT(dst, dst_val, src, max_src_chars, expected_ret, expected_dst)\
|
||||
STMT( \
|
||||
tcpy(dst, T(dst_val)); \
|
||||
int ret = tncat_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars)); \
|
||||
TEST(ret == expected_ret); \
|
||||
if(dst != 0) \
|
||||
TEST(!tcmp(dst, T(expected_dst))); \
|
||||
)
|
||||
|
||||
|
||||
// contains all tests that verify correct behavior for bogus input.
|
||||
// our implementation suppresses error dialogs while the self-test is active,
|
||||
// but others (e.g. the functions shipped with VC8) do not.
|
||||
// since we have no control over their error reporting (which ends up taking
|
||||
// down the program), we must skip this part of the test if using them.
|
||||
// this is still preferable to completely disabling the self-test.
|
||||
static void test_param_validation()
|
||||
{
|
||||
#if !HAVE_STRING_S
|
||||
TEST_CPY(0 ,0,0 , EINVAL,""); // all invalid
|
||||
TEST_CPY(0 ,0,s1, EINVAL,""); // dst = 0, max = 0
|
||||
TEST_CPY(0 ,1,s1, EINVAL,""); // dst = 0, max > 0
|
||||
TEST_CPY(d1,1,0 , EINVAL,""); // src = 0
|
||||
TEST_CPY(d1,0,s1, ERANGE,""); // max_dst_chars = 0
|
||||
|
||||
TEST_CPY2(d1 ,s1, ERANGE,"");
|
||||
TEST_CPY2(d1 ,s5, ERANGE,"");
|
||||
TEST_CPY2(d5 ,s5, ERANGE,"");
|
||||
|
||||
TEST_NCPY(d1 ,s1,1, ERANGE,"");
|
||||
TEST_NCPY(d1 ,s5,1, ERANGE,"");
|
||||
TEST_NCPY(d5 ,s5,5, ERANGE,"");
|
||||
|
||||
TEST_CAT(0 ,0,0 , EINVAL,""); // all invalid
|
||||
TEST_CAT(0 ,0,s1, EINVAL,""); // dst = 0, max = 0
|
||||
TEST_CAT(0 ,1,s1, EINVAL,""); // dst = 0, max > 0
|
||||
TEST_CAT(d1,1,0 , EINVAL,""); // src = 0
|
||||
TEST_CAT(d1,0,s1, ERANGE,""); // max_dst_chars = 0
|
||||
TEST_CAT(no_null,5,s1, ERANGE,""); // dst not terminated
|
||||
|
||||
TEST_CAT2(d1 ,"" ,s1, ERANGE,"");
|
||||
TEST_CAT2(d1 ,"" ,s5, ERANGE,"");
|
||||
TEST_CAT2(d10,"" ,s10, ERANGE,""); // empty, total overflow
|
||||
TEST_CAT2(d10,"12345",s5 , ERANGE,""); // not empty, overflow
|
||||
TEST_CAT2(d10,"12345",s10, ERANGE,""); // not empty, total overflow
|
||||
|
||||
TEST_NCAT(d1 ,"" ,s1,1, ERANGE,"");
|
||||
TEST_NCAT(d1 ,"" ,s5,5, ERANGE,"");
|
||||
TEST_NCAT(d10,"" ,s10,10, ERANGE,""); // empty, total overflow
|
||||
TEST_NCAT(d10,"12345",s5 ,5 , ERANGE,""); // not empty, overflow
|
||||
TEST_NCAT(d10,"12345",s10,10, ERANGE,""); // not empty, total overflow
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void test_length()
|
||||
{
|
||||
TEST_LEN(s0, 0 , 0 );
|
||||
TEST_LEN(s0, 1 , 0 );
|
||||
TEST_LEN(s0, 50, 0 );
|
||||
TEST_LEN(s1, 0 , 0 );
|
||||
TEST_LEN(s1, 1 , 1 );
|
||||
TEST_LEN(s1, 50, 1 );
|
||||
TEST_LEN(s5, 0 , 0 );
|
||||
TEST_LEN(s5, 1 , 1 );
|
||||
TEST_LEN(s5, 50, 5 );
|
||||
TEST_LEN(s10,9 , 9 );
|
||||
TEST_LEN(s10,10, 10);
|
||||
TEST_LEN(s10,11, 10);
|
||||
}
|
||||
|
||||
|
||||
static void test_copy()
|
||||
{
|
||||
TEST_CPY2(d2 ,s1, 0,"a");
|
||||
TEST_CPY2(d6 ,s5, 0,"abcde");
|
||||
TEST_CPY2(d11,s5, 0,"abcde");
|
||||
|
||||
TEST_NCPY(d2 ,s1,1, 0,"a");
|
||||
TEST_NCPY(d6 ,s5,5, 0,"abcde");
|
||||
TEST_NCPY(d11,s5,5, 0,"abcde");
|
||||
|
||||
tcpy(d5, T("----"));
|
||||
TEST_NCPY(d5,s5,0 , 0,""); // specified behavior! see 3.6.2.1.1 #4
|
||||
TEST_NCPY(d5,s5,1 , 0,"a");
|
||||
TEST_NCPY(d5,s5,4 , 0,"abcd");
|
||||
TEST_NCPY(d6,s5,5 , 0,"abcde");
|
||||
TEST_NCPY(d6,s5,10, 0,"abcde");
|
||||
}
|
||||
|
||||
|
||||
static void test_concatenate()
|
||||
{
|
||||
TEST_CAT2(d3 ,"1",s1, 0,"1a");
|
||||
TEST_CAT2(d5 ,"1",s1, 0,"1a");
|
||||
TEST_CAT2(d6 ,"" ,s5, 0,"abcde");
|
||||
TEST_CAT2(d10,"" ,s5, 0,"abcde");
|
||||
TEST_CAT2(d10,"1234" ,s5 , 0,"1234abcde");
|
||||
|
||||
TEST_NCAT(d3 ,"1",s1,1, 0,"1a");
|
||||
TEST_NCAT(d5 ,"1",s1,1, 0,"1a");
|
||||
TEST_NCAT(d6 ,"" ,s5,5, 0,"abcde");
|
||||
TEST_NCAT(d10,"" ,s5,5, 0,"abcde");
|
||||
TEST_NCAT(d10,"1234" ,s5 ,5 , 0,"1234abcde");
|
||||
|
||||
TEST_NCAT(d5,"----",s5,0 , 0,"----");
|
||||
TEST_NCAT(d5,"",s5,1 , 0,"a");
|
||||
TEST_NCAT(d5,"",s5,4 , 0,"abcd");
|
||||
TEST_NCAT(d5,"12",s5,2 , 0,"12ab");
|
||||
TEST_NCAT(d6,"",s5,10, 0,"abcde");
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_param_validation();
|
||||
test_length();
|
||||
test_copy();
|
||||
test_concatenate();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
} // namespace test
|
||||
|
||||
+13
-52
@@ -42,9 +42,6 @@
|
||||
#error ia32.cpp needs inline assembly support!
|
||||
#endif
|
||||
|
||||
#define SELF_TEST_ENABLED 1
|
||||
#include "self_test.h"
|
||||
|
||||
// set by ia32_init, referenced by ia32_memcpy (asm)
|
||||
extern "C" u32 ia32_memcpy_size_mask = 0;
|
||||
|
||||
@@ -58,7 +55,7 @@ void ia32_init()
|
||||
// .. check for PREFETCHNTA and MOVNTQ support. these are part of the SSE
|
||||
// instruction set, but also supported on older Athlons as part of
|
||||
// the extended AMD MMX set.
|
||||
if(ia32_cap(SSE) || ia32_cap(AMD_MMX_EXT))
|
||||
if(ia32_cap(IA32_CAP_SSE) || ia32_cap(IA32_CAP_AMD_MMX_EXT))
|
||||
ia32_memcpy_size_mask = ~0u;
|
||||
}
|
||||
|
||||
@@ -183,7 +180,7 @@ __asm{
|
||||
// calling conventions.
|
||||
// MSC, ICC and GCC currently return 64 bits in edx:eax, which even
|
||||
// matches rdtsc output, but we play it safe and return a temporary.
|
||||
u64 rdtsc()
|
||||
u64 ia32_rdtsc()
|
||||
{
|
||||
u64 c;
|
||||
#if HAVE_MS_ASM
|
||||
@@ -226,7 +223,7 @@ void ia32_debug_break()
|
||||
void mfence()
|
||||
{
|
||||
// Pentium IV
|
||||
if(ia32_cap(SSE2))
|
||||
if(ia32_cap(IA32_CAP_SSE2))
|
||||
#if HAVE_MS_ASM
|
||||
__asm mfence
|
||||
#elif HAVE_GNU_ASM
|
||||
@@ -248,7 +245,7 @@ void serialize()
|
||||
// CPU / feature detect
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ia32_cap(CpuCap cap)
|
||||
bool ia32_cap(IA32Cap cap)
|
||||
{
|
||||
// treated as 128 bit field; order: std ecx, std edx, ext ecx, ext edx
|
||||
// keep in sync with enum CpuCap!
|
||||
@@ -364,7 +361,7 @@ static void get_cpu_type()
|
||||
SAFE_STRCPY(cpu_type, "AMD Athlon");
|
||||
else
|
||||
{
|
||||
if(ia32_cap(AMD_MP))
|
||||
if(ia32_cap(IA32_CAP_AMD_MP))
|
||||
SAFE_STRCPY(cpu_type, "AMD Athlon MP");
|
||||
else
|
||||
SAFE_STRCPY(cpu_type, "AMD Athlon XP");
|
||||
@@ -465,7 +462,7 @@ static void get_cpu_count()
|
||||
// note: we don't check if it's Intel and P4 or above - HT may be
|
||||
// supported on other CPUs in future. all processors should set this
|
||||
// feature bit correctly, so it's not a problem.
|
||||
if(ia32_cap(HT))
|
||||
if(ia32_cap(IA32_CAP_HT))
|
||||
{
|
||||
log_id_bits = log2(log_cpu_per_package); // see above
|
||||
last_phys_id = last_log_id = INVALID_ID;
|
||||
@@ -495,7 +492,7 @@ static void check_for_speedstep()
|
||||
{
|
||||
if(vendor == INTEL)
|
||||
{
|
||||
if(ia32_cap(EST))
|
||||
if(ia32_cap(IA32_CAP_EST))
|
||||
cpu_speedstep = 1;
|
||||
}
|
||||
else if(vendor == AMD)
|
||||
@@ -520,11 +517,11 @@ static void measure_cpu_freq()
|
||||
// make sure the TSC is available, because we're going to
|
||||
// measure actual CPU clocks per known time interval.
|
||||
// counting loop iterations ("bogomips") is unreliable.
|
||||
if(ia32_cap(TSC))
|
||||
if(ia32_cap(IA32_CAP_TSC))
|
||||
{
|
||||
// note: no need to "warm up" cpuid - it will already have been
|
||||
// called several times by the time this code is reached.
|
||||
// (background: it's used in rdtsc() to serialize instruction flow;
|
||||
// (background: it's used in ia32_rdtsc() to serialize instruction flow;
|
||||
// the first call is documented to be slower on Intel CPUs)
|
||||
|
||||
int num_samples = 16;
|
||||
@@ -550,27 +547,27 @@ static void measure_cpu_freq()
|
||||
do
|
||||
{
|
||||
// note: get_time effectively has a long delay (up to 5 us)
|
||||
// before returning the time. we call it before rdtsc to
|
||||
// before returning the time. we call it before ia32_rdtsc to
|
||||
// minimize the delay between actually sampling time / TSC,
|
||||
// thus decreasing the chance for interference.
|
||||
// (if unavoidable background activity, e.g. interrupts,
|
||||
// delays the second reading, inaccuracy is introduced).
|
||||
t1 = get_time();
|
||||
c1 = rdtsc();
|
||||
c1 = ia32_rdtsc();
|
||||
}
|
||||
while(t1 == t0);
|
||||
// .. wait until start of next tick and at least 1 ms elapsed.
|
||||
do
|
||||
{
|
||||
const double t2 = get_time();
|
||||
const u64 c2 = rdtsc();
|
||||
const u64 c2 = ia32_rdtsc();
|
||||
dc = (i64)(c2 - c1);
|
||||
dt = t2 - t1;
|
||||
}
|
||||
while(dt < 1e-3);
|
||||
|
||||
// .. freq = (delta_clocks) / (delta_seconds);
|
||||
// cpuid/rdtsc/timer overhead is negligible.
|
||||
// ia32_rdtsc/timer overhead is negligible.
|
||||
const double freq = dc / dt;
|
||||
samples[i] = freq;
|
||||
}
|
||||
@@ -676,39 +673,3 @@ LibError ia32_get_call_target(void* ret_addr, void** target)
|
||||
|
||||
WARN_RETURN(ERR_CPU_UNKNOWN_OPCODE);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_float_int()
|
||||
{
|
||||
TEST(i32_from_float(0.99999f) == 0);
|
||||
TEST(i32_from_float(1.0f) == 1);
|
||||
TEST(i32_from_float(1.01f) == 1);
|
||||
TEST(i32_from_float(5.6f) == 5);
|
||||
|
||||
TEST(i32_from_double(0.99999) == 0);
|
||||
TEST(i32_from_double(1.0) == 1);
|
||||
TEST(i32_from_double(1.01) == 1);
|
||||
TEST(i32_from_double(5.6) == 5);
|
||||
|
||||
TEST(i64_from_double(0.99999) == 0LL);
|
||||
TEST(i64_from_double(1.0) == 1LL);
|
||||
TEST(i64_from_double(1.01) == 1LL);
|
||||
TEST(i64_from_double(5.6) == 5LL);
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_float_int();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
+18
-16
@@ -89,37 +89,39 @@ extern void* ia32_memcpy(void* dst, const void* src, size_t nbytes); // asm
|
||||
extern uint ia32_control87(uint new_val, uint mask); // asm
|
||||
|
||||
|
||||
extern u64 rdtsc(void);
|
||||
extern u64 ia32_rdtsc(void);
|
||||
|
||||
extern void ia32_debug_break(void);
|
||||
|
||||
|
||||
// CPU caps (128 bits)
|
||||
// CPU capability flags (128 bits)
|
||||
// do not change the order!
|
||||
enum CpuCap
|
||||
enum IA32Cap
|
||||
{
|
||||
// standard (ecx) - currently only defined by Intel
|
||||
SSE3 = 0+0, // Streaming SIMD Extensions 3
|
||||
EST = 0+7, // Enhanced Speedstep Technology
|
||||
IA32_CAP_SSE3 = 0+0, // Streaming SIMD Extensions 3
|
||||
IA32_CAP_EST = 0+7, // Enhanced Speedstep Technology
|
||||
|
||||
// standard (edx)
|
||||
TSC = 32+4, // TimeStamp Counter
|
||||
CMOV = 32+15, // Conditional MOVe
|
||||
MMX = 32+23, // MultiMedia eXtensions
|
||||
SSE = 32+25, // Streaming SIMD Extensions
|
||||
SSE2 = 32+26, // Streaming SIMD Extensions 2
|
||||
HT = 32+28, // HyperThreading
|
||||
IA32_CAP_FPU = 32+0, // Floating Point Unit
|
||||
IA32_CAP_TSC = 32+4, // TimeStamp Counter
|
||||
IA32_CAP_CMOV = 32+15, // Conditional MOVe
|
||||
IA32_CAP_MMX = 32+23, // MultiMedia eXtensions
|
||||
IA32_CAP_SSE = 32+25, // Streaming SIMD Extensions
|
||||
IA32_CAP_SSE2 = 32+26, // Streaming SIMD Extensions 2
|
||||
IA32_CAP_HT = 32+28, // HyperThreading
|
||||
|
||||
// extended (ecx)
|
||||
|
||||
// extended (edx) - currently only defined by AMD
|
||||
AMD_MP = 96+19, // MultiProcessing capable; reserved on AMD64
|
||||
AMD_MMX_EXT = 96+22,
|
||||
AMD_3DNOW_PRO = 96+30,
|
||||
AMD_3DNOW = 96+31
|
||||
IA32_CAP_AMD_MP = 96+19, // MultiProcessing capable; reserved on AMD64
|
||||
IA32_CAP_AMD_MMX_EXT = 96+22,
|
||||
IA32_CAP_AMD_3DNOW_PRO = 96+30,
|
||||
IA32_CAP_AMD_3DNOW = 96+31
|
||||
};
|
||||
|
||||
extern bool ia32_cap(CpuCap cap);
|
||||
// indicate if the CPU supports the indicated cap.
|
||||
extern bool ia32_cap(IA32Cap cap);
|
||||
|
||||
|
||||
extern void ia32_get_cpu_info(void);
|
||||
|
||||
@@ -41,11 +41,6 @@
|
||||
# include "lib/sysdep/ia32.h"
|
||||
#endif
|
||||
|
||||
// raises an an annoying exception, so disable unless needed
|
||||
#undef SELF_TEST_ENABLED
|
||||
#define SELF_TEST_ENABLED 0
|
||||
|
||||
|
||||
#if MSC_VERSION
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#pragma comment(lib, "oleaut32.lib") // VariantChangeType
|
||||
@@ -1954,241 +1949,3 @@ static LibError wdbg_sym_shutdown()
|
||||
ptr_reset_visited();
|
||||
return sym_shutdown();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
#pragma optimize("", off)
|
||||
|
||||
|
||||
|
||||
static void test_array()
|
||||
{
|
||||
struct Small
|
||||
{
|
||||
int i1;
|
||||
int i2;
|
||||
};
|
||||
|
||||
struct Large
|
||||
{
|
||||
double d1;
|
||||
double d2;
|
||||
double d3;
|
||||
double d4;
|
||||
};
|
||||
|
||||
Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(large_array_of_large_structs);
|
||||
Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(small_array_of_large_structs);
|
||||
Small large_array_of_small_structs[8] = { { 1,2 } }; UNUSED2(large_array_of_small_structs);
|
||||
Small small_array_of_small_structs[2] = { { 1,2 } }; UNUSED2(small_array_of_small_structs);
|
||||
|
||||
int ints[] = { 1,2,3,4,5 }; UNUSED2(ints);
|
||||
wchar_t chars[] = { 'w','c','h','a','r','s',0 }; UNUSED2(chars);
|
||||
|
||||
// note: prefer simple error (which also generates stack trace) to
|
||||
// exception, because it is guaranteed to work (no issues with the
|
||||
// debugger swallowing exceptions).
|
||||
DISPLAY_ERROR(L"wdbg_sym self test: check if stack trace below is ok.");
|
||||
//RaiseException(0xf001,0,0,0);
|
||||
}
|
||||
|
||||
// also used by test_stl as an element type
|
||||
struct Nested
|
||||
{
|
||||
int nested_member;
|
||||
struct Nested* self_ptr;
|
||||
};
|
||||
|
||||
static void test_udt()
|
||||
{
|
||||
Nested nested = { 123 }; nested.self_ptr = &nested;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 s1;
|
||||
u8 s2;
|
||||
char s3;
|
||||
}
|
||||
Small;
|
||||
Small small__ = { 0x55, 0xaa, -1 }; UNUSED2(small__);
|
||||
|
||||
struct Large
|
||||
{
|
||||
u8 large_member_u8;
|
||||
std::string large_member_string;
|
||||
double large_member_double;
|
||||
}
|
||||
large = { 0xff, "large struct string", 123456.0 }; UNUSED2(large);
|
||||
|
||||
|
||||
class Base
|
||||
{
|
||||
int base_int;
|
||||
std::wstring base_wstring;
|
||||
public:
|
||||
Base()
|
||||
: base_int(123), base_wstring(L"base wstring")
|
||||
{
|
||||
}
|
||||
};
|
||||
class Derived : private Base
|
||||
{
|
||||
double derived_double;
|
||||
public:
|
||||
Derived()
|
||||
: derived_double(-1.0)
|
||||
{
|
||||
}
|
||||
}
|
||||
derived;
|
||||
|
||||
test_array();
|
||||
}
|
||||
|
||||
// STL containers and their contents
|
||||
static void test_stl()
|
||||
{
|
||||
std::vector<std::wstring> v_wstring;
|
||||
v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2");
|
||||
|
||||
std::deque<int> d_int;
|
||||
d_int.push_back(1); d_int.push_back(2); d_int.push_back(3);
|
||||
std::deque<std::string> d_string;
|
||||
d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c");
|
||||
|
||||
std::list<float> l_float;
|
||||
l_float.push_back(0.1f); l_float.push_back(0.2f); l_float.push_back(0.3f); l_float.push_back(0.4f);
|
||||
|
||||
std::map<std::string, int> m_string_int;
|
||||
m_string_int.insert(std::make_pair<std::string,int>("s5", 5));
|
||||
m_string_int.insert(std::make_pair<std::string,int>("s6", 6));
|
||||
m_string_int.insert(std::make_pair<std::string,int>("s7", 7));
|
||||
std::map<int, std::string> m_int_string;
|
||||
m_int_string.insert(std::make_pair<int,std::string>(1, "s1"));
|
||||
m_int_string.insert(std::make_pair<int,std::string>(2, "s2"));
|
||||
m_int_string.insert(std::make_pair<int,std::string>(3, "s3"));
|
||||
std::map<int, int> m_int_int;
|
||||
m_int_int.insert(std::make_pair<int,int>(1, 1));
|
||||
m_int_int.insert(std::make_pair<int,int>(2, 2));
|
||||
m_int_int.insert(std::make_pair<int,int>(3, 3));
|
||||
|
||||
STL_HASH_MAP<std::string, int> hm_string_int;
|
||||
hm_string_int.insert(std::make_pair<std::string,int>("s5", 5));
|
||||
hm_string_int.insert(std::make_pair<std::string,int>("s6", 6));
|
||||
hm_string_int.insert(std::make_pair<std::string,int>("s7", 7));
|
||||
STL_HASH_MAP<int, std::string> hm_int_string;
|
||||
hm_int_string.insert(std::make_pair<int,std::string>(1, "s1"));
|
||||
hm_int_string.insert(std::make_pair<int,std::string>(2, "s2"));
|
||||
hm_int_string.insert(std::make_pair<int,std::string>(3, "s3"));
|
||||
STL_HASH_MAP<int, int> hm_int_int;
|
||||
hm_int_int.insert(std::make_pair<int,int>(1, 1));
|
||||
hm_int_int.insert(std::make_pair<int,int>(2, 2));
|
||||
hm_int_int.insert(std::make_pair<int,int>(3, 3));
|
||||
|
||||
|
||||
std::set<uintptr_t> s_uintptr;
|
||||
s_uintptr.insert(0x123); s_uintptr.insert(0x456);
|
||||
|
||||
// empty
|
||||
std::deque<u8> d_u8_empty;
|
||||
std::list<Nested> l_nested_empty;
|
||||
std::map<double,double> m_double_empty;
|
||||
std::multimap<int,u8> mm_int_empty;
|
||||
std::set<uint> s_uint_empty;
|
||||
std::multiset<char> ms_char_empty;
|
||||
std::vector<double> v_double_empty;
|
||||
std::queue<double> q_double_empty;
|
||||
std::stack<double> st_double_empty;
|
||||
#if HAVE_STL_HASH
|
||||
STL_HASH_MAP<double,double> hm_double_empty;
|
||||
STL_HASH_MULTIMAP<double,std::wstring> hmm_double_empty;
|
||||
STL_HASH_SET<double> hs_double_empty;
|
||||
STL_HASH_MULTISET<double> hms_double_empty;
|
||||
#endif
|
||||
#if HAVE_STL_SLIST
|
||||
STL_SLIST<double> sl_double_empty;
|
||||
#endif
|
||||
std::string str_empty;
|
||||
std::wstring wstr_empty;
|
||||
|
||||
test_udt();
|
||||
|
||||
// uninitialized
|
||||
std::deque<u8> d_u8_uninit;
|
||||
std::list<Nested> l_nested_uninit;
|
||||
std::map<double,double> m_double_uninit;
|
||||
std::multimap<int,u8> mm_int_uninit;
|
||||
std::set<uint> s_uint_uninit;
|
||||
std::multiset<char> ms_char_uninit;
|
||||
std::vector<double> v_double_uninit;
|
||||
std::queue<double> q_double_uninit;
|
||||
std::stack<double> st_double_uninit;
|
||||
#if HAVE_STL_HASH
|
||||
STL_HASH_MAP<double,double> hm_double_uninit;
|
||||
STL_HASH_MULTIMAP<double,std::wstring> hmm_double_uninit;
|
||||
STL_HASH_SET<double> hs_double_uninit;
|
||||
STL_HASH_MULTISET<double> hms_double_uninit;
|
||||
#endif
|
||||
#if HAVE_STL_SLIST
|
||||
STL_SLIST<double> sl_double_uninit;
|
||||
#endif
|
||||
std::string str_uninit;
|
||||
std::wstring wstr_uninit;
|
||||
}
|
||||
|
||||
|
||||
// also exercises all basic types because we need to display some values
|
||||
// anyway (to see at a glance whether symbol engine addrs are correct)
|
||||
static void test_addrs(int p_int, double p_double, char* p_pchar, uintptr_t p_uintptr)
|
||||
{
|
||||
debug_printf("\nTEST_ADDRS\n");
|
||||
|
||||
uint l_uint = 0x1234;
|
||||
bool l_bool = true; UNUSED2(l_bool);
|
||||
wchar_t l_wchars[] = L"wchar string";
|
||||
enum TestEnum { VAL1=1, VAL2=2 } l_enum = VAL1;
|
||||
u8 l_u8s[] = { 1,2,3,4 };
|
||||
void (*l_funcptr)(void) = test_stl;
|
||||
|
||||
static double s_double = -2.718;
|
||||
static char s_chars[] = {'c','h','a','r','s',0};
|
||||
static void (*s_funcptr)(int, double, char*, uintptr_t) = test_addrs;
|
||||
static void* s_ptr = (void*)(uintptr_t)0x87654321;
|
||||
static HDC s_hdc = (HDC)0xff0;
|
||||
|
||||
debug_printf("p_int addr=%p val=%d\n", &p_int, p_int);
|
||||
debug_printf("p_double addr=%p val=%g\n", &p_double, p_double);
|
||||
debug_printf("p_pchar addr=%p val=%s\n", &p_pchar, p_pchar);
|
||||
debug_printf("p_uintptr addr=%p val=%lu\n", &p_uintptr, p_uintptr);
|
||||
|
||||
debug_printf("l_uint addr=%p val=%u\n", &l_uint, l_uint);
|
||||
debug_printf("l_wchars addr=%p val=%ws\n", &l_wchars, l_wchars);
|
||||
debug_printf("l_enum addr=%p val=%d\n", &l_enum, l_enum);
|
||||
debug_printf("l_u8s addr=%p val=%d\n", &l_u8s, l_u8s);
|
||||
debug_printf("l_funcptr addr=%p val=%p\n", &l_funcptr, l_funcptr);
|
||||
|
||||
test_stl();
|
||||
|
||||
int uninit_int; UNUSED2(uninit_int);
|
||||
float uninit_float; UNUSED2(uninit_float);
|
||||
double uninit_double; UNUSED2(uninit_double);
|
||||
bool uninit_bool; UNUSED2(uninit_bool);
|
||||
HWND uninit_hwnd; UNUSED2(uninit_hwnd);
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_addrs(123, 3.1415926535897932384626, "pchar string", 0xf00d);
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
#pragma optimize("", on)
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -190,7 +190,7 @@ static LibError choose_impl()
|
||||
// will do this as well (if not to save power, for heat reasons).
|
||||
// frequency changes are too often and drastic to correct,
|
||||
// and we don't want to mess with the system power settings => unsafe.
|
||||
if(cpu_freq > 0.0 && ia32_cap(TSC))
|
||||
if(cpu_freq > 0.0 && ia32_cap(IA32_CAP_TSC))
|
||||
{
|
||||
safe = (cpus == 1 && cpu_speedstep == 0);
|
||||
SAFETY_OVERRIDE(HRT_TSC);
|
||||
@@ -299,7 +299,7 @@ static i64 ticks_lk()
|
||||
// TSC
|
||||
#if CPU_IA32 && !defined(NO_TSC)
|
||||
case HRT_TSC:
|
||||
return (i64)rdtsc();
|
||||
return (i64)ia32_rdtsc();
|
||||
#endif
|
||||
|
||||
// QPC
|
||||
|
||||
+3
-3
@@ -51,7 +51,7 @@ extern void calc_fps(void);
|
||||
|
||||
// since TIMER_ACCRUE et al. are called so often, we try to keep
|
||||
// overhead to an absolute minimum. this flag allows storing
|
||||
// raw tick counts (e.g. CPU cycles returned by rdtsc) instead of
|
||||
// raw tick counts (e.g. CPU cycles returned by ia32_rdtsc) instead of
|
||||
// absolute time. there are two benefits:
|
||||
// - no need to convert from raw->time on every call
|
||||
// (instead, it's only done once when displaying the totals)
|
||||
@@ -213,7 +213,7 @@ public:
|
||||
{
|
||||
#if TIMER_USE_RAW_TICKS
|
||||
# if CPU_IA32
|
||||
t0 = rdtsc();
|
||||
t0 = ia32_rdtsc();
|
||||
# else
|
||||
# error "port"
|
||||
# endif
|
||||
@@ -226,7 +226,7 @@ public:
|
||||
{
|
||||
#if TIMER_USE_RAW_TICKS
|
||||
# if CPU_IA32
|
||||
TimerUnit t1 = rdtsc();
|
||||
TimerUnit t1 = ia32_rdtsc();
|
||||
# else
|
||||
# error "port"
|
||||
# endif
|
||||
|
||||
@@ -524,74 +524,3 @@ void CMatrix3D::SetRotation(const CQuaternion& quat)
|
||||
{
|
||||
quat.ToMatrix(*this);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test_inverse()
|
||||
{
|
||||
CMatrix3D m;
|
||||
srand(0);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
for (int j = 0; j < 16; ++j)
|
||||
m._data[j] = -1.0f + 2.0f*(rand()/(float)RAND_MAX);
|
||||
CMatrix3D n;
|
||||
m.GetInverse(n);
|
||||
m *= n;
|
||||
for (int x = 0; x < 4; ++x)
|
||||
for (int y = 0; y < 4; ++y)
|
||||
{
|
||||
float expected = (x==y)? 1.0f : 0.0f; // identity should have 1s on diagonal
|
||||
TEST(feq(m(x,y), expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_quats()
|
||||
{
|
||||
srand(0);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
CQuaternion q;
|
||||
q.FromEulerAngles(
|
||||
-6.28f + 12.56f*(rand()/(float)RAND_MAX),
|
||||
-6.28f + 12.56f*(rand()/(float)RAND_MAX),
|
||||
-6.28f + 12.56f*(rand()/(float)RAND_MAX)
|
||||
);
|
||||
CMatrix3D m;
|
||||
q.ToMatrix(m);
|
||||
CQuaternion q2 = m.GetRotation();
|
||||
|
||||
// I hope there's a good reason why they're sometimes negated, and
|
||||
// it's not just a bug...
|
||||
bool ok_oneway =
|
||||
feq(q2.m_W, q.m_W) &&
|
||||
feq(q2.m_V.X, q.m_V.X) &&
|
||||
feq(q2.m_V.Y, q.m_V.Y) &&
|
||||
feq(q2.m_V.Z, q.m_V.Z);
|
||||
bool ok_otherway =
|
||||
feq(q2.m_W, -q.m_W) &&
|
||||
feq(q2.m_V.X, -q.m_V.X) &&
|
||||
feq(q2.m_V.Y, -q.m_V.Y) &&
|
||||
feq(q2.m_V.Z, -q.m_V.Z);
|
||||
TEST(ok_oneway ^ ok_otherway);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_inverse();
|
||||
test_quats();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -128,36 +128,6 @@ CStrW CStr8::FromUTF8() const
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test1()
|
||||
{
|
||||
const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffe };
|
||||
const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbe };
|
||||
CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t));
|
||||
CStr8 str_utf8 = str_utf16.ToUTF8();
|
||||
TEST(str_utf8.length() == sizeof(chr_utf8));
|
||||
TEST(memcmp(str_utf8.data(), chr_utf8, sizeof(chr_utf8)) == 0);
|
||||
TEST(str_utf8.FromUTF8() == str_utf16);
|
||||
}
|
||||
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test1();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
|
||||
#else
|
||||
|
||||
// The following code is compiled twice, as CStrW then as CStr8:
|
||||
|
||||
@@ -1089,115 +1089,3 @@ CParser& CParserCache::Get(const char* str)
|
||||
return *parser;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test1()
|
||||
{
|
||||
CParser Parser;
|
||||
Parser.InputTaskType("test", "_$ident_=_$value_");
|
||||
|
||||
std::string str;
|
||||
int i;
|
||||
|
||||
CParserLine Line;
|
||||
|
||||
TEST(Line.ParseString(Parser, "value=23"));
|
||||
|
||||
TEST(Line.GetArgString(0, str) && str == "value");
|
||||
TEST(Line.GetArgInt(1, i) && i == 23);
|
||||
}
|
||||
|
||||
|
||||
static void test2()
|
||||
{
|
||||
CParser Parser;
|
||||
Parser.InputTaskType("test", "_$value_[$value]_");
|
||||
|
||||
std::string str;
|
||||
|
||||
CParserLine Line;
|
||||
|
||||
TEST(Line.ParseString(Parser, "12 34"));
|
||||
TEST(Line.GetArgCount() == 2);
|
||||
TEST(Line.GetArgString(0, str) && str == "12");
|
||||
TEST(Line.GetArgString(1, str) && str == "34");
|
||||
|
||||
TEST(Line.ParseString(Parser, "56"));
|
||||
TEST(Line.GetArgCount() == 1);
|
||||
TEST(Line.GetArgString(0, str) && str == "56");
|
||||
|
||||
TEST(! Line.ParseString(Parser, " "));
|
||||
}
|
||||
|
||||
|
||||
static void test3()
|
||||
{
|
||||
CParser Parser;
|
||||
Parser.InputTaskType("test", "_[$value]_[$value]_[$value]_");
|
||||
|
||||
std::string str;
|
||||
|
||||
CParserLine Line;
|
||||
|
||||
TEST(Line.ParseString(Parser, "12 34 56"));
|
||||
TEST(Line.GetArgCount() == 3);
|
||||
TEST(Line.GetArgString(0, str) && str == "12");
|
||||
TEST(Line.GetArgString(1, str) && str == "34");
|
||||
TEST(Line.GetArgString(2, str) && str == "56");
|
||||
|
||||
TEST(Line.ParseString(Parser, "78 90"));
|
||||
TEST(Line.GetArgCount() == 2);
|
||||
TEST(Line.GetArgString(0, str) && str == "78");
|
||||
TEST(Line.GetArgString(1, str) && str == "90");
|
||||
|
||||
TEST(Line.ParseString(Parser, "ab"));
|
||||
TEST(Line.GetArgCount() == 1);
|
||||
TEST(Line.GetArgString(0, str) && str == "ab");
|
||||
|
||||
TEST(Line.ParseString(Parser, " "));
|
||||
TEST(Line.GetArgCount() == 0);
|
||||
}
|
||||
|
||||
|
||||
static void test4()
|
||||
{
|
||||
CParser Parser;
|
||||
Parser.InputTaskType("test", "<[_a_][_b_]_x_>");
|
||||
|
||||
std::string str;
|
||||
|
||||
CParserLine Line;
|
||||
TEST(Line.ParseString(Parser, "a b x a b x"));
|
||||
TEST(Line.ParseString(Parser, "a x b x"));
|
||||
TEST(Line.ParseString(Parser, "a x"));
|
||||
TEST(Line.ParseString(Parser, "b x"));
|
||||
TEST(Line.ParseString(Parser, "x"));
|
||||
TEST(! Line.ParseString(Parser, "a x c x"));
|
||||
TEST(! Line.ParseString(Parser, "a b a x"));
|
||||
TEST(! Line.ParseString(Parser, "a"));
|
||||
TEST(! Line.ParseString(Parser, "a a x"));
|
||||
TEST(Line.ParseString(Parser, "a x a b x a x b x b x b x b x a x a x a b x a b x b x a x"));
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test1();
|
||||
test2();
|
||||
test3();
|
||||
test4();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
@@ -182,70 +182,3 @@ template <> void XMLWriter_File::ElementAttribute<CStrW>(const char* name, const
|
||||
{
|
||||
ElementAttribute(name, value.ToUTF8(), newelement);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// built-in self test
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#if SELF_TEST_ENABLED
|
||||
namespace test {
|
||||
|
||||
static void test1()
|
||||
{
|
||||
XML_Start("utf-8");
|
||||
XML_Doctype("Scenario", "/maps/scenario.dtd");
|
||||
|
||||
{
|
||||
XML_Element("Scenario");
|
||||
{
|
||||
XML_Comment("Comment test.");
|
||||
XML_Comment("Comment test again.");
|
||||
{
|
||||
XML_Element("a");
|
||||
XML_Attribute("one", 1);
|
||||
XML_Attribute("two", "TWO");
|
||||
XML_Text("b");
|
||||
XML_Text(" (etc)");
|
||||
}
|
||||
{
|
||||
XML_Element("c");
|
||||
XML_Text("d");
|
||||
}
|
||||
XML_Setting("c2", "d2");
|
||||
{
|
||||
XML_Element("e");
|
||||
{
|
||||
{
|
||||
XML_Element("f");
|
||||
XML_Text("g");
|
||||
}
|
||||
{
|
||||
XML_Element("h");
|
||||
}
|
||||
{
|
||||
XML_Element("i");
|
||||
XML_Attribute("j", 1.23);
|
||||
{
|
||||
XML_Element("k");
|
||||
XML_Attribute("l", 2.34);
|
||||
XML_Text("m");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For this test to be useful, it should actually test something.
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test1();
|
||||
}
|
||||
|
||||
SELF_TEST_RUN;
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
Reference in New Issue
Block a user