# 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:
janwas
2006-05-31 04:01:59 +00:00
parent 835bfbc74f
commit 1ead202b24
34 changed files with 1772 additions and 2608 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ void ColorActivateFastImpl()
{
}
#if CPU_IA32
else if (ia32_cap(SSE))
else if (ia32_cap(IA32_CAP_SSE))
{
ConvertRGBColorTo4ub = sse_ConvertRGBColorTo4ub;
}
-191
View File
@@ -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
-154
View File
@@ -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__
-59
View File
@@ -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
+1 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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__
-227
View File
@@ -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
+1 -1
View File
@@ -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
+3
View File
@@ -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;
}
+1
View File
@@ -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.
+1 -1
View File
@@ -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.
-75
View File
@@ -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
+6 -7
View File
@@ -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);
-37
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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__
+11
View File
@@ -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;
+177 -47
View File
@@ -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__
+85
View File
@@ -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
View File
@@ -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__
+3
View File
@@ -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__
-197
View File
@@ -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
View File
@@ -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
View File
@@ -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);
-243
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
-71
View File
@@ -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
-30
View File
@@ -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:
-112
View File
@@ -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
-67
View File
@@ -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