1
0
forked from 0ad/0ad

the big merge (tm). see forum post for changes made.

This was SVN commit r158.
This commit is contained in:
janwas 2004-03-02 23:56:51 +00:00
parent 91cb7f7138
commit 89c5b0d88c
41 changed files with 10060 additions and 0 deletions

111
source/lib/adts.cpp Executable file
View File

@ -0,0 +1,111 @@
#include "adts.h"
#include <cassert>
template<class T, int max_items> class DynArray
{
public:
DynArray() {}
~DynArray() {}
T* operator[](uint n)
{
T*& page = pages[n / items_per_page];
if(!page)
{
page = (T*)calloc(PAGE_SIZE, 1);
if(!page)
return 0;
}
return page + (n % items_per_page);
}
private:
enum { PAGE_SIZE = 4096 };
// const size_t items_per_page = sizeof(T) / PAGE_SIZE;
// const size_t num_pages = (max_items + items_per_page-1) / items_per_page;
T* pages[(max_items + (sizeof(T) / PAGE_SIZE)-1) / (sizeof(T) / PAGE_SIZE)];
};
//
// cache implementation
//
// mark l as the most recently used line.
void Cache::reference(List_iterator l)
{
lru_list.splice(lru_list.begin(), lru_list, l);
idx[l->tag] = l;
}
// return the line identified by tag, or 0 if not in cache.
Cache::Line* Cache::find_line(u64 tag)
{
Map::const_iterator i = idx.find(tag);
// not found
if(i == idx.end())
return 0;
// index points us to list entry
List_iterator l = i->second;
reference(l);
return &*l;
}
// return the pointer associated with the line identified by tag,
// or 0 if not in cache.
void* Cache::get(u64 tag)
{
Line* l = find_line(tag);
return l? l->p : 0;
}
// add tag to cache, and associate p with it.
// return 0 on success, or -1 if already in cache.
int Cache::add(u64 tag, void* p)
{
if(get(tag))
{
assert(0 && "add: tag already in cache!");
return -1;
}
// add directly to front of LRU list
lru_list.push_front(Line(tag, p));
idx[tag] = lru_list.begin();
return 0;
}
// find least recently used entry that isn't locked;
// change its tag, and return its associated pointer.
void* Cache::replace_lru_with(u64 new_tag)
{
// scan in least->most used order for first non-locked entry
List_iterator l = lru_list.end();
while(l != lru_list.begin())
{
--l;
if(!l->locked)
goto have_entry;
}
// all are locked and cannot be displaced.
// caller should add() enough lines so that this never happens.
assert(0 && "replace_lru_with not possible - all lines are locked");
return 0;
have_entry:
idx.erase(l->tag);
l->tag = new_tag;
reference(l);
return l->p;
}

146
source/lib/adts.h Executable file
View File

@ -0,0 +1,146 @@
#include "lib.h"
#include <list>
#include <map>
template<class T, int n> struct RingBuf
{
size_t size_; // # of entries in buffer
size_t pos; // index of oldest data
T data[n];
RingBuf() { clear(); }
void clear() { size_ = 0; pos = 0; }
size_t size() { return size_; }
const T& operator[](int idx) const
{
assert(idx < n);
return data[idx];
}
void push_back(const T& item)
{
if(size_ < n)
size_++;
data[pos] = item;
pos = (pos + 1) % n;
}
class const_iterator
{
public:
const_iterator() : data(0), pos(0) {}
const_iterator(const T* _data, size_t _pos) : data(_data), pos(_pos) {}
const T& operator[](int idx) const
{ return data[(pos+idx) % n]; }
const T& operator*() const
{ return data[pos]; }
const T* operator->() const
{ return &**this; }
const_iterator& operator++() // pre
{ pos = (pos+1) % n; return (*this); }
const_iterator operator++(int) // post
{ const_iterator tmp = *this; ++*this; return tmp; }
bool operator==(const const_iterator& rhs) const
{ return pos == rhs.pos && data == rhs.data; }
bool operator!=(const const_iterator& rhs) const
{ return !(*this == rhs); }
protected:
const T* data;
size_t pos;
};
const_iterator begin() const
{ return const_iterator(data, (size_ < n)? 0 : pos); }
const_iterator end() const
{ return const_iterator(data, (size_ < n)? size_ : pos); }
};
//
// cache
//
class Cache
{
public:
// return the pointer associated with the line identified by tag,
// or 0 if not in cache.
void* get(u64 tag);
// add tag to cache, and associate p with it.
// return 0 on success, or -1 if already in cache.
int add(u64 tag, void* p);
// find least recently used entry that isn't locked;
// change its tag, and return its associated pointer.
void* replace_lru_with(u64 new_tag);
int get_ctx(u64 tag, uintptr_t& ctx)
{
Line* l = find_line(tag);
if(!l)
return -1;
ctx = l->ctx;
return 0;
}
int set_ctx(u64 tag, uintptr_t ctx)
{
Line* l = find_line(tag);
if(!l)
return -1;
l->ctx = ctx;
return 0;
}
int lock(u64 tag, bool locked)
{
Line* l = find_line(tag);
if(!l)
return -1;
l->locked = locked;
return 0;
}
private:
// implementation:
// cache lines are stored in a list, most recently used in front.
// a map finds the list entry containing a given tag in log-time.
struct Line
{
u64 tag;
void* p;
bool locked; // protect from displacement
uintptr_t ctx;
Line(u64 _tag, void* _p)
{
tag = _tag;
p = _p;
locked = false;
ctx = 0;
}
};
typedef std::list<Line> List;
typedef List::iterator List_iterator;
List lru_list;
typedef std::map<u64, List_iterator> Map;
Map idx;
// return the line identified by tag, or 0 if not in cache.
Line* find_line(u64 tag);
// mark l as the most recently used line.
void reference(List_iterator l);
};

26
source/lib/config.h Executable file
View File

@ -0,0 +1,26 @@
#if defined(_WIN32)
# define OS_WIN
#elif defined(linux)
# define OS_LINUX
#elif defined(macintosh)
# define OS_MACOS
#elif defined(__APPLE__) && defined(__MACH__)
# define OS_MACOSX
#else
# error "unknown OS - add define here"
#endif
// HAVE_C99: check if compiler advertises support for C99
// (make sure it's #defined before testing value to avoid ICC warning)
#undef HAVE_C99
#ifdef __STDC_VERSION__
# if __STDC_VERSION__ >= 199901L
# define HAVE_C99
# endif
#endif
#undef HAVE_GETTIMEOFDAY
#undef HAVE_X

106
source/lib/lib.cpp Executable file
View File

@ -0,0 +1,106 @@
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "types.h"
#include "lib.h"
#include <cassert>
#include <cstdlib>
// more powerful atexit, with 0 or 1 parameters.
// callable before libc initialized, frees up the real atexit table,
// and often obviates a separate cleanup_everything function.
//
// problem: some of the functions registered here must be called after
// all other shutdown code (e.g. Winsock cleanup).
// we can't wedge ourselves between the regular atexit calls and
// process termination, so hooking exit isn't possible.
// need to use regular atexit, which must be called after _cinit.
// AFAIK, we can't interpose ourselves between libc init and constructors
// either, so constructors MUST NOT:
// - exit() (otherwise, some resources leak, because our atexit handler
// wouldn't have been registered yet - it's done from main())
// - call atexit (our exit handler would be called before its handler,
// so we may have shut down something important already).
const int MAX_EXIT_FUNCS = 8;
static struct ExitFunc
{
void* func;
uintptr_t arg;
CallConvention cc;
}
exit_funcs[MAX_EXIT_FUNCS];
static int num_exit_funcs;
// call all registered exit handlers in LIFO order.
// called from exit, so don't worry about thread safety.
static void call_exit_funcs(void)
{
ExitFunc* p = exit_funcs;
for(int i = num_exit_funcs-1; i >= 0; i--)
{
switch(p->cc)
{
case CC_CDECL_0:
((void(*)(void))p->func)();
break;
case CC_CDECL_1:
((void(*)(uintptr_t))p->func)(p->arg);
break;
#ifdef _WIN32
case CC_STDCALL_0:
((void(__stdcall*)(void))p->func)();
break;
case CC_STDCALL_1:
((void(__stdcall*)(uintptr_t))p->func)(p->arg);
break;
#endif
default:
assert(0 && "call_exit_funcs: invalid calling convention in ExitFunc!");
}
p++;
}
num_exit_funcs = 0;
}
int atexit2(void* func, uintptr_t arg, CallConvention cc)
{
if(num_exit_funcs >= MAX_EXIT_FUNCS)
{
assert("atexit2: too many functions registered. increase MAX_EXIT_FUNCS");
return -1;
}
ExitFunc* p = &exit_funcs[num_exit_funcs++];
p->func = func;
p->arg = arg;
p->cc = cc;
return 0;
}
// call from main as early as possible.
void lib_init()
{
atexit(call_exit_funcs);
}

175
source/lib/lib.h Executable file
View File

@ -0,0 +1,175 @@
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef LIB_H
#define LIB_H
#include <stddef.h>
#include "config.h"
#include "misc.h"
#include "sysdep/sysdep.h"
// yikes! avoid template warning spew on VC6
#if _MSC_VER <= 1200
#pragma warning(disable:4786)
#endif
extern void log_out(char* fmt, ...);
extern void log_out2(char* fmt, ...);
extern void log_out3(char* fmt, ...);
extern "C" int _heapchk();
// must not be used before main entered! (i.e. not from NLS constructors / functions)
#define ONCE(ONCE_code__)\
{\
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;\
static bool ONCE_done__ = false;\
if(pthread_mutex_trylock(&(mutex)) == 0 && !ONCE_done__)\
{\
ONCE_done__ = true;\
ONCE_code__;\
}\
}
enum LibError
{
ERR_INVALID_HANDLE = -1000,
ERR_NO_MEM = -1001,
ERR_EOF = -1002, // attempted to read beyond EOF
ERR_INVALID_PARAM = -1003,
ERR_FILE_NOT_FOUND = -1004,
ERR_x
};
#ifndef MIN
#define MIN(a, b) (((a) < (b))? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b))? (a) : (b))
#endif
#define UNUSED(param) (void)param;
//
// compile-time assert, especially useful for testing sizeof().
// no runtime overhead; may be used anywhere, including file scope.
//
// generate a symbol containing the line number of the macro invocation.
// used to give a unique name (per file) to types made by cassert.
// we can't prepend __FILE__ to make it globally unique -
// the filename may be enclosed in quotes.
#define MAKE_UID2__(l) LINE_ ## l
#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.
#define cassert(expr) struct UID__ { int CASSERT_FAILURE: (expr); };
// less helpful error message, but redefinition doesn't trigger warnings.
#define cassert2(expr) extern char CASSERT_FAILURE[expr];
// note: alternative method in C++: specialize a struct only for true;
// sizeof will raise 'incomplete type' errors if instantiated with false.
#define STMT(STMT_code__) do { STMT_code__; } while(0)
// converts 4 character string to u32 for easy comparison
// can't pass code as string, and use s[0]..s[3], because
// VC6/7 don't realize the macro is constant
// (it should be useable as a switch{} expression)
#ifdef BIG_ENDIAN
#define FOURCC(a,b,c,d) ( ((u32)a << 24) | ((u32)b << 16) | \
((u32)c << 8 ) | ((u32)d << 0 ) )
#else
#define FOURCC(a,b,c,d) ( ((u32)a << 0 ) | ((u32)b << 8 ) | \
((u32)c << 16) | ((u32)d << 24) )
#endif
const size_t KB = 1 << 10;
const size_t MB = 1 << 20;
#ifdef _WIN32
#define DIR_SEP '\\'
#else
#define DIR_SEP '/'
#endif
extern u32 fnv_hash(const char* str, size_t len);
#define BIT(n) (1ul << (n))
// call from main as early as possible.
void lib_init();
enum CallConvention // number of parameters
{
CC_CDECL_0,
CC_CDECL_1,
#ifdef _WIN32
CC_STDCALL_0,
CC_STDCALL_1,
#endif
CC_UNUSED // no trailing comma if !_WIN32
};
#ifdef _WIN32
#define CC_DEFAULT CC_STDCALL_1
#else
#define CC_DEFAULT CC_CDECL_1
#endif
// more powerful atexit: registers an exit handler, with 0 or 1 parameters.
// callable before libc initialized, and frees up the real atexit table.
// stdcall convention is provided on Windows to call APIs (e.g. WSACleanup).
// for these to be called at exit, lib_main must be invoked after _cinit.
extern int atexit2(void* func, uintptr_t arg, CallConvention cc = CC_DEFAULT);
#include "posix.h"
#endif // #ifndef LIB_H

953
source/lib/res/file.cpp Executable file
View File

@ -0,0 +1,953 @@
// file layer on top of POSIX.
// provides streaming support and caching.
//
// Copyright (c) 2004 Jan Wassenberg
//
// This file is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This file 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "lib.h"
#include "file.h"
#include "h_mgr.h"
#include "mem.h"
#include "detect.h"
#include "adts.h"
#include <cassert>
#include <vector>
#include <functional>
#include <algorithm>
// block := power-of-two sized chunk of a file.
// all transfers are expanded to naturally aligned, whole blocks
// (this makes caching parts of files feasible; it is also much faster
// for some aio implementations, e.g. wposix).
const size_t BLOCK_SIZE_LOG2 = 16; // 2**16 = 64 KB
const size_t BLOCK_SIZE = 1ul << BLOCK_SIZE_LOG2;
// rationale for aio, instead of only using mmap:
// - parallel: instead of just waiting for the transfer to complete,
// other work can be done in the meantime.
// example: decompressing from a Zip archive is practically free,
// because we inflate one block while reading the next.
// - throughput: with aio, the drive always has something to do, as opposed
// to read requests triggered by the OS for mapped files, which come
// in smaller chunks. this leads to much higher transfer rates.
// - memory: when used with VFS, aio makes better use of a file cache.
// data is generally compressed in an archive. a cache should store the
// decompressed and decoded (e.g. TGA color swapping) data; mmap would
// keep the original, compressed data in memory, which doesn't help.
// we bypass the OS file cache via aio, and store partial blocks here (*);
// higher level routines will cache the actual useful data.
// * requests for part of a block are usually followed by another.
///////////////////////////////////////////////////////////////////////////////
//
// file open/close
//
///////////////////////////////////////////////////////////////////////////////
// interface rationale:
// - this module depends on the handle manager for IO management,
// but should be useable without the VFS (even if they are designed
// to work together).
// - allocating a Handle for the file info would solve several problems
// (see below), but we don't want to allocate 2..3 (VFS, file, Zip file)
// for every file opened - that'd add up quickly.
// the Files are always freed at exit though, since they're part of
// VFile handles in the VFS.
// - we want the VFS open logic to be triggered on file invalidate
// (if the dev. file is deleted, we should use what's in the archives).
// we don't want to make this module depend on VFS, so we can't
// call up into vfs_foreach_path from reload here =>
// VFS needs to allocate the handle.
// - no problem exposing our internals via File struct -
// we're only used by the VFS and Zip modules. don't bother making
// an opaque struct - that'd have to be kept in sync with the real thing.
// - when Zip opens its archives via file_open, a handle isn't needed -
// the Zip module hides its File struct (required to close the file),
// and the Handle approach doesn't guard against some idiot calling
// close(our_fd) directly, either.
// marker for File struct, to make sure it's valid
#ifdef PARANOIA
static const u32 FILE_MAGIC = FOURCC('F','I','L','E');
#endif
static int file_validate(uint line, File* f)
{
const char* msg = "";
int err = -1;
if(!f)
{
msg = "File* parameter = 0";
err = ERR_INVALID_PARAM;
}
#ifdef PARANOIA
else if(f->magic != FILE_MAGIC)
msg = "File corrupted (magic field incorrect)";
#endif
else if(f->fd < 0)
msg = "File fd invalid (< 0)";
#ifndef NDEBUG
else if(!f->fn_hash)
msg = "File fn_hash not set";
#endif
// everything is OK
else
return 0;
// failed somewhere - err is the error code,
// or -1 if not set specifically above.
debug_out("file_validate at line %d failed: %s\n", line, msg);
assert(0 && "file_validate failed");
return err;
}
#define CHECK_FILE(f)\
do\
{\
int err = file_validate(__LINE__, f);\
if(err < 0)\
return err;\
}\
while(0);
int file_open(const char* path, int flags, File* f)
{
memset(f, 0, sizeof(File));
if(!f)
goto invalid_f;
// jump to CHECK_FILE post-check, which will handle this.
{
// don't stat if opening for writing - the file may not exist yet
size_t size = 0;
int mode = O_RDONLY;
if(flags & FILE_WRITE)
mode = O_WRONLY;
else
{
struct stat s;
int err = stat(path, &s);
if(err < 0)
return err;
size = s.st_size;
}
int fd = open(path, mode);
if(fd < 0)
return 1;
#ifdef PARANOIA
f->magic = FILE_MAGIC;
#endif
f->size = size;
f->flags = flags;
f->fn_hash = fnv_hash(path, strlen(path));
f->mapping = 0;
f->fd = fd;
}
invalid_f:
CHECK_FILE(f)
return 0;
}
int file_close(File* f)
{
CHECK_FILE(f);
// (check fd to avoid BoundsChecker warning about invalid close() param)
if(f->fd != -1)
{
close(f->fd);
f->fd = -1;
}
f->size = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
//
// block cache
//
///////////////////////////////////////////////////////////////////////////////
static Cache c;
// create a tag for use with the Cache that uniquely identifies
// the block from the file <fn_hash> containing <ofs>.
static u64 make_tag(u32 fn_hash, size_t ofs)
{
// tag format: filename hash | block number
// 63 32 31 0
//
// we assume the hash (currently: FNV) is unique for all filenames.
// chance of a collision is tiny, and a build tool will ensure
// filenames in the VFS archives are safe.
//
// block_num will always fit in 32 bits (assuming maximum file size
// = 2^32 * BLOCK_SIZE = 2^48 - enough); we check this, but don't
// include a workaround. we could return 0, and the caller would have
// to allocate their own buffer, but don't bother.
// make sure block_num fits in 32 bits
size_t block_num = ofs / BLOCK_SIZE;
assert(block_num <= 0xffffffff);
u64 tag = fn_hash; // careful, don't shift a u32 32 bits left
tag <<= 32;
tag |= block_num;
return tag;
}
//
static void* block_alloc(u64 tag)
{
void* p;
// initialize pool, if not done already.
static size_t cache_size;
static size_t cache_pos = 0;
static void* cache = 0;
if(!cache)
{
cache_size = 16 * BLOCK_SIZE;
get_mem_status();
// TODO: adjust cache_size
cache = mem_alloc(cache_size, BLOCK_SIZE);
if(!cache)
return 0;
}
// we have free blocks - add to cache
if(cache_pos < cache_size)
{
p = (char*)cache + cache_pos;
cache_pos += BLOCK_SIZE;
if(c.add(tag, p) < 0)
{
assert(0 && "block_alloc: Cache::add failed!");
return 0;
}
}
// all of our pool's blocks are in the cache.
// displace the LRU entry. if not possible (all are locked), fail.
else
{
p = c.replace_lru_with(tag);
if(!p)
return 0;
}
if(c.lock(tag, true) < 0)
assert(0 && "block_alloc: Cache::lock failed!");
// can't happen: only cause is tag not found, but we successfully
// added it above. if it did fail, that'd be bad: we leak the block,
// and/or the buffer may be displaced while in use. hence, assert.
return p;
}
// modifying cached blocks is not supported.
// we could allocate a new buffer and update the cache to point to that,
// but that'd fragment our memory pool.
// instead, add a copy on write call, if necessary.
static int block_retrieve(u64 tag, void*& p)
{
p = c.get(tag);
return p? 0 : -1;
// important note:
// for the purposes of starting the IO, we can regard blocks whose read
// is still pending it as cached. when getting the IO results,
// we'll have to wait on the actual IO for that block.
//
// don't want to require IOs to be completed in order of issue:
// that'd mean only 1 caller can read from file at a time.
// would obviate associating tag with IO, but is overly restrictive.
}
static int block_discard(u64 tag)
{
return c.lock(tag, false);
}
void file_free_buf(void *p)
{
}
// remove from cache?
int discard_buf(void* p)
{
return 0;
}
int free_buf(void* p)
{
uintptr_t _p = (uintptr_t)p;
void* actual_p = (void*)(_p - (_p % BLOCK_SIZE)); // round down
return mem_free(actual_p);
}
///////////////////////////////////////////////////////////////////////////////
//
// async I/O
//
///////////////////////////////////////////////////////////////////////////////
enum
{
CACHED = 1,
};
struct IO
{
struct aiocb* cb;
// struct aiocb is too big to store here.
// IOs are reused, so we don't allocate a
// new aiocb every file_start_io.
u64 tag;
void* user_p;
size_t user_ofs;
size_t user_size;
uint flags;
void* block;
};
H_TYPE_DEFINE(IO)
// don't support forcibly closing files => don't need to keep track of
// all IOs pending for each file. too much work, little benefit.
static void IO_init(IO* io, va_list args)
{
size_t size = round_up(sizeof(struct aiocb), 16);
io->cb = (struct aiocb*)mem_alloc(size, 16, MEM_ZERO);
}
static void IO_dtor(IO* io)
{
mem_free(io->cb);
}
// TODO: prevent reload if already open, i.e. IO pending
// we don't support transparent read resume after file invalidation.
// if the file has changed, we'd risk returning inconsistent data.
// i don't think it's possible anyway, without controlling the AIO
// implementation: when we cancel, we can't prevent the app from calling
// aio_result, which would terminate the read.
static int IO_reload(IO* io, const char* fn)
{
if(!io->cb)
return -1;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// extra layer on top of h_alloc, so we can reuse IOs
//
// (avoids allocating the aiocb every IO => less fragmentation)
//
// don't worry about reassigning IOs to their associated file -
// they don't need to be reloaded, since the VFS refuses reload
// requests for files with pending IO.
typedef std::vector<Handle> IOList;
// accessed MRU for better cache locality
static IOList free_ios;
// list of all IOs allocated.
// used to find active IO, given tag (see below).
// also used to free all IOs before the handle manager
// cleans up at exit, so they aren't seen as resource leaks.
static IOList all_ios;
struct Free
{
void operator()(Handle h)
{
h_free(h, H_IO);
}
};
// free all allocated IOs, so they aren't seen as resource leaks.
static void io_cleanup(void)
{
std::for_each(all_ios.begin(), all_ios.end(), Free());
}
static Handle io_alloc()
{
ONCE(atexit(io_cleanup))
// grab from freelist
if(!free_ios.empty())
{
Handle h = free_ios.back();
free_ios.pop_back();
// note:
// we don't check if the freelist contains valid handles.
// that "can't happen", and if it does, it'll be caught
// by the handle dereference in file_start_io.
//
// note that no one else can actually free an IO -
// that would require its handle type, which is private to
// this module. the free_io call just adds it to the freelist;
// all allocated IOs are destroyed by the handle manager at exit.
}
// allocate a new IO
Handle h = h_alloc(H_IO, 0);
// .. it's valid - store in list.
if(h > 0)
all_ios.push_back(h);
return h;
}
static int io_free(Handle hio)
{
// mark it unused, and incidentally make sure hio is valid
// before adding to freelist.
H_DEREF(hio, IO, io);
io->tag = 0;
free_ios.push_back(hio);
return 0;
}
// need to find IO, given tag, to make sure a block
// that is marked cached has actually been read.
// it is expected that there only be a few allocated IOs,
// so it's ok to search this list every cache hit.
// adding to the cache data structure would be messier.
struct FindTag : public std::binary_function<Handle, u64, bool>
{
bool operator()(Handle hio, u64 tag) const
{
// can't use H_DEREF - we return bool
IO* io = (IO*)h_user_data(hio, H_IO);
if(!io)
{
assert(0 && "invalid handle in all_ios list!");
return false;
}
return io->tag == tag;
}
};
static Handle io_find_tag(u64 tag)
{
IOList::const_iterator it;
it = std::find_if(all_ios.begin(), all_ios.end(), std::bind2nd(FindTag(), tag));
// not found
if(it == all_ios.end())
return 0;
return *it;
}
///////////////////////////////////////////////////////////////////////////////
// rationale for extra alignment / copy layer, even though aio takes care of it:
// aio would read pad to its minimum read alignment, copy over, and be done;
// in our case, if something is unaligned, a request for the remainder of the
// block is likely to follow, so we want to cache the whole block.
// pads the request up to BLOCK_SIZE, and stores the original parameters in IO.
// transfers of more than 1 block (including padding) are allowed, but do not
// go through the cache. don't see any case where that's necessary, though.
Handle file_start_io(File* f, size_t user_ofs, size_t user_size, void* user_p)
{
int err;
CHECK_FILE(f)
if(user_size == 0)
{
assert(0 && "file_start_io: user_size = 0 - why?");
return ERR_INVALID_PARAM;
}
if(user_ofs >= f->size)
{
assert(0 && "file_start_io: user_ofs beyond f->size");
return -1;
}
size_t bytes_left = f->size - user_ofs; // > 0
int op = (f->flags & FILE_WRITE)? LIO_WRITE : LIO_READ;
// don't read beyond EOF
if(user_size > bytes_left) // avoid min() - it wants int
user_size = bytes_left;
const u64 tag = make_tag(f->fn_hash, user_ofs);
// allocate IO slot
Handle hio = io_alloc();
H_DEREF(hio, IO, io);
struct aiocb* cb = io->cb;
io->tag = tag;
io->user_p = user_p;
io->user_ofs = user_ofs;
io->user_size = user_size;
// notes: io->flags and io->block are already zeroed;
// cb holds the actual IO request (aligned offset and size).
// if already cached, we're done
if(block_retrieve(tag, io->block) == 0)
{
io->flags = CACHED;
return hio;
}
// aio already safely handles unaligned buffers or offsets.
// when reading zip files, we don't want to repeat a read
// if a block containing end of one file and start of the next
// (speed concern).
//
// note: cache even if this is the last block before EOF:
// a zip archive may contain one last file in the block.
// if not, no loss - the buffer will be LRU, and reused.
size_t ofs = user_ofs;
size_t padding = ofs % BLOCK_SIZE;
ofs -= padding;
size_t size = round_up(padding + user_size, BLOCK_SIZE);
void* buf = 0;
void* our_buf = 0;
if(user_p && !padding)
buf = user_p;
else
{
if(size == BLOCK_SIZE)
our_buf = io->block = block_alloc(tag);
// transferring more than one block - doesn't go through cache!
else
our_buf = mem_alloc(size, BLOCK_SIZE);
if(!our_buf)
{
err = ERR_NO_MEM;
goto fail;
}
buf = our_buf;
}
// send off async read/write request
cb->aio_lio_opcode = op;
cb->aio_buf = buf;
cb->aio_fildes = f->fd;
cb->aio_offset = (off_t)ofs;
cb->aio_nbytes = size;
err = lio_listio(LIO_NOWAIT, &cb, 1, (struct sigevent*)0);
// return as soon as I/O is queued
if(err < 0)
{
fail:
file_discard_io(hio);
file_free_buf(our_buf);
return err;
}
return hio;
}
int file_wait_io(const Handle hio, void*& p, size_t& size)
{
int ret = 0;
p = 0;
size = 0;
H_DEREF(hio, IO, io);
struct aiocb* cb = io->cb;
size = io->user_size;
ssize_t bytes_transferred;
// block's tag is in cache. need to check if its read is still pending.
if(io->flags & CACHED)
{
Handle cache_hio = io_find_tag(io->tag);
// was already finished - don't wait
if(cache_hio <= 0)
goto skip_wait;
// not finished yet; wait for it below, as with uncached reads.
else
{
H_DEREF(cache_hio, IO, cache_io);
// can't fail, since io_find_tag has to dereference each handle.
cb = cache_io->cb;
}
}
// wait for transfer to complete
{
while(aio_error(cb) == -EINPROGRESS)
aio_suspend(&cb, 1, NULL);
bytes_transferred = aio_return(cb);
assert(bytes_transferred == BLOCK_SIZE);
ret = bytes_transferred? 0 : -1;
}
skip_wait:
if(io->block)
{
size_t padding = io->user_ofs % BLOCK_SIZE;
void* src = (char*)io->block + padding;
// copy over into user's buffer
if(io->user_p)
{
p = io->user_p;
memcpy(p, src, io->user_size);
}
// return pointer to cache block
else
p = src;
}
// read directly into target buffer
else
p = cb->aio_buf;
return ret;
}
int file_discard_io(Handle& hio)
{
H_DEREF(hio, IO, io);
block_discard(io->tag);
io_free(hio);
return 0;
}
// transfer modes:
// *p != 0: *p is the source/destination address for the transfer.
// (FILE_MEM_READONLY?)
// *p == 0: allocate a buffer, read into it, and return it in *p.
// when no longer needed, it must be freed via file_discard_buf.
// p == 0: read raw_size bytes from file, starting at offset raw_ofs,
// into temp buffers; each block read is passed to cb, which is
// expected to write actual_size bytes total to its output buffer
// (for which it is responsible).
// useful for reading compressed data.
//
// return (positive) number of raw bytes transferred if successful;
// otherwise, an error code.
ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** const p,
const FILE_IO_CB cb, const uintptr_t ctx) // optional
{
CHECK_FILE(f)
const bool is_write = (f->flags == FILE_WRITE);
//
// transfer parameters
//
// reading: make sure we don't go beyond EOF
if(!is_write)
{
if(raw_ofs >= f->size)
return ERR_EOF;
size_t bytes_left = f->size - raw_ofs; // > 0
size_t max_size = MIN(bytes_left, raw_size);
}
// writing: make sure buffer is valid
else
{
// temp buffer OR supposed to be allocated here: invalid
if(!p || !*p)
{
assert(0 && "file_io: write to file from 0 buffer");
return ERR_INVALID_PARAM;
}
}
const size_t misalign = raw_ofs % BLOCK_SIZE;
// actual transfer start offset
const size_t start_ofs = raw_ofs - misalign; // BLOCK_SIZE-aligned
void* buf = 0; // I/O source or sink; assume temp buffer
void* our_buf = 0; // buffer we allocate, if necessary
// check buffer param
// .. temp buffer requested
if(!p)
; // nothing to do - buf already initialized to 0
// .. user specified, or requesting we allocate the buffer
else
{
// the underlying aio implementation likes buffer and offset to be
// sector-aligned; if not, the transfer goes through an align buffer,
// and requires an extra memcpy.
//
// if the user specifies an unaligned buffer, there's not much we can
// do - we can't assume the buffer contains padding. therefore,
// callers should let us allocate the buffer if possible.
//
// if ofs misalign = buffer, only the first and last blocks will need
// to be copied by aio, since we read up to the next block boundary.
// otherwise, everything will have to be copied; at least we split
// the read into blocks, so aio's buffer won't have to cover the
// whole file.
// user specified buffer
if(*p)
{
buf = *p;
// warn in debug build if buffer not aligned
#ifndef NDEBUG
size_t buf_misalign = ((uintptr_t)buf) % BLOCK_SIZE;
if(misalign != buf_misalign)
debug_out("file_io: warning: buffer %p and offset %x are misaligned", buf, raw_ofs);
#endif
}
// requesting we allocate the buffer
else
{
size_t buf_size = round_up(misalign + raw_size, BLOCK_SIZE);
our_buf = mem_alloc(buf_size, BLOCK_SIZE);
if(!our_buf)
return ERR_NO_MEM;
buf = our_buf;
*p = (char*)buf + misalign;
}
}
// buf is now the source or sink, regardless of who allocated it.
// we need to keep our_buf (memory we allocated), so we can free
// it if we fail; it's 0 if the caller passed in a buffer.
//
// now we read the file in 64 KB chunks, N-buffered.
// if reading from Zip, inflate while reading the next block.
//
const int MAX_IOS = 2;
Handle ios[MAX_IOS] = { 0, 0 };
int head = 0;
int tail = 0;
int pending_ios = 0;
bool all_issued = false;
size_t raw_cnt = 0; // amount of raw data transferred so far
size_t issue_cnt = 0; // sum of I/O transfer requests
ssize_t err = +1; // loop terminates if <= 0
for(;;)
{
// queue not full, data remaining to transfer, and no error:
// start transferring next block.
if(pending_ios < MAX_IOS && !all_issued && err > 0)
{
// calculate issue_size:
// want to transfer up to the next block boundary.
size_t issue_ofs = start_ofs + issue_cnt;
const size_t left_in_block = BLOCK_SIZE - (issue_ofs % BLOCK_SIZE);
const size_t left_in_file = raw_size - issue_cnt;
size_t issue_size = MIN(left_in_block, left_in_file);
// assume temp buffer allocated by file_start_io
void* data = 0;
// if transferring to/from normal file, use buf instead
if(buf)
data = (void*)((uintptr_t)buf + issue_cnt);
Handle hio = file_start_io(f, issue_ofs, issue_size, data);
if(hio <= 0)
err = (ssize_t)hio;
// transfer failed - loop will now terminate after
// waiting for all pending transfers to complete.
issue_cnt += issue_size;
if(issue_cnt >= raw_size)
all_issued = true;
// store IO in ring buffer
ios[head] = hio;
head = (head + 1) % MAX_IOS;
pending_ios++;
}
// IO pending: wait for it to complete, and process it.
else if(pending_ios)
{
Handle& hio = ios[tail];
tail = (tail + 1) % MAX_IOS;
pending_ios--;
void* block;
size_t size;
int ret = file_wait_io(hio, block, size);
if(ret < 0)
err = (ssize_t)ret;
//// if size comes out short, we must be at EOF
raw_cnt += size;
if(cb && !(err <= 0))
{
ssize_t ret = cb(ctx, block, size);
// if negative: processing failed; if 0, callback is finished.
// either way, loop will now terminate after waiting for all
// pending transfers to complete.
if(ret <= 0)
err = ret;
}
file_discard_io(hio); // zeroes array entry
}
// (all issued OR error) AND no pending transfers - done.
else
break;
}
// failed (0 means callback reports it's finished)
if(err < 0)
{
// user didn't specify output buffer - free what we allocated,
// and clear 'out', which points to the freed buffer.
if(our_buf)
{
mem_free(our_buf);
*p = 0;
// we only allocate if p && *p, but had set *p above.
}
return err;
}
assert(issue_cnt >= raw_cnt && raw_cnt == raw_size);
return (ssize_t)raw_cnt;
}
///////////////////////////////////////////////////////////////////////////////
//
// memory mapping
//
///////////////////////////////////////////////////////////////////////////////
int file_map(File* f, void*& p, size_t& size)
{
CHECK_FILE(f)
p = f->mapping;
size = f->size;
// already mapped - done
if(p)
return 0;
int prot = (f->flags & FILE_WRITE)? PROT_WRITE : PROT_READ;
p = f->mapping = mmap((void*)0, (uint)size, prot, MAP_PRIVATE, f->fd, (long)0);
if(!p)
{
size = 0;
return ERR_NO_MEM;
}
return 0;
}
int file_unmap(File* f)
{
CHECK_FILE(f)
void* p = f->mapping;
// not currently mapped
if(!p)
return -1;
f->mapping = 0;
// don't reset size - the file is still open.
return munmap(p, (uint)f->size);
}

79
source/lib/res/file.h Executable file
View File

@ -0,0 +1,79 @@
// file layer on top of POSIX.
// provides streaming support and caching.
//
// Copyright (c) 2004 Jan Wassenberg
//
// This file is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This file 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef FILE_H
#define FILE_H
#include "h_mgr.h"
#include "lib.h"
struct File
{
#ifdef PARANOIA
u32 magic;
#endif
size_t size;
int flags;
u32 fn_hash;
void* mapping;
int fd;
};
enum
{
// open for writing
FILE_WRITE = 1,
FILE_MEM_READONLY = 2,
// do not cache any part of the file
// (e.g. if caching on a higher level)
FILE_NO_CACHE = 4,
};
extern int file_open(const char* path, int flags, File* f);
extern int file_close(File* f);
extern int file_map(File* f, void*& p, size_t& size);
extern int file_unmap(File* f);
extern Handle file_start_io(File* f, size_t ofs, size_t size, void* buf);
extern int file_wait_io(const Handle hio, void*& p, size_t& size);
extern int file_discard_io(Handle& hio);
// return value:
// < 0: failed (do not call again) - abort transfer.
// = 0: done (no more data needed) - stop transfer gracefully.
// > 0: bytes output (not used ATM; useful for statistics) - continue.
typedef ssize_t(*FILE_IO_CB)(uintptr_t ctx, void* p, size_t size);
extern ssize_t file_io(File* f, size_t ofs, size_t size, void** p,
FILE_IO_CB cb = 0, uintptr_t ctx = 0);
#endif // #ifndef FILE_H

239
source/lib/res/font.cpp Executable file
View File

@ -0,0 +1,239 @@
/*
* OpenGL texture font
*
* Copyright (c) 2002 Jan Wassenberg
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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. See the GNU
* General Public License for more details.
*
* Contact info:
* Jan.Wassenberg@stud.uni-karlsruhe.de
* http://www.stud.uni-karlsruhe.de/~urkt/
*/
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "lib.h"
#include "mem.h"
#include "font.h"
#include "h_mgr.h"
#include "vfs.h"
#include "tex.h"
#include "ogl.h"
#include "misc.h"
/*
#include <ft2build.h>
//#include FT_FREETYPE_H
static FT_Library lib;
static void cleanup(void)
{
FT_Done_FreeType(&lib);
}
int build_font(const char* in_ttf, const char* out_fnt, const char* out_raw, int height)
{
if(!lib)
{
FT_Init_FreeType(&lib);
atexit(cleanup);
}
FT_Face face;
if(FT_New_Face(lib, in_ttf, 0, &face))
return -1;
FT_Set_Pixel_Sizes(face, 0, height);
const int tex_dim = 256;
const int w = 24, h = 24;
FILE* f = fopen(out_fnt, "w");
if(!f)
return -1;
fprintf(f, "%s\n%d %d\n", out_raw, w, h); // header
u8* tex = (u8*)calloc(tex_dim*tex_dim, 2); // GL_LUMINANCE_ALPHA fmt
int x = 0, y = 0;
for(int c = 32; c < 128; c++) // for each (printable) char
{
FT_Load_Char(face, c, FT_LOAD_RENDER);
const u8* bmp = face->glyph->bitmap.buffer;
// copy glyph's bitmap into texture
for(int j = 0; j < face->glyph->bitmap.rows; j++)
{
u8* pos = &tex[(y+h-8-face->glyph->bitmap_top+j)*tex_dim*2 + (x+face->glyph->bitmap_left)*2];
for(int i = 0; i < face->glyph->bitmap.width; i++)
{
*pos++ = *bmp; // luminance
*pos++ = (*bmp)? 0xff : 0x00; // alpha
bmp++;
}
}
x += w;
if(x + w >= tex_dim)
x = 0, y += h;
fprintf(f, "%d ", face->glyph->advance.x / 64);
}
fclose(f);
// write texture
f = fopen(out_raw, "wb");
fwrite(tex, 2, tex_dim*tex_dim, f);
fclose(f);
free(tex);
return 0;
}
*/
struct Font
{
Handle ht;
uint list_base;
};
H_TYPE_DEFINE(Font)
static void Font_init(Font* f, va_list args)
{
}
static void Font_dtor(Font* f)
{
tex_free(f->ht);
glDeleteLists(f->list_base, 96);
}
static int Font_reload(Font* f, const char* fn)
{
// we pass the loaded file to sscanf. the data needs to be 0-terminated,
// so we read, and then copy into a 0-terminated buffer. ugh.
void* tmp_file;
size_t file_size;
Handle err = vfs_load(fn, tmp_file, file_size);
if(err <= 0)
return (int)err;
void* p = mem_alloc(file_size + 1);
if(!p)
return ERR_NO_MEM;
memcpy(p, tmp_file, file_size);
((char*)p)[file_size] = 0; // 0-terminate for sscanf
int pos; // current position in the file
const char* file = (const char*)p;
// read header
char tex_filename[PATH_MAX];
int x_stride, y_stride; // glyph spacing in texture
if(sscanf(file, "%s\n%d %d\n%n", tex_filename, &x_stride, &y_stride, &pos) != 3)
{
debug_out("Font_reload: \"%s\": header is invalid", fn);
return -1;
}
// read glyph widths
int adv[128];
for(int i = 32; i < 128; i++)
{
file += pos;
if(sscanf(file, "%d %n", &adv[i], &pos) != 1)
{
debug_out("Font_reload: \"%s\": glyph width array is invalid", fn);
return -1;
}
}
mem_free(p);
// load glyph texture
const Handle ht = tex_load(tex_filename);
if(ht <= 0)
return ht;
tex_upload(ht);
const int tex_dim = 256;
const float du = (float)x_stride / (float)tex_dim;
float u = 0, v = 0;
// create a display list for each glyph
const uint list_base = glGenLists(128);
for(int c = 32; c < 128; c++)
{
const float w = (float)adv[c], h = (float)y_stride; // glyph quad width/height
const float tw = w / tex_dim, th = h / tex_dim; // texture space width/height
glNewList(list_base+c, GL_COMPILE);
glBegin(GL_QUADS);
glTexCoord2f(u, v+th); glVertex2f(0, 0);
glTexCoord2f(u+tw, v+th); glVertex2f(w, 0);
glTexCoord2f(u+tw, v); glVertex2f(w, h);
glTexCoord2f(u, v); glVertex2f(0, h);
glEnd();
glTranslatef(w, 0, 0);
glEndList();
u += du;
if(u + du > 1.f)
u = 0.f, v += th;
}
f->ht = ht;
f->list_base = list_base;
return 0;
}
inline Handle font_load(const char* fn, int scope)
{
return h_alloc(H_Font, fn, scope);
}
int font_bind(const Handle h)
{
H_DEREF(h, Font, f);
tex_bind(f->ht);
glListBase(f->list_base);
return 0;
}
void glprintf(const char* fmt, ...)
{
va_list args;
char buf[1024]; buf[1023] = 0;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf)-1, fmt, args);
va_end(args);
glCallLists((GLsizei)strlen(buf), GL_UNSIGNED_BYTE, buf);
}

81
source/lib/res/font.h Executable file
View File

@ -0,0 +1,81 @@
// OpenGL texture font
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __FONT_H__
#define __FONT_H__
#include "types.h"
#include "h_mgr.h"
// load and return a handle to the font defined in <fn>
extern Handle font_load(const char* fn, int scope = RES_STATIC);
// use the font referenced by h for all subsequent glprintf() calls
extern int font_bind(Handle h);
// output text at current OpenGL modelview pos.
// assumes ortho projection with texturing, alpha test, and blending enabled.
// must bind a font before calling!
extern void glprintf(const char* fmt, ...);
#endif // #ifndef __FONT_H__
/*
EXAMPLE:
#include "font.h"
Handle h;
void init()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, xres, 0, yres, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5);
h = font_load("font.fnt");
if(!h)
abort();
}
void render()
{
font_bind(h);
glprintf("string");
}
// FONT FILE FORMAT:
%s // texture file name
%d %d // width/height of glyphs in the texture
%d [...] %d // advance width for chars 32..127
*/

518
source/lib/res/h_mgr.cpp Executable file
View File

@ -0,0 +1,518 @@
// handle-based resource manager
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <climits>
#include "lib.h"
#include "h_mgr.h"
#include "mem.h"
// TODO: h_find - required for caching
//
// handle
//
// TODO: explain handle scheme, how it solves problems
// 0 = invalid handle value
// < 0 is an error code (we assume < 0 <==> MSB is set -
// true for 1s and 2s complement and sign-magnitude systems)
//
// tag = 1-based; index = 0-based
//
// shift value = # bits between LSB and field LSB.
// may be larger than the field type - only shift Handle vars!
// fields:
// - allows checking if the resource has been freed
// determines maximum unambiguous resource allocs
#define TAG_BITS 32
const uint TAG_SHIFT = 0;
const Handle TAG_MASK = (((Handle)1) << TAG_BITS) - 1;
// - index into data array
// determines maximum (currently open) handles
#define IDX_BITS 16
const uint IDX_SHIFT = 32;
const u32 IDX_MASK = (((Handle)1) << IDX_BITS) - 1;
cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT);
// return the handle's index field (always non-negative).
// no error checking!
static inline i32 h_idx(const Handle h)
{ return (i32)((h >> IDX_SHIFT) & IDX_MASK); }
// return the handle's tag field.
// no error checking!
static inline u32 h_tag(const Handle h)
{ return (u32)((h >> TAG_SHIFT) & TAG_MASK); }
// build a handle from index and tag
static inline Handle handle(const i32 idx, const i32 tag)
{
assert(idx <= IDX_MASK && tag <= TAG_MASK && "handle: idx or tag too big");
// somewhat clunky, but be careful with the shift:
// *_SHIFT may be larger than its field's type.
Handle _idx = idx & IDX_MASK; _idx <<= IDX_SHIFT;
Handle _tag = tag & TAG_MASK; _tag <<= TAG_SHIFT;
return _idx | _tag;
}
inline __int64 __declspec(naked) GetCycleCount()
{
__asm
{
RDTSC
}
}
//
// internal per-resource-instance data
//
// determines maximum number of references to a resource.
// a handle's idx field isn't stored in its HDATA entry (not needed);
// to save space, this should take its place, i.e. it should fit in IDX_BITS.
static const uint REF_BITS = 12;
static const u32 REF_MAX = 1ul << REF_BITS;
// chosen so that all current resource structs are covered,
// and so sizeof(HDATA) is a power of 2 (for more efficient array access
// and array page usage).
static const size_t HDATA_USER_SIZE = 48;
// 64 bytes
struct HDATA
{
uintptr_t key;
u32 tag : TAG_BITS;
u32 refs : REF_BITS;
H_Type type;
const char* fn;
u8 user[HDATA_USER_SIZE];
};
// max data array entries. compared to last_in_use => signed.
static const i32 hdata_cap = 1ul << IDX_BITS;
// allocate entries as needed so as not to waste memory
// (hdata_cap may be large). deque-style array of pages
// to balance locality, fragmentation, and waste.
static const size_t PAGE_SIZE = 4096;
static const uint hdata_per_page = PAGE_SIZE / sizeof(HDATA);
static const uint num_pages = hdata_cap / hdata_per_page;
static HDATA* pages[num_pages];
// these must be signed, because there won't always be a valid
// first or last element.
static i32 first_free = -1; // don't want to scan array every h_alloc
static i32 last_in_use = -1; // don't search unused entries
// error checking strategy:
// all handles passed in go through h_data(Handle, Type)
// get an array entry (array is non-contiguous).
// fails (returns 0) if idx is out of bounds, or if accessing a new page
// for the first time, and there's not enough memory to allocate it.
// used by h_alloc to find a free entry.
static HDATA* h_data(const i32 idx)
{
// don't compare against last_in_use - this is called before allocating
// new entries, and to check if the next (but possibly not yet valid)
// entry is free. tag check protects against using unallocated entries.
if(idx < 0 || idx >= hdata_cap)
return 0;
HDATA*& page = pages[idx / hdata_per_page];
if(!page)
{
page = (HDATA*)calloc(PAGE_SIZE, 1);
if(!page)
return 0;
}
return &page[idx % hdata_per_page];
}
int hdataseq=0;
// get HDATA for the given handle. verifies the handle
// isn't invalid or an error code, and checks the tag field.
// used by functions callable for any handle type, e.g. h_filename.
static HDATA* h_data(const Handle h)
{
hdataseq++;
_heapchk();
// invalid, or an error code
if(h <= 0)
return 0;
i32 idx = h_idx(h);
// this function is only called for existing handles.
// they'd also fail the tag check below, but bail here
// to avoid needlessly allocating that entry's page.
if(idx > last_in_use)
return 0;
HDATA* hd = h_data(idx);
if(!hd)
return 0;
// note: tag = 0 marks unused entries => is invalid
u32 tag = h_tag(h);
if(tag == 0 || tag != hd->tag)
return 0;
return hd;
}
// get HDATA for the given handle, also checking handle type.
// used by most functions accessing handle data.
static HDATA* h_data(const Handle h, const H_Type type)
{
HDATA* hd = h_data(h);
if(!hd)
return 0;
// h_alloc makes sure type isn't 0, so no need to check that here.
if(hd->type != type)
return 0;
return hd;
}
static void cleanup(void)
{
// close open handles
for(i32 i = 0; i < last_in_use; i++)
{
HDATA* hd = h_data(i);
if(hd)
{
// somewhat messy, but this only happens on cleanup.
// better, i think, than an additional h_free(i32 idx) version.
Handle h = handle(i, hd->tag);
h_free(h, hd->type);
}
}
// free HDATA array
for(uint j = 0; j < num_pages; j++)
{
free(pages[j]);
pages[j] = 0;
}
}
static int alloc_idx(i32* pidx, HDATA** phd)
{
assert(pidx && phd && "alloc_idx: invalid param");
*pidx = 0;
*phd = 0;
i32 idx;
HDATA* hd;
// we already know the first free entry
if(first_free != -1)
{
idx = first_free;
hd = h_data(idx);
}
// need to look for a free entry, or alloc another
else
{
// look for an unused entry
for(idx = 0; idx <= last_in_use; idx++)
{
hd = h_data(idx);
assert(hd); // can't fail - idx is valid
// found one - done
if(!hd->tag)
goto have_idx;
}
// add another
if(last_in_use >= hdata_cap)
{
assert(!"alloc_idx: too many open handles (increase IDX_BITS)");
return -1;
}
idx = last_in_use+1; // incrementing idx would start it at 1
hd = h_data(idx);
if(!hd)
return ERR_NO_MEM;
// can't fail for any other reason - idx is checked above.
{ // VC6 goto fix
bool is_unused = !hd->tag;
assert(is_unused && "alloc_idx: invalid last_in_use");
}
have_idx:;
}
// check if next entry is free
HDATA* hd2 = h_data(idx+1);
if(hd2 && hd2->tag == 0)
first_free = idx+1;
else
first_free = -1;
if(idx > last_in_use)
last_in_use = idx;
*pidx = idx;
*phd = hd;
return 0;
}
static int free_idx(i32 idx)
{
if(first_free == -1 || idx < first_free)
first_free = idx;
return 0;
}
int h_free(Handle& h, H_Type type)
{
HDATA* hd = h_data(h, type);
if(!hd)
return ERR_INVALID_HANDLE;
// not the last reference
if(--hd->refs)
return 0;
// TODO: keep this handle open (cache)
// h_alloc makes sure type != 0; if we get here, it still is
H_VTbl* vtbl = hd->type;
// call its destructor
// note: H_TYPE_DEFINE currently always defines a dtor, but play it safe
if(vtbl->dtor)
vtbl->dtor(hd->user);
memset(hd, 0, sizeof(HDATA));
i32 idx = h_idx(h);
free_idx(idx);
return 0;
}
// any further params are passed to type's init routine
Handle h_alloc(H_Type type, const char* fn, uint flags, ...)
{
// ONCE(atexit(cleanup))
i32 idx;
HDATA* hd;
// verify type
if(!type)
{
assert(0 && "h_alloc: type param is 0");
return 0;
}
if(type->user_size > HDATA_USER_SIZE)
{
assert(0 && "h_alloc: type's user data is too large for HDATA");
return 0;
}
if(type->name == 0)
{
assert(0 && "h_alloc: type's name field is 0");
return 0;
}
uintptr_t key = 0;
// not backed by file; fn is the key
if(flags & RES_KEY)
{
key = (uintptr_t)fn;
fn = 0;
}
else
{
if(fn)
key = fnv_hash(fn, strlen(fn));
}
if(key)
{
/*
// object already loaded?
Handle h = h_find(type, key);
if(h)
{
hd = h_data(h, type);
if(hd->refs == REF_MAX)
{
assert(0 && "h_alloc: too many references to a handle - increase REF_BITS");
return 0;
}
hd->refs++;
return h;
}
*/
}
if(alloc_idx(&idx, &hd) < 0)
return 0;
static u32 tag;
if(++tag >= TAG_MASK)
{
assert(!"h_alloc: tag overflow - may not notice stale handle reuse (increase TAG_BITS)");
tag = 1;
}
hd->key = key;
hd->tag = tag;
hd->type = type;
Handle h = handle(idx, tag);
H_VTbl* vtbl = type;
va_list args;
va_start(args, flags);
if(vtbl->init)
vtbl->init(hd->user, args);
va_end(args);
if(vtbl->reload)
{
int err = vtbl->reload(hd->user, fn);
if(err < 0)
{
h_free(h, type);
return 0;
}
}
return h;
}
void* h_user_data(const Handle h, const H_Type type)
{
HDATA* hd = h_data(h, type);
return hd? hd->user : 0;
}
const char* h_filename(const Handle h)
{
HDATA* hd = h_data(h);
return hd? hd->fn : 0;
}
/*
// some resource types are heavyweight and reused between files (e.g. VRead).
// this reassigns dst's (of type <type>) associated file to that of src.
int h_reassign(const Handle dst, const H_Type dst_type, const Handle src)
{
HDATA* hd_dst = h_data(dst, dst_type);
HDATA* hd_src = h_data(src);
if(!hd_dst || !hd_src)
return -1;
hd_dst->fn = hd_src->fn;
hd_dst->key = hd_src->key;
return 0;
}
*/
int h_reload(const char* fn)
{
if(!fn)
{
assert(0 && "h_reload: fn = 0");
return ERR_INVALID_PARAM;
}
const u32 key = fnv_hash(fn, strlen(fn));
i32 i;
// destroy (note: not free!) all handles backed by this file.
// do this before reloading any of them, because we don't specify reload
// order (the parent resource may be reloaded first, and load the child,
// whose original data would leak).
for(i = 0; i <= last_in_use; i++)
{
HDATA* hd = h_data(i);
if(hd && hd->key == key)
hd->type->dtor(hd->user);
}
int ret = 0;
// now reload all affected handles
// TODO: if too slow
for(i = 0; i <= last_in_use; i++)
{
HDATA* hd = h_data(i);
if(!hd)
continue;
int err = hd->type->reload(hd->user, hd->fn);
// don't stop if an error is encountered - try to reload them all.
if(err < 0)
ret = err;
}
return ret;
}
int res_cur_scope;

188
source/lib/res/h_mgr.h Executable file
View File

@ -0,0 +1,188 @@
// handle based caching resource manager
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __RES_H__
#define __RES_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h> // type init routines get va_list of args
#include "../types.h"
// handle type (for 'type safety' - can't use a texture handle as a sound)
//
// rationale: we could use the destructor passed to h_alloc to identify
// the handle, but it's good to have a list of all types, and we avoid having
// to create empty destructors for handle types that wouldn't need them.
// finally, we save memory - this fits in a few bits, vs. needing a pointer.
// registering extension for each module is bad - some may use many
// (e.g. texture - many formats).
// handle manager shouldn't know about handle types
/*
enum H_Type
{
H_Mem = 1,
H_ZArchive = 2,
H_ZFile = 3,
H_VFile = 4,
H_VRead = 5,
H_Tex = 6,
H_Font = 7,
H_Sound = 8,
NUM_HANDLE_TYPES
};
*/
/*
///xxx advantage of manual vtbl:
no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot
vtbl contains sizeof resource data, and name!
but- has to handle variable params, a bit ugly
*/
// 'manual vtbl' type id
// handles have a type, to prevent using e.g. texture handles as a sound.
//
// alternatives:
// - enum of all handle types (smaller, have to pass all methods to h_alloc)
// - class (difficult to compare type, handle manager needs to know of all users)
//
// checked in h_alloc:
// - user_size must fit in what res.cpp is offering (currently 44 bytes)
// - name must not be 0
//
// init: user data is initially zeroed
// dtor: user data is zeroed automatically afterwards
// reload: if this resource type is opened by another resource's reload,
// our reload routine MUST check if already opened! This is relevant when
// a file is invalidated: if e.g. a sound object opens a file, the handle
// manager calls the reload routines for the 2 handles in unspecified order.
// ensuring the order would require a tag field that can't overflow -
// not really guaranteed with 32-bit handles. it'd also be more work
// to sort the handles by creation time, or account for several layers of
// dependencies.
struct H_VTbl
{
void(*init)(void* user, va_list);
int(*reload)(void* user, const char* fn);
void(*dtor)(void* user);
size_t user_size;
const char* name;
};
typedef H_VTbl* H_Type;
#define H_TYPE_DEFINE(t)\
static void t##_init(t*, va_list);\
static int t##_reload(t*, const char*);\
static void t##_dtor(t*);\
static H_VTbl V_##t = {\
(void(*)(void*, va_list))t##_init,\
(int(*)(void*, const char*))t##_reload,\
(void(*)(void*))t##_dtor,\
sizeof(t),\
#t\
};\
static H_Type H_##t = &V_##t;
// <type>* <var> = H_USER_DATA(<h_var>, <type>)
#define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type);
#define H_DEREF(h, type, var)\
type* const var = (type*)h_user_data(h, H_##type);\
if(!var)\
return ERR_INVALID_HANDLE;
// 0 = invalid handle value; < 0 is an error code.
// 64 bits, because we want tags to remain unique: tag overflow may
// let handle use errors slip through, or worse, cause spurious errors.
// with 32 bits, we'd need >= 12 for the index, leaving < 512K tags -
// not a lot.
typedef i64 Handle;
// all functions check the passed tag (part of the handle) and type against
// the internal values. if they differ, an error is returned.
// resource scope
// used together with flags (e.g. in mem), so no separate type
enum
{
RES_TEMP = 1,
RES_LEVEL = 2,
RES_STATIC = 3
};
#define RES_SCOPE_MASK 3
// h_alloc flags
enum
{
// the resource isn't backed by a file. the fn parameter is treated as the search key (uintptr_t)
// currently only used by mem manager
RES_KEY = 4
};
// allocate a new handle.
// if key is 0, or a (key, type) handle doesn't exist,
// the first free entry is used.
// otherwise, a handle to the existing object is returned,
// and HDATA.size != 0.
//// user_size is checked to make sure the user data fits in the handle data space.
// dtor is associated with type and called when the object is freed.
// handle data is initialized to 0; optionally, a pointer to it is returned.
extern Handle h_alloc(H_Type type, const char* fn, uint flags = 0, ...);
extern int h_free(Handle& h, H_Type type);
/*
// find and return a handle by key (typically filename hash)
// currently O(n).
extern Handle h_find(H_Type type, uintptr_t key);
*/
// return a pointer to handle data
extern void* h_user_data(Handle h, H_Type type);
extern const char* h_filename(Handle h);
// some resource types are heavyweight and reused between files (e.g. VRead).
// this reassigns dst's (of type <type>) associated file to that of src.
int h_reassign(const Handle dst, const H_Type dst_type, const Handle src);
extern int res_cur_scope;
#ifdef __cplusplus
}
#endif
#endif // #ifndef __RES_H__

290
source/lib/res/mem.cpp Executable file
View File

@ -0,0 +1,290 @@
// malloc layer for less fragmentation, alignment, and automatic release
#include <cstdlib>
#include <cassert>
#include "lib.h"
#include "types.h"
#include "mem.h"
#include "h_mgr.h"
#include "misc.h"
#include <map>
//////////////////////////////////////////////////////////////////////////////
static void heap_free(void* const p, const size_t size, const uintptr_t ctx)
{
void* org_p = (void*)ctx;
free(org_p);
}
static void* heap_alloc(const size_t size, const int align, uintptr_t& ctx, MEM_DTOR& dtor)
{
u8* org_p = (u8*)malloc(size+align-1);
u8* p = (u8*)round_up((uintptr_t)org_p, align);
ctx = (uintptr_t)org_p;
dtor = heap_free;
return p;
}
//////////////////////////////////////////////////////////////////////////////
static u8* pool;
static size_t pool_pos;
static const size_t POOL_CAP = 8*MB; // TODO: user editable
static void pool_free(void* const p, const size_t size, const uintptr_t ctx)
{
size_t ofs = ctx;
// at end of pool? if so, 'free' it
if(ofs + size == pool_pos)
pool_pos -= size;
else
; // TODO: warn about 'leaked' memory;
// suggest using a different allocator
}
static void* pool_alloc(const size_t size, const uint align, uintptr_t& ctx, MEM_DTOR& dtor)
{
if(!pool)
{
pool = (u8*)mem_alloc(size, align, 0);
if(!pool)
return 0;
}
ptrdiff_t ofs = round_up(pool_pos, align);
ctx = (uintptr_t)ofs;
dtor = pool_free;
if(ofs+size > POOL_CAP)
{
assert(0 && "pool_alloc: not enough memory in pool");
return 0;
}
pool_pos = ofs+size;
return (u8*)pool + ofs;
}
//////////////////////////////////////////////////////////////////////////////
typedef std::map<void*, Handle> PtrToH;
// undefined NLSO init order fix
static PtrToH& _ptr_to_h()
{
static PtrToH ptr_to_h_;
return ptr_to_h_;
}
#define ptr_to_h _ptr_to_h()
// not needed by other modules - mem_get_size and mem_assign is enough.
static Handle find_alloc(void* p)
{
PtrToH::const_iterator it = ptr_to_h.find(p);
if(it == ptr_to_h.end())
return 0;
return it->second;
}
// returns the handle that will be removed, for mem_free convenience.
// makes sure p is in the mapping.
static Handle remove_alloc(void* p)
{
PtrToH::iterator it = ptr_to_h.find(p);
if(it == ptr_to_h.end())
{
assert("remove_alloc: pointer not in map");
return 0;
}
Handle hm = it->second;
ptr_to_h.erase(it);
return hm;
}
// p must not alread be in mapping!
static void set_alloc(void* p, Handle hm)
{
ptr_to_h[p] = hm;
}
struct Mem
{
void* p;
size_t size;
// allocator specific
uintptr_t ctx;
MEM_DTOR dtor; // this allows user-specified dtors.
// alternative: switch(mem->type) in mem_dtor
};
H_TYPE_DEFINE(Mem)
static void Mem_init(Mem* m, va_list args)
{
}
static void Mem_dtor(Mem* m)
{
if(m->dtor)
m->dtor(m->p, m->size, m->ctx);
}
// can't alloc here, because h_alloc needs the key when called
// (key == pointer we allocate)
static int Mem_reload(Mem* m, const char* fn)
{
return 0;
}
int mem_free_p(void*& p)
{
if(!p)
return ERR_INVALID_PARAM;
Handle hm = remove_alloc(p);
p = 0;
return h_free(hm, H_Mem);
}
int mem_free_h(Handle& hm)
{
void* p = mem_get_ptr(hm);
hm = 0;
return mem_free_p(p);
}
Handle mem_assign(void* p, size_t size, uint flags /* = 0 */, MEM_DTOR dtor /* = 0 */, uintptr_t ctx /* = 0 */)
{
// we've already allocated that pointer - returns its handle
Handle hm = find_alloc(p);
if(hm)
return hm;
if(!p || !size)
{
assert(0 && "mem_assign: invalid p or size");
return 0;
}
hm = h_alloc(H_Mem, (const char*)p, flags | RES_KEY);
if(!hm)
return 0;
set_alloc(p, hm);
H_DEREF(hm, Mem, m);
m->p = p;
m->size = size;
m->dtor = dtor;
m->ctx = ctx;
return hm;
}
void* mem_alloc(size_t size, const uint align, uint flags, Handle* phm)
{
if(phm)
*phm = 0;
if(size == 0)
{
assert(0 && "mem_alloc: why is size = 0?");
size = 1;
}
// no scope indicated
if(!flags)
// in a handle _reload function - default to its scope
if(res_cur_scope)
flags = res_cur_scope;
// otherwise, assume global scope
int scope = flags & RES_SCOPE_MASK;
// filled in by allocators
uintptr_t ctx;
MEM_DTOR dtor;
void* p = 0;
if(scope == RES_TEMP)
p = pool_alloc(size, align, ctx, dtor);
else
p = heap_alloc(size, align, ctx, dtor);
if(!p)
return 0;
Handle hm = mem_assign(p, size, scope, dtor, ctx);
if(!hm) // failed to allocate a handle
{
dtor(p, size, ctx);
return 0;
}
// check if pointer was already allocated?
// caller is asking for the handle
// (freeing the memory via handle is faster than mem_free, because
// we wouldn't have to scan all handles looking for the pointer)
if(phm)
*phm = hm;
if(flags & MEM_ZERO)
memset(p, 0, size);
return p;
}
void* mem_get_ptr(Handle hm, size_t* size)
{
Mem* m = H_USER_DATA(hm, Mem);
if(!m)
return 0;
assert((!m->p || m->size) && "mem_get_ptr: mem corrupted (p valid =/=> size > 0)");
if(size)
*size = m->size;
return m->p;
}
ssize_t mem_size(void* p)
{
Handle hm = find_alloc(p);
H_DEREF(hm, Mem, m);
return (ssize_t)m->size;
}

43
source/lib/res/mem.h Executable file
View File

@ -0,0 +1,43 @@
#ifndef MEM_H
#define MEM_H
#include "h_mgr.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void(*MEM_DTOR)(void* const p, const size_t size, const uintptr_t ctx);
// VC6 requires const here if the actual implementations define as such.
// mem_alloc flags
enum
{
// RES_*
MEM_ZERO = 0x1000
};
extern void* mem_alloc(size_t size, uint align = 1, uint flags = 0, Handle* ph = 0);
#define mem_free(p) mem_free_p((void*&)p)
extern int mem_free_p(void*& p);
// faster than mem_free(void*) - no scan of open handles for the pointer
extern int mem_free_h(Handle& hm);
// create a H_MEM handle of type MEM_USER,
// and assign it the specified memory range.
// dtor is called when the handle is freed, if non-NULL.
extern Handle mem_assign(void* p, size_t size, uint flags = 0, MEM_DTOR dtor = 0, uintptr_t ctx = 0);
// returns 0 if the handle is invalid
extern void* mem_get_ptr(Handle h, size_t* size = 0);
#ifdef __cplusplus
}
#endif
#endif // #ifndef MEM_H

0
source/lib/res/res.cpp Executable file
View File

5
source/lib/res/res.h Executable file
View File

@ -0,0 +1,5 @@
#include "res/h_mgr.h"
#include "res/vfs.h"
#include "res/tex.h"
#include "res/mem.h"
#include "res/font.h"

888
source/lib/res/tex.cpp Executable file
View File

@ -0,0 +1,888 @@
// OpenGL texturing
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
// supported formats: DDS, TGA, PNG, JP2, BMP, RAW
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include "lib.h"
#include "vfs.h"
#include "tex.h"
#include "mem.h"
#include "ogl.h"
#include "h_mgr.h"
#include "misc.h"
#define NO_JP2
//#define NO_PNG
#ifndef NO_JP2
#include <jasper/jasper.h>
#endif
#define _WINDOWS_
#define WINAPI __stdcall
#define WINAPIV __cdecl
#ifndef NO_PNG
#include <png.h>
#pragma comment(lib, "libpng.lib")
#endif
// filled by loader funcs => declare here
struct Tex
{
u32 w : 16;
u32 h : 16;
u32 fmt : 16;
u32 bpp : 16;
size_t ofs; // offset to image data in file
Handle hm; // H_MEM handle to loaded file
uint id;
};
H_TYPE_DEFINE(Tex)
const u32 FMT_UNKNOWN = 0;
//////////////////////////////////////////////////////////////////////////////
//
// DDS
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_DDS
// modified from ddraw header
#pragma pack(push, 1)
// DDPIXELFORMAT.dwFlags
#define DDPF_ALPHAPIXELS 0x00000001
typedef struct
{
u32 dwSize; // size of structure (32)
u32 dwFlags; // indicates which fields are valid
u32 dwFourCC; // (DDPF_FOURCC) FOURCC code, "DXTn"
u32 dwReserved1[5]; // reserved
}
DDPIXELFORMAT;
typedef struct
{
u32 dwCaps[4];
}
DDSCAPS2;
// DDSURFACEDESC2.dwFlags
#define DDSD_HEIGHT 0x00000002
#define DDSD_WIDTH 0x00000004
#define DDSD_PIXELFORMAT 0x00001000
#define DDSD_MIPMAPCOUNT 0x00020000
typedef struct
{
u32 dwSize; // size of structure (124)
u32 dwFlags; // indicates which fields are valid
u32 dwHeight; // height of main image (pixels)
u32 dwWidth; // width of main image (pixels)
u32 dwLinearSize; // (DDSD_LINEARSIZE): total image size
u32 dwDepth; // (DDSD_DEPTH) vol. textures: vol. depth
u32 dwMipMapCount; // (DDSD_MIPMAPCOUNT) total # levels
u32 dwReserved1[11]; // reserved
DDPIXELFORMAT ddpfPixelFormat; // pixel format description of the surface
DDSCAPS2 ddsCaps; // direct draw surface capabilities
u32 dwReserved2; // reserved
}
DDSURFACEDESC2;
#pragma pack(pop)
static inline bool dds_valid(const u8* ptr, size_t size)
{
UNUSED(size) // only need first 4 chars
return *(u32*)ptr == FOURCC('D','D','S',' ');
}
// TODO: DXT1a?
static int dds_load(const char* fn, const u8* p, size_t size, Tex* t)
{
const char* err = 0;
const DDSURFACEDESC2* surf = (const DDSURFACEDESC2*)(p+4);
const u32 hdr_size = 4+sizeof(DDSURFACEDESC2);
// make sure we can access all header fields
if(size < hdr_size)
err = "header not completely read";
else
{
const u32 sd_size = read_le32(&surf->dwSize);
const u32 sd_flags = read_le32(&surf->dwSize);
const u32 h = read_le32(&surf->dwHeight);
const u32 w = read_le32(&surf->dwWidth);
const u32 img_size = read_le32(&surf->dwLinearSize);
u32 mipmaps = read_le32(&surf->dwMipMapCount);
const u32 pf_size = read_le32(&surf->ddpfPixelFormat.dwSize);
const u32 pf_flags = read_le32(&surf->ddpfPixelFormat.dwFlags);
const u32 fourcc = surf->ddpfPixelFormat.dwFourCC;
// compared against FOURCC, which takes care of endian conversion.
// we'll use these fields; make sure they're present below.
// note: we can't guess image dimensions if not specified -
// the image isn't necessarily square.
const u32 sd_req_flags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
// make sure fields that aren't indicated as valid are zeroed.
if(!(sd_flags & DDSD_MIPMAPCOUNT))
mipmaps = 0;
// MS DXTex tool doesn't set the required dwPitchOrLinearSize field -
// they can't even write out their own file format correctly. *sigh*
// we need to pass to OpenGL; it's calculated from w, h, and bpp,
// which we determine from the pixel format.
u32 bpp = 0;
u32 fmt = FMT_UNKNOWN;
switch(fourcc)
{
case FOURCC('D','X','T','1'):
if(pf_flags & DDPF_ALPHAPIXELS)
fmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
else
fmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
bpp = 4;
break;
case FOURCC('D','X','T','3'):
fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
bpp = 8;
break;
case FOURCC('D','X','T','5'):
fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
bpp = 8;
break;
}
if(size < hdr_size + img_size)
err = "image not completely loaded";
if(w % 4 || h % 4)
err = "image dimensions not padded to S3TC block size";
if(!w || !h)
err = "width or height = 0 -- that's silly";
if(mipmaps > 0)
err = "contains mipmaps";
if(fmt == 0)
err = "invalid pixel format (not DXT{1,3,5})";
if((sd_flags & sd_req_flags) != sd_req_flags)
err = "missing one or more required fields (w, h, pixel format)";
if(sizeof(DDPIXELFORMAT) != pf_size)
err = "DDPIXELFORMAT size mismatch";
if(sizeof(DDSURFACEDESC2) != sd_size)
err = "DDSURFACEDESC2 size mismatch";
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = hdr_size;
}
if(err)
{
printf("dds_load: %s: %s\n", fn, err);
return -1;
}
return 0;
}
#endif
//////////////////////////////////////////////////////////////////////////////
//
// TGA
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_TGA
static inline bool tga_valid(const u8* ptr, size_t size)
{
UNUSED(size)
// no color map; uncompressed grayscale or true color
return (ptr[1] == 0 && (ptr[2] == 2 || ptr[2] == 3));
}
// requirements: uncompressed, direct color, bottom up
static int tga_load(const char* fn, const u8* ptr, size_t size, Tex* t)
{
const char* err = 0;
const u8 img_id_len = ptr[0];
const uint hdr_size = 18+img_id_len;
if(size < hdr_size)
err = "header not completely read";
else
{
const u8 type = ptr[2];
const u16 w = read_le16(ptr+12);
const u16 h = read_le16(ptr+14);
const u8 bpp = ptr[16];
const u8 desc = ptr[17];
const u8 alpha_bits = desc & 0x0f;
const ulong img_size = (ulong)w * h * bpp / 8;
const u32 ofs = hdr_size;
// determine format
u32 fmt = ~0;
// .. grayscale
if(type == 3)
{
// 8 bit format: several are possible, we can't decide
if(bpp == 8)
fmt = FMT_UNKNOWN;
else if(bpp == 16 && alpha_bits == 8)
fmt = GL_LUMINANCE_ALPHA;
}
// .. true color
else if(type == 2)
{
if(bpp == 24 && alpha_bits == 0)
fmt = GL_BGR;
else if(bpp == 32 && alpha_bits == 8)
fmt = GL_BGRA;
}
if(fmt == ~0)
err = "invalid format or bpp";
if(desc & 0x18)
err = "image is not bottom-up and left-to-right";
if(size < hdr_size + img_size)
err = "size < image size";
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = ofs;
}
if(err)
{
printf("tga_load: %s: %s\n", fn, err);
return -1;
}
return 0;
}
#endif
//////////////////////////////////////////////////////////////////////////////
//
// BMP
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_BMP
#pragma pack(push, 1)
struct BITMAPFILEHEADER
{
u16 bfType; // "BM"
u32 bfSize; // of file
u32 reserved;
u32 bfOffBits; // offset to image data
};
// BITMAPCOREHEADER + compression field
struct BITMAPCOREHEADER2
{
u32 biSize;
long biWidth;
long biHeight;
u16 biPlanes; // = 1
u16 biBitCount; // bpp
u32 biCompression;
};
#pragma pack(pop)
#define BI_RGB 0 // bch->biCompression
static inline bool bmp_valid(const u8* ptr, size_t size)
{
UNUSED(size)
// bfType == BM? (check single bytes => endian safe)
return ptr[0] == 'B' && ptr[1] == 'M';
}
// requirements: uncompressed, direct color, bottom up
static int bmp_load(const char* fn, const u8* ptr, size_t size, Tex* t)
{
const char* err = 0;
BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER*)ptr;
BITMAPCOREHEADER2* bch = (BITMAPCOREHEADER2*)(ptr+sizeof(BITMAPFILEHEADER));
const int hdr_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPCOREHEADER2);
if(size < hdr_size)
err = "header not completely read";
else
{
const long w = read_le32(&bch->biWidth);
const long h = read_le32(&bch->biHeight);
const u16 bpp = read_le16(&bch->biBitCount);
const u32 compress = read_le32(&bch->biCompression);
const u32 ofs = read_le32(&bfh->bfOffBits);
const u32 img_size = w * h * bpp/8;
const u32 fmt = (bpp == 24)? GL_BGR : GL_BGRA;
if(h < 0)
err = "top-down";
if(compress != BI_RGB)
err = "compressed";
if(bpp < 24)
err = "not direct color";
if(size < ofs+img_size)
err = "image not completely read";
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = ofs;
}
if(err)
{
printf("bmp_load: %s: %s\n", fn, err);
return -1;
}
return 0;
}
// TODO: no extra buffer needed here; dealloc?
#endif
//////////////////////////////////////////////////////////////////////////////
//
// RAW
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_RAW
static inline bool raw_valid(const u8* p, size_t size)
{
UNUSED(p)
UNUSED(size)
return true;
}
static int raw_load(const char* fn, const u8* ptr, size_t size, Tex* t)
{
static u32 fmts[5] = { 0, 0, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA };
for(uint i = 1; i <= 4; i++)
{
u32 dim = (u32)sqrtf((float)size/i);
// TODO: differentiate 8/32 bpp
if(dim*dim*i != size)
continue;
const u32 w = dim;
const u32 h = dim;
const u32 fmt = fmts[i];
const u32 bpp = i*8;
const u32 ofs = 0;
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = ofs;
return 0;
}
printf("raw_load: %s: %s\n", fn, "no matching format found");
return -1;
}
#endif
//////////////////////////////////////////////////////////////////////////////
//
// PNG
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_PNG
struct MemRange
{
const u8* p;
size_t size;
};
static void png_read_fn(png_struct* png_ptr, u8* data, png_size_t length)
{
MemRange* const mr = (MemRange*)png_ptr->io_ptr;
if(mr->size < length)
png_error(png_ptr, "png_read_fn: not enough data to satisfy request!");
memcpy(data, mr->p, length);
mr->p += length;
mr->size -= length; // > 0 due to test above
}
static inline bool png_valid(const u8* ptr, size_t size)
{
return png_sig_cmp((u8*)ptr, 0, MIN(size, 8)) == 0;
}
// requirement: direct color
static int png_load(const char* fn, const u8* ptr, size_t size, Tex* t)
{
const char* err = 0;
// allocate PNG structures
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if(!png_ptr)
return ERR_NO_MEM;
png_infop info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr)
{
png_destroy_read_struct(&png_ptr, 0, 0);
return ERR_NO_MEM;
}
// setup error handling
if(setjmp(png_jmpbuf(png_ptr)))
{
fail:
printf("png_load: %s: %s\n", fn, err? err : "");
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
return -1;
}
MemRange mr = { ptr, size };
png_set_read_fn(png_ptr, &mr, png_read_fn);
png_read_info(png_ptr, info_ptr);
unsigned long w, h;
int prec, color_type;
png_get_IHDR(png_ptr, info_ptr, &w, &h, &prec, &color_type, 0, 0, 0);
size_t pitch = png_get_rowbytes(png_ptr, info_ptr);
const u32 fmts[8] = { 0, ~0, GL_RGB, ~0, GL_LUMINANCE_ALPHA, ~0, GL_RGBA, ~0 };
const u32 fmt = color_type < 8? fmts[color_type] : ~0;
const u32 bpp = (u32)(pitch / w * 8);
const u32 ofs = 0; // libpng returns decoded image data; no header
if(prec != 8)
err = "channel precision != 8 bits";
if(fmt == ~0)
err = "color type is invalid (must be direct color)";
if(err)
goto fail;
// allocate mem for image - rows point into buffer (sequential)
// .. (rows is freed in png_destroy_read_struct)
u8** rows = (u8**)png_malloc(png_ptr, (h+1)*sizeof(void*));
if(!rows)
goto fail;
size_t img_size = pitch * (h+1);
Handle img_hm;
u8* img = (u8*)mem_alloc(img_size, 64*KB, 0, &img_hm);
if(!img)
goto fail;
u8* pos = img;
for(u32 i = 0; i < h+1; i++)
{
rows[i] = pos;
pos += pitch;
}
png_read_image(png_ptr, rows);
png_read_end(png_ptr, 0);
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
mem_free_h(t->hm);
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = ofs;
t->hm = img_hm;
return 0;
}
#endif
//////////////////////////////////////////////////////////////////////////////
//
// JP2
//
//////////////////////////////////////////////////////////////////////////////
#ifndef NO_JP2
static inline bool jp2_valid(const u8* p, size_t size)
{
static bool initialized;
if(!initialized)
{
jas_init();
initialized = true;
}
jas_stream_t* stream = jas_stream_memopen((char*)ptr, size);
return jp2_validate(stream) >= 0;
}
static int jp2_load(const char* fn, const u8* ptr, size_t size, Tex* t)
{
const char* err = 0;
jas_stream_t* stream = jas_stream_memopen((char*)ptr, size);
jas_image_t* image = jas_image_decode(stream, -1, 0);
if(!image)
return -1;
const int num_cmpts = jas_image_numcmpts(image);
jas_matrix_t* matr[4] = {0};
jas_seqent_t* rows[4] = {0};
const u32 w = jas_image_cmptwidth (image, 0);
const u32 h = jas_image_cmptheight(image, 0);
const int prec = jas_image_cmptprec (image, 0);
const u32 fmt = GL_RGB;
const u32 bpp = num_cmpts * 8;
const u32 ofs = 0; // jasper returns decoded image data; no header
if(depth != 8)
{
err = "channel precision != 8";
fail:
printf("jp2_load: %s: %s\n", fn, err);
// TODO: destroy image
return -1;
}
size_t img_size = w * h * num_cmpts;
Handle img_hm;
u8* img = (u8*)mem_alloc(img_size, 64*KB, 0, &img_hm);
u8* out = img;
int cmpt;
for(cmpt = 0; cmpt < num_cmpts; cmpt++)
matr[cmpt] = jas_matrix_create(1, w);
for(int y = 0; y < h; y++)
{
for(cmpt = 0; cmpt < num_cmpts; cmpt++)
{
jas_image_readcmpt(image, cmpt, 0, y, w, 1, matr[cmpt]);
rows[cmpt] = jas_matrix_getref(matr[cmpt], 0, 0);
}
for(int x = 0; x < w; x++)
for(cmpt = 0; cmpt < num_cmpts; cmpt++)
*out++ = *rows[cmpt]++;
}
for(cmpt = 0; cmpt < num_cmpts; cmpt++)
jas_matrix_destroy(matr[cmpt]);
mem_free_h(t->hm);
t->w = w;
t->h = h;
t->fmt = fmt;
t->bpp = bpp;
t->ofs = ofs;
t->hm = img_hm;
return 0;
}
#endif
static void Tex_init(Tex* t, va_list args)
{
}
static void Tex_dtor(Tex* t)
{
mem_free_h(t->hm);
glDeleteTextures(1, &t->id);
}
// TEX output param is invalid if function fails
static int Tex_reload(Tex* t, const char* fn)
{
// load file
void* _p = 0;
size_t size;
Handle hm = vfs_load(fn, _p, size);
if(hm <= 0)
return (int)hm;
// guarantee *_valid routines 4 header bytes
if(size < 4)
{
mem_free_h(hm);
return -1;
}
t->hm = hm;
int err = -1;
// more convenient to pass loaders u8 - less casting
const u8* p = (const u8*)_p;
#ifndef NO_DDS
if(dds_valid(p, size))
err = dds_load(fn, p, size, t); else
#endif
#ifndef NO_PNG
if(png_valid(p, size))
err = png_load(fn, p, size, t); else
#endif
#ifndef NO_JP2
if(jp2_valid(p, size))
err = jp2_load(fn, p, size, t); else
#endif
#ifndef NO_BMP
if(bmp_valid(p, size))
err = bmp_load(fn, p, size, t); else
#endif
#ifndef NO_TGA
if(tga_valid(p, size))
err = tga_load(fn, p, size, t); else
#endif
#ifndef NO_RAW
if(raw_valid(p, size))
err = raw_load(fn, p, size, t); else
#endif
; // make sure else chain is ended
if(err < 0)
{
mem_free_h(hm);
return err;
}
// loaders weren't able to determine type
if(t->fmt == FMT_UNKNOWN)
{
assert(t->bpp == 8);
t->fmt = GL_ALPHA;
// TODO: check file name, go to 32 bit if wrong
}
uint id;
glGenTextures(1, &id);
t->id = id;
// this can't realistically fail, just note that the already_loaded
// check above assumes (id > 0) <==> texture is loaded and valid
return 0;
}
inline Handle tex_load(const char* const fn, int scope)
{
return h_alloc(H_Tex, fn, scope);
}
int tex_bind(const Handle h)
{
Tex* t = H_USER_DATA(h, Tex);
if(!t)
{
glBindTexture(GL_TEXTURE_2D, 0);
return ERR_INVALID_HANDLE;
}
else
{
glBindTexture(GL_TEXTURE_2D, t->id);
return 0;
}
}
int tex_filter = GL_LINEAR;
uint tex_bpp = 32; // 16 or 32
int tex_upload(const Handle ht, int filter, int int_fmt)
{
H_DEREF(ht, Tex, t);
// greater than max supported tex dimension?
// no-op if oglInit not yet called
if(t->w > (uint)max_tex_size || t->h > (uint)max_tex_size)
{
assert(!"tex_upload: image dimensions exceed OpenGL implementation limit");
return 0;
}
// both NV_texture_rectangle and subtexture require work for the client
// (changing tex coords) => we'll just disallow non-power of 2 textures.
// TODO: ARB_texture_non_power_of_two
if(!is_pow2(t->w) || !is_pow2(t->h))
{
assert(!"tex_upload: image is not power-of-2");
return 0;
}
tex_bind(ht);
// get pointer to image data
size_t size;
void* p = mem_get_ptr(t->hm, &size);
if(!p)
{
assert(0 && "tex_upload: mem object is a NULL pointer");
return 0;
}
const u8* img = (const u8*)p + t->ofs;
// set filter
if(!filter)
filter = tex_filter;
const int mag = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR;
const bool mipmap = (filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_LINEAR_MIPMAP_NEAREST ||
filter == GL_NEAREST_MIPMAP_LINEAR || filter == GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
const bool has_alpha = t->fmt == GL_RGBA || t->fmt == GL_BGRA || t->fmt == GL_LUMINANCE_ALPHA || t->fmt == GL_ALPHA;
// S3TC compressed
if(t->fmt >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT &&
t->fmt <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
{
const int img_size = t->w * t->h * t->bpp / 8;
assert(4+sizeof(DDSURFACEDESC2)+img_size == size && "tex_upload: dds file size mismatch");
glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, t->fmt, t->w, t->h, 0, img_size, img);
}
// normal
else
{
// calc internal fmt from format and global bpp, if not passed as a param
if(!int_fmt)
{
if(t->bpp == 32)
int_fmt = (tex_bpp == 32)? GL_RGBA8 : GL_RGBA4;
else if(t->bpp == 24)
int_fmt = (tex_bpp == 32)? GL_RGB8 : GL_RGB5;
else if(t->bpp == 16)
int_fmt = (tex_bpp == 32)? GL_LUMINANCE8_ALPHA8 : GL_LUMINANCE4_ALPHA4;
else if(t->fmt == GL_ALPHA)
int_fmt = (tex_bpp == 32)? GL_ALPHA8 : GL_ALPHA4;
else if(t->fmt == GL_LUMINANCE)
int_fmt = (tex_bpp == 32)? GL_LUMINANCE8 : GL_LUMINANCE4;
else
return -1;
}
// check if SGIS_generate_mipmap is available (once)
static int sgm_avl = -1;
if(sgm_avl == -1)
sgm_avl = oglExtAvail("GL_SGIS_generate_mipmap");
// manual mipmap gen via GLU (box filter)
if(mipmap && !sgm_avl)
gluBuild2DMipmaps(GL_TEXTURE_2D, int_fmt, t->w, t->h, t->fmt, GL_UNSIGNED_BYTE, img);
// auto mipmap gen, or no mipmap
else
{
if(mipmap)
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, t->w, t->h, 0, t->fmt, GL_UNSIGNED_BYTE, img);
}
}
mem_free_h(t->hm);
return 0;
}
int tex_free(Handle& ht)
{
return h_free(ht, H_Tex);
}
int tex_info(Handle ht, int* w, int* h, void** p)
{
H_DEREF(ht, Tex, t);
if(w)
*w = t->w;
if(h)
*h = t->h;
if(p)
*p = mem_get_ptr(t->hm);
return 0;
}

43
source/lib/res/tex.h Executable file
View File

@ -0,0 +1,43 @@
// OpenGL texturing
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __TEX_H__
#define __TEX_H__
#include "types.h"
#include "h_mgr.h"
#include "misc.h"
// load and return a handle to the texture given in <fn>.
// supports RAW, BMP, JP2, PNG, TGA, DDS
extern Handle tex_load(const char* fn, int scope = 0);
extern int tex_bind(Handle ht);
extern int tex_info(Handle ht, int* w, int* h, void** p);
extern int tex_filter; // GL values; default: GL_LINEAR
extern uint tex_bpp; // 16 or 32; default: 32
// upload the specified texture to OpenGL. Texture filter and internal format
// may be specified to override the global defaults.
extern int tex_upload(Handle ht, int filter_override = 0, int internal_fmt_override = 0);
extern int tex_free(Handle& ht);
#endif // __TEX_H__

711
source/lib/res/vfs.cpp Executable file
View File

@ -0,0 +1,711 @@
// virtual file system - transparent access to files in archives;
// allows multiple search paths
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include "lib.h"
#include "file.h"
#include "zip.h"
#include "misc.h"
#include "vfs.h"
#include "mem.h"
#include <string>
#include <vector>
#include <stack>
#include <algorithm>
// currently not thread safe, but that will most likely change
// (if prefetch thread is to be used).
// not safe to call before main!
// rationale for no forcibly-close support:
// issue:
// we might want to edit files while the game has them open.
// usual case: edit file, notify engine that it should be reloaded.
// here: need to tell the engine to stop what it's doing and close the file;
// only then can the artist write to the file, and trigger a reload.
//
// work involved:
// since closing a file with pending aios results in undefined
// behavior on Win32, we would have to keep track of all aios from each file,
// and cancel them. we'd also need to notify the higher level resource user
// that its read was cancelled, as opposed to failing due to read errors
// (which might cause the game to terminate).
//
// this is just more work than benefit. cases where the game holds on to files
// are rare:
// - streaming music (artist can use regular commands to stop the current
// track, or all music)
// - if the engine happens to be reading that file at the moment (expected
// to happen only during loading, and these are usually one-shot anway,
// i.e. it'll be done soon)
// - bug (someone didn't close a file - tough luck, and should be fixed
// instead of hacking around it).
// - archives (these remain open. allowing reload would mean we'd have to keep
// track of all files from an archive, and reload them all. another hassle.
// anyway, if files are to be changed in-game, then change the plain-file
// version, that's what they're for).
// rationale for n-archives per PATH entry:
// We need to be able to unmount specific paths (e.g. when switching mods).
// Don't want to remount everything (slow), or specify a mod tag when mounting
// (not this module's job). Instead, we include all archives in one path entry;
// the game keeps track of what path(s) it mounted for a mod,
// and unmounts those when needed.
struct PATH
{
struct PATH* next;
// "" if root, otherwise path from root, including DIR_SEP.
// points to space at end of this struct.
char* dir;
size_t num_archives;
Handle archives[1];
// space allocated here for archive Handles + dir string
};
static PATH* path_list;
int vfs_set_root(const char* argv0, const char* root)
{
if(access(argv0, X_OK) < 0)
return errno;
char path[PATH_MAX+1];
path[PATH_MAX] = 0;
if(!realpath(argv0, path))
return errno;
// remove executable name
char* fn = strrchr(path, DIR_SEP);
if(!fn)
return -1;
*fn = 0;
chdir(path);
chdir(root);
return vfs_mount(".");
}
int vfs_mount(const char* path)
{
size_t i;
const size_t path_len = strlen(path);
if(path_len > VFS_MAX_PATH)
{
assert(!"vfs_mount_dir: path name is longer than VFS_MAX_PATH");
return -1;
}
// security check: path must not contain "..",
// so that it can't access the FS above the VFS root.
if(strstr(path, ".."))
{
assert(0 && "vfs_mount: .. in path string");
return -1;
}
// enumerate all archives in <path>
std::vector<std::string> archives;
DIR* dir = opendir(path);
struct dirent* ent;
while((ent = readdir(dir)))
{
const char* fn = ent->d_name;
struct stat s;
if(stat(fn, &s) < 0)
continue;
// regular file
if(s.st_mode & S_IFREG)
{
char* ext = strrchr(fn, '.');
// it's a Zip file - add to list
if(ext && !strcmp(ext, ".zip"))
archives.push_back(fn);
}
}
closedir(dir);
// number of Zip files we'll try to open;
// final # archives may be less, if opening fails on some of them
// note: this many are allocated for the PATH entry,
// but only successfully opened archives are added
const size_t num_zip_files = archives.size();
// alloc search path entry (add to front)
const size_t archives_size = num_zip_files*sizeof(Handle);
const size_t dir_size = path_len+1+1; // DIR_SEP and '\0' appended
const size_t tot_size = sizeof(PATH)+archives_size+dir_size;
const size_t entry_size = round_up((long)(tot_size), 32);
PATH* entry = (PATH*)mem_alloc(entry_size, 32);
if(!entry)
return ERR_NO_MEM;
entry->next = path_list;
path_list = entry;
// copy over path string, and convert '/' to platform specific DIR_SEP.
entry->dir = (char*)&entry->archives[0] + archives_size;
char* d = entry->dir;
for(i = 0; i < path_len; i++)
{
int c = path[i];
if(c == '/' || c == '\\' || c == ':')
{
assert(c == '/' && "vfs_mount: path string contains platform specific dir separator; use '/'");
c = DIR_SEP;
}
*d++ = c;
}
// add trailing DIR_SEP, so we can just append filename when opening.
// exception: if mounting the current directory "." (i.e. the VFS root),
// make it "" - guaranteed to be portable and possibly faster.
if(!strcmp(path, "."))
entry->dir[0] = '\0';
else
{
d[0] = DIR_SEP;
d[1] = '\0';
}
// add archives in alphabetical order
std::sort(archives.begin(), archives.end());
Handle* p = entry->archives;
for(i = 0; i < num_zip_files; i++)
{
const Handle h = zip_archive_open(archives[i].c_str());
if(h > 0)
*p++ = h;
}
entry->num_archives = p - entry->archives; // actually valid archives
return 0;
}
int vfs_umount(const char* path)
{
PATH** prev = &path_list;
PATH* entry = path_list;
while(entry)
{
// found
if(!strcmp(entry->dir, path))
{
// close all archives
for(size_t i = 0; i < entry->num_archives; i++)
zip_archive_close(entry->archives[i]);
// remove from list
*prev = entry->next;
mem_free(entry);
return 0;
}
prev = &entry->next;
entry = entry->next;
}
// not found
return -1;
}
typedef int (*VFS_PATH_CB)(const char* full_rel_path, Handle ha, uintptr_t ctx);
// call cb, passing the ctx argument, for each mounted path or archive.
// if it returns an error (< 0) other than ERR_FILE_NOT_FOUND or succeeds
// (returns 0), return that value; otherwise, continue calling.
// if it never succeeded, fail with ERR_FILE_NOT_FOUND.
// rationale: we want to fail with the correct error value if something
// actually goes wrong (e.g. file locked). callbacks can abort the sequence
// by returning some error value.
static int vfs_foreach_path(VFS_PATH_CB cb, const char* fn, uintptr_t ctx)
{
char full_rel_path[PATH_MAX+1]; full_rel_path[PATH_MAX] = 0;
int err;
for(PATH* entry = path_list; entry; entry = entry->next)
{
// dir (already includes DIR_SEP)
snprintf(full_rel_path, PATH_MAX, "%s%s", entry->dir, fn);
err = cb(full_rel_path, 0, ctx);
if(err <= 0 && err != ERR_FILE_NOT_FOUND)
return err;
// archive
for(size_t i = 0; i < entry->num_archives; i++)
{
err = cb(fn, entry->archives[i], ctx);
if(err <= 0 && err != ERR_FILE_NOT_FOUND)
return err;
}
}
// if we get here, the function always failed with ERR_FILE_NOT_FOUND or
// requested the next path.
return ERR_FILE_NOT_FOUND;
}
static int realpath_cb(const char* path, Handle ha, uintptr_t ctx)
{
char* full_path = (char*)ctx;
struct stat s;
int err;
if(!path && !ha)
{
assert(0 && "realpath_cb: called with invalid path and archive handle");
return 1;
}
if(ha)
{
err = zip_stat(ha, path, &s);
if(!err)
{
const char* fn = h_filename(ha);
if(!fn)
{
assert(0 && "realpath_cb: h_filename 0, despite successful zip_stat");
return 1;
}
strncpy(full_path, fn, PATH_MAX);
return 0;
}
}
else
{
err = stat(path, &s);
if(!err)
{
strncpy(full_path, path, PATH_MAX);
return 0;
}
}
// failed *stat above - return error code.
return err;
}
int vfs_realpath(const char* fn, char* full_path)
{
return vfs_foreach_path(realpath_cb, fn, (uintptr_t)full_path);
}
static int stat_cb(const char* path, Handle ha, uintptr_t ctx)
{
struct stat* s = (struct stat*)ctx;
if(!ha)
return stat(path, s);
else
return zip_stat(ha, path, s);
assert(0 && "stat_cb: called with invalid path and archive handle");
return 1;
}
int vfs_stat(const char* fn, struct stat* s)
{
return vfs_foreach_path(stat_cb, fn, (uintptr_t)s);
}
///////////////////////////////////////////////////////////////////////////////
//
// file
//
///////////////////////////////////////////////////////////////////////////////
enum
{
VF_OPEN = 1,
VF_ZIP = 2,
VF_WRITE = 4
};
struct VFile
{
int flags;
size_t size;
// duplicated in File/ZFile below - oh well.
// resource data size is fixed anyway.
// cached contents of file from vfs_load
// (can't just use pointer - may be freed behind our back)
Handle hm;
union
{
File f;
ZFile zf;
};
};
H_TYPE_DEFINE(VFile)
static void VFile_init(VFile* vf, va_list args)
{
vf->flags = va_arg(args, int);
}
static void VFile_dtor(VFile* vf)
{
if(vf->flags & VF_OPEN)
{
if(vf->flags & VF_ZIP)
zip_close(&vf->zf);
else
file_close(&vf->f);
vf->flags &= ~(VF_OPEN);
}
mem_free_h(vf->hm);
}
// note: can't use the same callback: must check all paths
// for a plain file version before looking in archives.
// called for each mounted path or archive.
static int file_open_cb(const char* path, Handle ha, uintptr_t ctx)
{
VFile* vf = (VFile*)ctx;
// not a normal file - ask for next path.
if(ha != 0)
return 1;
int err = file_open(path, vf->flags, &vf->f);
if(!err)
{
vf->size = vf->f.size;
// somewhat of a hack.. but returning size from file_open
// is uglier, and relying on start of VFile / File to
// overlap is unsafe (#define PARANOIA adds a magic field).
return 0; // found it - done
}
// failed to open.
return err;
}
// called for each mounted path or archive.
static int zip_open_cb(const char* path, Handle ha, uintptr_t ctx)
{
VFile* vf = (VFile*)ctx;
// file not in archive - ask for next path.
if(ha == 0)
return 1;
int err = zip_open(ha, path, &vf->zf);
if(!err)
{
vf->size = vf->zf.ucsize;
vf->flags |= VF_ZIP;
return 0; // found it - done
}
// failed to open
return err;
}
static int VFile_reload(VFile* vf, const char* fn)
{
// we're done if file is already open. need to check this because reload order
// (e.g. if resource opens a file) is unspecified.
if(vf->flags & VF_OPEN)
return 0;
int err = -1;
// careful! this code is a bit tricky. sorry :P
// only allow plain-file lookup before looking in Zip archives if
// opening for writing (we don't support writing to archive), or
// on dev builds. rationale for disabling in final builds: much faster,
// and a bit more secure (we can protect archives from modification more
// easily than the individual files).
// dev build: always allow plain-file lookup.
// final: only allow if opening for writing.
//
// note: flags have been set by init already
#ifdef FINAL
if(vf->flags & VF_WRITE)
#endif
err = vfs_foreach_path(file_open_cb, fn, (uintptr_t)vf);
// load from Zip iff we didn't successfully open the plain file,
// and we're not opening for writing.
if(err < 0 && !(vf->flags & VF_WRITE))
err = vfs_foreach_path(zip_open_cb, fn, (uintptr_t)vf);
// failed - return error code
if(err < 0)
return err;
// success
vf->flags |= VF_OPEN;
return 0;
}
Handle vfs_open(const char* fn, int flags /* = 0 */)
{
// security check: path must not include ".."
// (checking start of string isn't enough)
if(strstr(fn, ".."))
{
assert(0 && "vfs_open: .. in path string");
return 0;
}
return h_alloc(H_VFile, fn, 0, flags);
// pass file flags to init
}
inline int vfs_close(Handle& h)
{
return h_free(h, H_VFile);
}
// we return a ZFile handle for files in an archive, instead of making the
// handle a member of File, so we don't open 2 handles per file. there's too
// much internal Zip state to store here - we want to keep it encapsulated.
// note: since handle types are private to each module, each function has to
// give the Zip version a crack at its handle, and only continue if it fails.
// when adding file types, their state should go in File, or else the
// 'is this our handle' checks would get unwieldy.
ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p)
{
H_DEREF(hf, VFile, vf);
// (vfs_open makes sure it's not opened for writing if zip)
if(vf->flags & VF_ZIP)
return zip_read(&vf->zf, ofs, size, p);
// normal file:
// let file_io alloc the buffer if the caller didn't,
// because it knows about alignment / padding requirements
return file_io(&vf->f, ofs, size, &p);
}
Handle vfs_load(const char* fn, void*& p, size_t& size)
{
p = 0; // vfs_io needs initial 0 value
size = 0;
Handle hf = vfs_open(fn);
if(hf <= 0)
return hf; // error code
H_DEREF(hf, VFile, vf);
Handle hm = 0;
// already read into mem - return existing mem handle
// TODO: what if mapped?
if(vf->hm > 0)
{
p = mem_get_ptr(vf->hm, &size);
if(p)
{
assert(vf->size == size && "vfs_load: mismatch between File and Mem size");
hm = vf->hm;
goto skip_read;
}
else
assert(0 && "vfs_load: invalid MEM attached to vfile (0 pointer)");
// happens if someone frees the pointer. not an error!
}
size = vf->size;
{ // VC6 goto fix
size_t nread = vfs_io(hf, 0, size, p);
if(nread > 0)
hm = mem_assign(p, size);
}
skip_read:
vfs_close(hf);
// if we fail, make sure these are set to 0
// (they may have been assigned values above)
if(hm <= 0)
p = 0, size = 0;
return hm;
}
int vfs_store(const char* fn, void* p, size_t size)
{
Handle hf = vfs_open(fn, VFS_WRITE);
if(hf <= 0)
return (int)hf; // error code
H_DEREF(hf, VFile, vf);
int ret = vfs_io(hf, 0, size, p);
vfs_close(hf);
return ret;
}
// separate mem and mmap handles
//mmap used rarely, don't need transparency
//mmap needs filename (so it's invalidated when file changes), passed to h_alloc
//also uses reload and dtor - don't want to stretch mem too far (doesn't belong there)
struct MMap
{
void* p;
size_t size;
File f;
};
H_TYPE_DEFINE(MMap)
static void MMap_init(MMap* m, va_list args)
{
}
void MMap_dtor(MMap* m)
{
// munmap(m->p, m->size);
// vfs_close(m->hf);
}
/*
// get pointer to archive in memory
Handle ham;
if(0)
// if(zip_archive_info(0, 0, &ham) == 0)
{
void* archive_p;
size_t archive_size;
archive_p = mem_get_ptr(ham, &archive_size);
// return file's pos in mapping
assert(ofs < archive_size && "vfs_load: vfile.ofs exceeds Zip archive size");
_p = (char*)archive_p + ofs;
_size = out_size;
hm = mem_assign(_p, _size);
goto done;
}
}
*/
int MMap_reload(MMap* m, const char* fn)
{
/*
Handle hf = vfs_open(fn);
if(!hf)
return -1;
File* vf = H_USER_DATA(hf, File);
Handle hf2;
size_t ofs;
#if 0
if(vf->hz)
if(zip_get_file(vf->hz, hf2, ofs) < 0)
return -1;
#endif
void* p = mmap(0, (uint)vf->size, PROT_READ, MAP_PRIVATE, vf->fd, 0);
if(!p)
{
vfs_close(hf);
return -1;
}
m->p = p;
m->size = vf->size;
*/
return 0;
}
Handle vfs_map(const char* fn, int flags, void*& p, size_t& size)
{
Handle hf = vfs_open(fn, flags);
H_DEREF(hf, VFile, vf);
int err = file_map(&vf->f, p, size);
if(err < 0)
return err;
MEM_DTOR dtor = 0;
uintptr_t ctx = 0;
return mem_assign(p, size, 0, dtor, ctx);
}
int vfs_unmap(Handle& hm)
{
return h_free(hm, H_MMap);
}

62
source/lib/res/vfs.h Executable file
View File

@ -0,0 +1,62 @@
// virtual file system - transparent access to files in archives;
// allows multiple search paths
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __VFS_H__
#define __VFS_H__
#include "h_mgr.h"
#include "posix.h"
#define VFS_MAX_PATH 63
extern int vfs_set_root(const char* argv0, const char* root);
extern int vfs_mount(const char* path);
extern int vfs_umount(const char* path);
extern int vfs_stat(const char* fn, struct stat *buffer);
extern int vfs_realpath(const char* fn, char* realpath);
extern Handle vfs_load(const char* fn, void*& p, size_t& size);
extern Handle vfs_open(const char* fn, int flags = 0);
extern int vfs_close(Handle& h);
extern Handle vfs_map(Handle hf, int flags, void*& p, size_t& size);
//
// async read interface
//
extern Handle vfs_start_read(const Handle hf, size_t ofs, size_t& advance, void* buf);
extern int vfs_wait_read(Handle hr, void*& p, size_t& size);
extern int vfs_discard_read(Handle& hr);
extern ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p);
enum
{
VFS_WRITE = 1, // write-only access; otherwise, read only
VFS_MODIFY = 2, // want to be able to change in memory data
VFS_NOCACHE = 4, // don't cache whole file, e.g. if cached on a higher level
VFS_RANDOM = 8 // random access hint, allow offset
};
#endif // #ifndef __VFS_H__

536
source/lib/res/zip.cpp Executable file
View File

@ -0,0 +1,536 @@
// Zip archiving on top of ZLib.
//
// Copyright (c) 2004 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cassert>
#include <cstring>
#include <cstdlib>
#include "zip.h"
#include "file.h"
#include "lib.h"
#include "misc.h"
#include "h_mgr.h"
#include "mem.h"
#include "vfs.h"
#include <zlib.h>
#ifdef _MSC_VER
#pragma comment(lib, "zlib.lib")
#endif
//
// low-level in-memory inflate routines on top of ZLib
//
uintptr_t zip_init_ctx()
{
// allocate ZLib stream
z_stream* stream = (z_stream*)mem_alloc(round_up(sizeof(z_stream), 32), 32, MEM_ZERO);
if(inflateInit2(stream, -MAX_WBITS) != Z_OK)
// -MAX_WBITS indicates no zlib header present
return 0;
return (uintptr_t)stream;
}
int zip_start_read(uintptr_t ctx, void* out, size_t out_size)
{
if(!ctx)
return ERR_INVALID_PARAM;
z_stream* stream = (z_stream*)ctx;
if(stream->next_out || stream->avail_out)
{
assert(0 && "zip_start_read: ctx already in use!");
return -1;
}
stream->next_out = (Bytef*)out;
stream->avail_out = (uInt)out_size;
return 0;
}
ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size)
{
if(!ctx)
return ERR_INVALID_PARAM;
z_stream* stream = (z_stream*)ctx;
size_t prev_avail_out = stream->avail_out;
int err = inflate(stream, Z_SYNC_FLUSH);
// check+return how much actual data was read
size_t avail_out = stream->avail_out;
assert(avail_out <= prev_avail_out);
// make sure output buffer size didn't magically increase
size_t nread = prev_avail_out - avail_out;
if(!nread)
return (err < 0)? err : 0;
// try to pass along the ZLib error code, but make sure
// it isn't treated as 'bytes read', i.e. > 0.
return nread;
}
int zip_finish_read(uintptr_t ctx)
{
if(!ctx)
return ERR_INVALID_PARAM;
z_stream* stream = (z_stream*)ctx;
if(stream->avail_in || stream->avail_out)
{
assert("zip_finish_read: input or input buffer has space remaining");
stream->avail_in = stream->avail_out = 0;
return -1;
}
stream->next_in = 0;
stream->next_out = 0;
return 0;
}
int zip_free_ctx(uintptr_t ctx)
{
if(!ctx)
return ERR_INVALID_PARAM;
z_stream* stream = (z_stream*)ctx;
assert(stream->next_out == 0);
inflateEnd(stream);
mem_free(stream);
return 0;
}
//
// Zip archive
//
static const char ecdr_id[] = "PK\5\6"; // End of Central Directory Header identifier
static const char cdfh_id[] = "PK\1\2"; // Central File Header identifier
static const char lfh_id[] = "PK\3\4"; // Local File Header identifier
struct ZEnt
{
size_t ofs;
size_t csize; // 0 if not compressed
size_t ucsize;
// why csize?
// file I/O may be N-buffered, so it's good to know when the raw data
// stops (or else we potentially overshoot by N-1 blocks),
// but not critical, since Zip files are stored individually.
//
// we also need a way to check if file compressed (e.g. to fail mmap
// requests if the file is compressed). packing a bit in ofs or
// ucsize is error prone and ugly (1 bit less won't hurt though).
// any other way will mess up the nice 8 byte size anyway, so might
// as well store csize.
//
// don't worry too much about non-power-of-two size, will probably
// change to global FS tree instead of linear lookup later anyway.
};
struct ZArchive
{
File f;
// file lookup
u16 num_files;
u16 last_file; // index of last file we found (speed up lookups of sequential files)
u32* fn_hashs; // split for more efficient search
ZEnt* ents;
};
H_TYPE_DEFINE(ZArchive)
static void ZArchive_init(ZArchive* za, va_list args)
{
}
static void ZArchive_dtor(ZArchive* za)
{
file_close(&za->f);
mem_free(za->fn_hashs); // both fn_hashs[] and files[]
}
static int ZArchive_reload(ZArchive* za, const char* fn)
{
const u8* ecdr; // declare here to avoid goto scope problems
int err = file_open(fn, 0, &za->f);
if(err < 0)
return err;
void* p;
size_t size;
err = file_map(&za->f, p, size);
if(err < 0)
return err;
{
// find end of central dir record
// by scanning last 66000 bytes of file for ecdr_id magic
// (zip comment <= 65535 bytes, sizeof(ECDR) = 22, add some for safety)
// if the zip file is < 66000 bytes, scan the whole file
size_t bytes_left = 66000; // min(66k, size) - avoid stupid warning
if(bytes_left > size)
bytes_left = size;
ecdr = (const u8*)p + size - 22;
if(*(u32*)ecdr == *(u32*)&ecdr_id)
goto found_ecdr;
ecdr = (const u8*)p + size - bytes_left;
while(bytes_left-3 > 0)
{
if(*(u32*)ecdr == *(u32*)&ecdr_id)
goto found_ecdr;
// check next 4 bytes (non aligned!!)
ecdr++;
bytes_left--;
}
// reached EOF and still haven't found the ECDR identifier
}
fail:
file_unmap(&za->f);
file_close(&za->f);
return -1;
found_ecdr:
{
// read ECDR
const u16 num_files = read_le16(ecdr+10);
const u32 cd_ofs = read_le32(ecdr+16);
// memory for fn_hash and Ent arrays
void* file_list_mem = mem_alloc(num_files * (sizeof(u32) + sizeof(ZEnt)), 4*KB);
if(!file_list_mem)
goto fail;
u32* fn_hashs = (u32*)file_list_mem;
ZEnt* ents = (ZEnt*)((u8*)file_list_mem + num_files*sizeof(u32));
// cache file list for faster lookups
// currently linear search, comparing filename hash.
// TODO: if too slow, use hash table.
const u8* cdfh = (const u8*)p+cd_ofs;
u32* hs = fn_hashs;
ZEnt* ent = ents;
u16 i;
for(i = 0; i < num_files; i++)
{
// read CDFH
if(*(u32*)cdfh != *(u32*)cdfh_id)
continue;
const u32 csize = read_le32(cdfh+20);
const u32 ucsize = read_le32(cdfh+24);
const u16 fn_len = read_le16(cdfh+28);
const u16 e_len = read_le16(cdfh+30);
const u16 c_len = read_le16(cdfh+32);
const u32 lfh_ofs = read_le32(cdfh+42);
const u8 method = cdfh[10];
if(method & ~8) // neither deflated nor stored
continue;
// read LFH
const u8* const lfh = (const u8*)p + lfh_ofs;
if(*(u32*)lfh != *(u32*)lfh_id)
continue;
const u16 lfh_fn_len = read_le16(lfh+26);
const u16 lfh_e_len = read_le16(lfh+28);
const char* lfh_fn = (const char*)lfh+30;
*hs++ = fnv_hash(lfh_fn, lfh_fn_len);
ent->ofs = lfh_ofs + 30 + lfh_fn_len + lfh_e_len;
ent->csize = csize;
ent->ucsize = ucsize;
ent++;
(uintptr_t&)cdfh += 46 + fn_len + e_len + c_len;
}
za->num_files = i;
za->last_file = 0;
za->fn_hashs = fn_hashs;
za->ents = ents;
} // scope
return 0;
}
// open and return a handle to the zip archive indicated by <fn>
inline Handle zip_archive_open(const char* const fn)
{
return h_alloc(H_ZArchive, fn);
}
// close the archive <ha> and set ha to 0
inline int zip_archive_close(Handle& ha)
{
return h_free(ha, H_ZArchive);
}
//
// file from Zip archive
//
static int lookup(Handle ha, const char* fn, const ZEnt*& ent)
{
H_DEREF(ha, ZArchive, za);
// find its File descriptor
const u32 fn_hash = fnv_hash(fn, strlen(fn));
u16 i = za->last_file+1;
if(i >= za->num_files || za->fn_hashs[i] != fn_hash)
{
for(i = 0; i < za->num_files; i++)
if(za->fn_hashs[i] == fn_hash)
break;
if(i == za->num_files)
return ERR_FILE_NOT_FOUND;
za->last_file = i;
}
ent = &za->ents[i];
return 0;
}
// marker for ZFile struct, to make sure it's valid
static const u32 ZFILE_MAGIC = FOURCC('Z','F','I','L');
static int zfile_validate(uint line, ZFile* zf)
{
const char* msg = "";
int err = -1;
if(!zf)
{
msg = "ZFile* parameter = 0";
err = ERR_INVALID_PARAM;
}
#ifdef PARANOIA
else if(zf->magic != FILE_MAGIC)
msg = "ZFile corrupted (magic field incorrect)";
#endif
#ifndef NDEBUG
else if(!h_user_data(zf->ha, H_ZArchive))
msg = "invalid archive handle";
#endif
else if(!zf->ucsize)
msg = "ucsize = 0";
else if(!zf->read_ctx)
msg = "read context invalid";
// everything is OK
else
return 0;
// failed somewhere - err is the error code,
// or -1 if not set specifically above.
debug_out("zfile_validate at line %d failed: %s\n", line, msg);
assert(0 && "zfile_validate failed");
return err;
}
#define CHECK_ZFILE(f)\
do\
{\
int err = zfile_validate(__LINE__, f);\
if(err < 0)\
return err;\
}\
while(0);
int zip_open(const Handle ha, const char* fn, ZFile* zf)
{
memset(zf, 0, sizeof(ZFile));
if(!zf)
goto invalid_zf;
// jump to CHECK_ZFILE post-check, which will handle this.
{
const ZEnt* ze;
int err = lookup(ha, fn, ze);
if(err < 0)
return err;
#ifdef PARANOIA
zf->magic = ZFILE_MAGIC;
#endif
zf->ofs = ze->ofs;
zf->csize = ze->csize;
zf->ucsize = ze->ucsize;
zf->ha = ha;
zf->read_ctx = zip_init_ctx();
}
invalid_zf:
CHECK_ZFILE(zf)
return 0;
}
int zip_close(ZFile* zf)
{
CHECK_ZFILE(zf)
// remaining fields don't need to be freed/cleared
return zip_free_ctx(zf->read_ctx);
}
// return file information for <fn> in archive <ha>
int zip_stat(Handle ha, const char* fn, struct stat* s)
{
const ZEnt* ze;
int err = lookup(ha, fn, ze);
if(err < 0)
return err;
s->st_size = (off_t)ze->ucsize;
return 0;
}
// convenience function, allows implementation change in ZFile.
// note that size == ucsize isn't foolproof, and adding a flag to
// ofs or size is ugly and error-prone.
// no error checking - always called from functions that check zf.
static inline bool is_compressed(ZFile* zf)
{
return zf->csize != 0;
}
// note: we go to a bit of trouble to make sure the buffer we allocated
// (if p == 0) is freed when the read fails.
ssize_t zip_read(ZFile* zf, size_t raw_ofs, size_t size, void*& p)
{
CHECK_ZFILE(zf)
ssize_t err = -1;
ssize_t raw_bytes_read;
ZArchive* za = H_USER_DATA(zf->ha, ZArchive);
if(!za)
return ERR_INVALID_HANDLE;
void* our_buf = 0; // buffer we allocated (if necessary)
if(!p)
{
p = our_buf = mem_alloc(size);
if(!p)
return ERR_NO_MEM;
}
const size_t ofs = zf->ofs + raw_ofs;
// not compressed - just pass it on to file_io
// (avoid the Zip inflate start/finish stuff below)
if(!is_compressed(zf))
return file_io(&za->f, ofs, size, &p);
// no need to set last_raw_ofs - only checked if compressed.
// compressed
// make sure we continue where we left off
// (compressed data must be read in one stream / sequence)
//
// problem: partial reads
if(raw_ofs != zf->last_raw_ofs)
{
assert(0 && "zip_read: compressed read offset is non-continuous");
goto fail;
}
err = (ssize_t)zip_start_read(zf->read_ctx, p, size);
if(err < 0)
{
fail:
// we allocated it, so free it now
if(our_buf)
{
mem_free(our_buf);
p = 0;
}
return err;
}
// read blocks from the archive's file starting at ofs and pass them to
// zip_inflate, until all compressed data has been read, or it indicates
// the desired output amount has been reached.
const size_t raw_size = zf->csize;
raw_bytes_read = file_io(&za->f, ofs, raw_size, (void**)0, zip_inflate, zf->read_ctx);
err = zip_finish_read(zf->read_ctx);
if(err < 0)
goto fail;
err = raw_bytes_read;
// failed - make sure buffer is freed
if(err <= 0)
goto fail;
return err;
}
int zip_map(ZFile* zf, void*& p, size_t& size)
{
CHECK_ZFILE(zf)
// doesn't really make sense to map compressed files, so disallow it.
if(is_compressed(zf))
{
assert(0 && "mapping a compressed file from archive. why?");
return -1;
}
H_DEREF(zf->ha, ZArchive, za)
return file_map(&za->f, p, size);
}

110
source/lib/res/zip.h Executable file
View File

@ -0,0 +1,110 @@
// Zip archiving on top of ZLib.
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __ZIP_H__
#define __ZIP_H__
#include "h_mgr.h"
#include "lib.h"
#include "File.h"
//
// low-level in-memory inflate routines
//
extern uintptr_t zip_init_ctx();
extern int zip_start_read(uintptr_t ctx, void* out, size_t out_size);
extern ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size);
extern int zip_finish_read(uintptr_t ctx);
extern int zip_free_ctx(uintptr_t ctx);
//
// archive
//
// open and return a handle to the zip archive indicated by <fn>
extern Handle zip_archive_open(const char* fn);
// close the archive <ha> and set ha to 0
extern int zip_archive_close(Handle& ha);
//
// file
//
struct ZFile
{
#ifdef PARANOIA
u32 magic;
#endif
size_t ofs;
size_t csize;
size_t ucsize;
size_t last_raw_ofs;
Handle ha;
uintptr_t read_ctx;
};
// return file information for <fn> (may include path) in archive <ha>
extern int zip_stat(Handle ha, const char* fn, struct stat* s);
// open the file <fn> in archive <ha>, and fill *zf with information about it.
extern int zip_open(Handle ha, const char* fn, ZFile* zf);
// close the file <zf>
extern int zip_close(ZFile* zf);
extern int zip_map(ZFile* zf, void*& p, size_t& size);
extern ssize_t zip_read(ZFile* zf, size_t ofs, size_t size, void*& p);
//--------
// read from file <hz>, starting at offset <ofs> in the compressed data
// (typically only used if the file is known to be stored).
// p == 0: allocate, read into, and return the output buffer
// p != 0: read into given output buffer, return handle to it
// if file is compressed, size must be >= uncompressed file size
// size: no input value, unless specifying an output buffer (p != 0)
// out:
//extern void* zip_mmap(
#endif // #ifndef __ZIP_H__

6
source/lib/sdl.h Executable file
View File

@ -0,0 +1,6 @@
#if defined(_WIN32) && !defined(NO_WSDL)
#include "sysdep/win/wsdl.h"
#else
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
#endif

320
source/lib/sysdep/ia32.cpp Executable file
View File

@ -0,0 +1,320 @@
// IA-32 (x86) specific code
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifdef _M_IX86
#include <cstring>
#include <cstdio> // sscanf
#include <cmath>
#include <cassert>
#include <time.h>
#include <vector>
#include <algorithm>
#include "ia32.h"
#include "lib.h"
#include "detect.h"
#include "timer.h"
#ifdef _WIN32
#include "win/hrt.h"
#endif
// replace pathetic MS libc implementation
#ifdef _WIN32
double _ceil(double f)
{
double r;
const float _49 = 0.499999f;
__asm
{
fld [f]
fadd [_49]
frndint
fstp [r]
}
UNUSED(f)
return r;
}
#endif
// return convention for 64 bits with VC7.1, ICC8 is in edx:eax,
// so temp variable is unnecessary, but we play it safe.
inline u64 rdtsc()
{
u64 c;
__asm
{
cpuid
rdtsc
mov dword ptr [c], eax
mov dword ptr [c+4], edx
}
return c;
}
// change FPU control word (used to set precision)
uint _control87(uint new_cw, uint mask)
{
__asm
{
push eax
fnstcw [esp]
pop eax ; old_cw
mov ecx, [new_cw]
mov edx, [mask]
and ecx, edx ; new_cw & mask
not edx ; ~mask
and eax, edx ; old_cw & ~mask
or eax, ecx ; (old_cw & ~mask) | (new_cw & mask)
push eax
fldcw [esp]
pop eax
}
UNUSED(new_cw)
UNUSED(mask)
return 0;
}
long cpu_caps = 0;
long cpu_ext_caps = 0;
static char cpu_vendor[13];
static int family, model; // used to detect cpu_type
int tsc_is_safe = -1;
// optimized for size
static void __declspec(naked) cpuid()
{
__asm
{
pushad
; ICC7: pusha is the 16-bit form!
; make sure CPUID is supported
pushfd
or byte ptr [esp+2], 32
popfd
pushfd
pop eax
shr eax, 22 ; bit 21 toggled?
jnc no_cpuid
; get vendor string
xor eax, eax
cpuid
mov edi, offset cpu_vendor
xchg eax, ebx
stosd
xchg eax, edx
stosd
xchg eax, ecx
stosd
; (already 0 terminated)
; get CPU signature and std feature bits
push 1
pop eax
cpuid
mov [cpu_caps], edx
mov edx, eax
shr edx, 4
and edx, 0x0f
mov [model], edx
shr eax, 8
and eax, 0x0f
mov [family], eax
; make sure CPUID ext functions are supported
mov esi, 0x80000000
mov eax, esi
cpuid
cmp eax, esi
jbe no_ext_funcs
lea esi, [esi+4]
cmp eax, esi
jb no_brand_str
; get CPU brand string (>= Athlon XP, P4)
mov edi, offset cpu_type
push -2
pop ebp ; loop counter: [-2, 0]
$1: lea eax, [ebp+esi] ; 0x80000002 .. 4
cpuid
stosd
xchg eax, ebx
stosd
xchg eax, ecx
stosd
xchg eax, edx
stosd
inc ebp
jle $1
; (already 0 terminated)
no_brand_str:
; get extended feature flags
lea eax, [esi-3] ; 0x80000001
cpuid
mov [cpu_ext_caps], edx
no_ext_funcs:
no_cpuid:
popad
ret
}
}
void ia32_get_cpu_info()
{
cpuid();
if(cpu_caps == 0) // cpuid not supported - can't do the rest
return;
// cpu_type set from brand string - clean it up some
if(strcmp(cpu_type, "unknown") != 0)
{
// strip (tm) from Athlon string
if(!strncmp(cpu_type, "AMD Athlon(tm)", 14))
memmove(cpu_type+10, cpu_type+14, 34);
// remove 2x (R) and CPU freq from P4 string
float a;
// the indicated frequency isn't necessarily correct - the CPU may be
// overclocked. need to pass a variable though, since scanf returns
// the number of fields actually stored.
if(sscanf(cpu_type, " Intel(R) Pentium(R) 4 CPU %fGHz", &a) == 1)
strcpy(cpu_type, "Intel Pentium 4");
}
// detect cpu_type ourselves
else
{
// AMD
if(!strcmp(cpu_vendor, "AuthenticAMD"))
{
if(family == 6)
strcpy(cpu_type, (model == 3)? "AMD Duron" : "AMD Athlon");
}
// Intel
else if(!strcmp(cpu_vendor, "GenuineIntel"))
{
if(family == 6 && model >= 7)
strcpy(cpu_type, "Intel Pentium III / Celeron");
}
}
// .. get old policy and priority
int old_policy;
static sched_param old_param;
pthread_getschedparam(pthread_self(), &old_policy, &old_param);
// .. set max priority
static sched_param max_param;
max_param.sched_priority = sched_get_priority_max(SCHED_RR);
pthread_setschedparam(pthread_self(), SCHED_RR, &max_param);
// calculate CPU frequency.
// balance measuring time (~ 10 ms) and accuracy (< 1 0/00 error -
// ok for using the TSC as a time reference)
if(cpu_caps & TSC) // needed to calculate freq; bogomips are a WAG
{
// stabilize CPUID for timing (first few calls take longer)
__asm cpuid __asm cpuid __asm cpuid
u64 c0, c1;
std::vector<double> samples;
int num_samples = 20;
// if clock is low-res, do less samples so it doesn't take too long
if(timer_res() >= 1e-3)
num_samples = 10;
int i;
for(i = 0; i < num_samples; i++)
{
again:
// count # of clocks in max{1 tick, 1 ms}
double t0;
double t1 = get_time();
// .. wait for start of tick
do
{
c0 = rdtsc(); // changes quickly
t0 = get_time();
}
while(t0 == t1);
// .. wait until start of next tick and at least 1 ms
do
{
c1 = rdtsc();
t1 = get_time();
}
while(t1 < t0 + 1e-3);
double ds = t1 - t0;
if(ds < 0.0) // bogus time delta - take another sample
goto again;
// .. freq = (delta_clocks) / (delta_seconds);
// cpuid/rdtsc/timer overhead is negligible
double freq = (i64)(c1-c0) / ds;
// VC6 can't convert u64 -> double, and we don't need full range
samples.push_back(freq);
}
std::sort(samples.begin(), samples.end());
double median = samples[num_samples/2];
// median filter (remove upper and lower 25% and average the rest)
double sum = 0.0;
const int lo = num_samples/4, hi = 3*num_samples/4;
for(i = lo; i < hi; i++)
sum += samples[i];
cpu_freq = sum / (hi-lo);
// HACK: if _WIN32, the HRT makes its final implementation choice
// in the first calibrate call where cpu_freq and cpu_caps are
// available. call it here (via get_time) to have that happen now,
// so app code isn't surprised by a timer change, although the HRT
// does try to keep the timer continuous.
#ifdef _WIN32
hrt_override_impl(HRT_DEFAULT, HRT_NONE);
#endif
}
// else: TSC not available, can't measure
// restore previous policy and priority
pthread_setschedparam(pthread_self(), old_policy, &old_param);
}
#endif // #ifndef _M_IX86

66
source/lib/sysdep/ia32.h Executable file
View File

@ -0,0 +1,66 @@
// IA-32 (x86) specific code
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef _M_IX86
#error "including ia32.h without _M_IX86 defined"
#endif
#ifndef IA32_H
#define IA32_H
#include "lib.h"
extern double _ceil(double);
extern u64 rdtsc();
#ifndef _MCW_PC
#define _MCW_PC 0x0300 // Precision Control
#endif
#ifndef _PC_24
#define _PC_24 0x0000 // 24 bits
#endif
extern uint _control87(uint new_cw, uint mask);
enum
{
TSC = BIT(4),
CMOV = BIT(15),
MMX = BIT(23),
SSE = BIT(25),
SSE2 = BIT(26)
};
extern long cpu_caps;
// define instead of enum to avoid stupid sign conversion warning
#define EXT_3DNOW_PRO BIT(30)
#define EXT_3DNOW BIT(31)
extern long cpu_ext_caps;
extern int tsc_is_safe;
extern void ia32_get_cpu_info();
#endif // #ifndef IA32_H

26
source/lib/sysdep/sysdep.cpp Executable file
View File

@ -0,0 +1,26 @@
#include <stdio.h>
#include <stdarg.h>
#include "sysdep.h"
// portable debug output routines. Win32 offers better versions, which
// override these.
#ifndef _WIN32
// portable output routines (win.cpp overrides these)
void display_msg(const wchar_t* caption, const wchar_t* msg)
{
fwprintf(stderr, L"%ws: %ws\n", caption, msg);
}
void debug_out(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
#endif // #ifndef _WIN32

17
source/lib/sysdep/sysdep.h Executable file
View File

@ -0,0 +1,17 @@
#ifndef SYSDEP_H__
#define SYSDEP_H__
#include "win/win.h"
#ifdef __cplusplus
extern "C" {
#endif
extern void display_msg(const wchar_t* caption, const wchar_t* msg);
extern void debug_out(const char* fmt, ...);
#ifdef __cplusplus
}
#endif
#endif // #ifndef SYSDEP_H__

485
source/lib/sysdep/win/hrt.cpp Executable file
View File

@ -0,0 +1,485 @@
// Windows-specific high resolution timer
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cmath>
#include <cassert>
#include <cstdlib>
#include <numeric>
#include "hrt.h"
#include "lib.h"
#include "adts.h"
#include "sysdep/ia32.h"
#include "detect.h"
#include "win_internal.h"
#include <mmsystem.h> // not included by win due to WIN32_LEAN_AND_MEAN
#ifdef _MSC_VER
#pragma comment(lib, "winmm.lib")
#endif
// ticks per second; average of last few values measured in calibrate
static double hrt_freq = -1.0;
// used to start the hrt tick values near 0
static i64 hrt_origin = 0;
static HRTImpl hrt_impl = HRT_NONE;
static HRTOverride overrides[3] = { HRT_DEFAULT, HRT_DEFAULT, HRT_DEFAULT };
// HRTImpl enums as index
static i64 hrt_nominal_freq = -1;
#define lock() win_lock(HRT_CS)
#define unlock() win_unlock(HRT_CS)
// decide upon a HRT implementation, checking if we can work around
// each timer's issues on this platform, but allow user override
// in case there are unforeseen problems with one of them.
// order of preference (due to resolution and speed): TSC, QPC, TGT.
// split out of reset_impl so we can just return when impl is chosen.
static void choose_impl()
{
bool safe;
#define SAFETY_OVERRIDE(impl)\
if(overrides[impl] == HRT_DISABLE)\
safe = false;\
if(overrides[impl] == HRT_FORCE)\
safe = true;
#if defined(_M_IX86) && !defined(NO_TSC)
// CPU Timestamp Counter (incremented every clock)
// ns resolution, moderate precision (poor clock crystal?)
//
// issues:
// - multiprocessor systems: may be inconsistent across CPUs.
// could fix by keeping per-CPU timer state, but we'd need
// GetCurrentProcessorNumber (only available on Win Server 2003).
// spinning off a thread with set CPU affinity is too slow
// (we may have to wait until the next timeslice).
// we could discard really bad values, but that's still inaccurate.
// => unsafe.
// - deep sleep modes: TSC may not be advanced.
// not a problem though, because if the TSC is disabled, the CPU
// isn't doing any other work, either.
// - SpeedStep/'gearshift' CPUs: frequency may change.
// this happens on notebooks now, but eventually desktop systems
// 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_caps & TSC && cpu_freq > 0.0)
{
safe = (cpus == 1 && !cpu_speedstep);
SAFETY_OVERRIDE(HRT_TSC);
if(safe)
{
hrt_impl = HRT_TSC;
hrt_nominal_freq = (i64)cpu_freq;
return;
}
}
#endif // TSC
#if defined(_WIN32) && !defined(NO_QPC)
// Windows QueryPerformanceCounter API
// implementations:
// - PIT on Win2k - 838 ns resolution, slow to read (~3 µs)
// - PMT on WinXP - 279 ns ", moderate overhead (700 ns?)
// issues:
// 1) Q274323: may jump several seconds under heavy PCI bus load.
// not a problem, because the older systems on which this occurs
// have safe TSCs, so that is used instead.
// 2) "System clock problem can inflate benchmark scores":
// incorrect value if not polled every 4.5 seconds? solved
// by calibration thread, which reads timer every second anyway.
// - TSC on MP HAL - see TSC above.
// cache freq because QPF is fairly slow.
static i64 qpc_freq = -1;
// first call - check if QPC is supported
if(qpc_freq == -1)
{
LARGE_INTEGER i;
BOOL qpc_ok = QueryPerformanceFrequency(&i);
qpc_freq = qpc_ok? i.QuadPart : 0;
}
// QPC is available
if(qpc_freq > 0)
{
// PIT and PMT are safe.
if(qpc_freq == 1193182 || qpc_freq == 3579545)
safe = true;
// make sure QPC doesn't use the TSC
// (if it were safe, we would have chosen it above)
else
{
// can't decide yet - assume unsafe
if(cpu_freq == 0.0)
safe = false;
else
{
// compare QPC freq to CPU clock freq - can't rule out HPET,
// because its frequency isn't known (it's at least 10 MHz).
double freq_dist = fabs(cpu_freq / qpc_freq - 1.0);
safe = freq_dist > 0.05;
// safe if freqs not within 5% (i.e. it doesn't use TSC)
}
}
SAFETY_OVERRIDE(HRT_QPC);
if(safe)
{
hrt_impl = HRT_QPC;
hrt_nominal_freq = qpc_freq;
return;
}
}
#endif // QPC
//
// TGT
//
hrt_impl = HRT_TGT;
hrt_nominal_freq = 1000;
return;
assert(0 && "hrt_choose_impl: no safe timer found!");
hrt_impl = HRT_NONE;
hrt_nominal_freq = -1;
return;
}
// return ticks since first call. lock must be held.
//
// split to allow calling from reset_impl_lk without recursive locking.
static i64 ticks_lk()
{
i64 t;
switch(hrt_impl)
{
// TSC
#if defined(_M_IX86) && !defined(NO_TSC)
case HRT_TSC:
t = rdtsc();
break;
#endif
// QPC
#if defined(_WIN32) && !defined(NO_QPC)
case HRT_QPC:
LARGE_INTEGER i;
QueryPerformanceCounter(&i);
t = i.QuadPart;
break;
#endif
// TGT
#ifdef _WIN32
case HRT_TGT:
t = (i64)timeGetTime();
break;
#endif
// add further timers here.
default:
assert(0 && "hrt_ticks: invalid impl");
// fall through
case HRT_NONE:
t = 0;
} // switch(impl)
return t - hrt_origin;
}
// this module is dependent upon detect (supplies system information needed to
// choose a HRT), which in turn uses our timer to detect the CPU clock
// when running on Windows (clock(), the only cross platform HRT available on
// Windows, isn't good enough - only 10..15 ms resolution).
//
// we first use a safe timer, and choose again after client code calls
// hrt_override_impl when system information is available.
// the timer will work without this call, but it won't use certain
// implementations. we do it this way, instead of polling every hrt_ticks,
// because a timer implementation change causes hrt_ticks to jump.
// choose a HRT implementation. lock must be held.
//
// don't want to saddle timer with the problem of initializing us
// on first call - it wouldn't otherwise need to be thread-safe.
static void reset_impl_lk()
{
HRTImpl old_impl = hrt_impl;
double old_time = 0.0;
// if not first time: want to reset tick origin
if(hrt_nominal_freq > 0)
old_time = ticks_lk() / hrt_freq;
// don't call hrt_time to avoid recursive lock.
choose_impl();
// post: hrt_impl != HRT_NONE, hrt_nominal_freq > 0
hrt_freq = (double)hrt_nominal_freq;
// if impl has changed, re-base tick counter.
// want it 0-based, but it must not go backwards WRT previous reading.
if(old_impl != hrt_impl)
hrt_origin = ticks_lk() - (i64)(old_time * hrt_freq);
}
// multiple entry points, can't use ONCE.
static bool initialized;
static void init_calibration_thread();
// call iff !initialized. lock must be held.
static void init_lk()
{
assert(!initialized && "init_lk called more than once!");
reset_impl_lk();
init_calibration_thread();
initialized = true;
}
// return ticks since first call.
i64 hrt_ticks()
{
lock();
// ugly, but it'll fall-through in common case.
if(!initialized)
goto init;
ready:
{ // VC6 goto fix
i64 t = ticks_lk();
unlock();
return t;
}
// reached from first call if init_lk hasn't been called yet. lock is held.
init:
init_lk();
goto ready;
}
// return seconds since first call.
double hrt_time()
{
lock();
// ugly, but it'll fall-through in common case.
if(!initialized)
goto init;
ready:
{ // VC6 goto fix
double t = ticks_lk() / hrt_freq;
unlock();
return t;
}
// reached from first call if init_lk hasn't been called yet. lock is held.
init:
init_lk();
goto ready;
}
// return seconds between start and end timestamps (returned by hrt_ticks).
// negative if end comes before start.
double hrt_delta_s(i64 start, i64 end)
{
// paranoia: reading double may not be atomic.
lock();
double freq = hrt_freq;
unlock();
assert(freq != -1.0 && "hrt_delta_s called before hrt_ticks");
return (end - start) / freq;
}
// return current timer implementation and its nominal (rated) frequency.
// nominal_freq is never 0.
// implementation only changes after hrt_override_impl.
//
// may be called before first hrt_ticks / hrt_time, so do init here also.
void hrt_query_impl(HRTImpl& impl, i64& nominal_freq)
{
lock();
if(!initialized)
init_lk();
impl = hrt_impl;
nominal_freq = hrt_nominal_freq;
unlock();
assert(nominal_freq > 0 && "hrt_query_impl: invalid hrt_nominal_freq");
}
// override our 'safe to use' decision.
// resets (and chooses another, if applicable) implementation;
// the timer may jump after doing so.
// call with HRT_DEFAULT, HRT_NONE to re-evaluate implementation choice
// after system info becomes available.
int hrt_override_impl(HRTOverride ovr, HRTImpl impl)
{
if((ovr != HRT_DISABLE && ovr != HRT_FORCE && ovr != HRT_DEFAULT) ||
(impl != HRT_TSC && impl != HRT_QPC && impl != HRT_TGT && impl != HRT_NONE))
{
assert(0 && "hrt_override: invalid ovr or impl param");
return -1;
}
lock();
overrides[impl] = ovr;
reset_impl_lk();
unlock();
return 0;
}
// 'safe' millisecond timer, used to measure HRT freq
static long ms_time()
{
#ifdef _WIN32
return (long)timeGetTime();
#else
return (long)clock();
#endif
}
static void calibrate()
{
lock();
// past couple of calculated hrt freqs, for averaging
typedef RingBuf<double, 8> SampleBuf;
static SampleBuf samples;
const i64 hrt_cur = ticks_lk();
const long ms_cur = ms_time();
// get elapsed times since last call
static long ms_cal_time;
static i64 hrt_cal_time;
double hrt_ds = (hrt_cur - hrt_cal_time) / hrt_freq;
double ms_ds = (ms_cur - ms_cal_time) / 1e3;
hrt_cal_time = hrt_cur;
ms_cal_time = ms_cur;
//
// when we wake up, we don't know if timer has been updated yet.
// they may be off by 1 tick - try to compensate.
//
double dt = ms_ds; // actual elapsed time since last calibration
double hrt_err = ms_ds - hrt_ds;
double hrt_abs_err = fabs(hrt_err), hrt_rel_err = hrt_abs_err / ms_ds;
double hrt_est_freq = hrt_ds / ms_ds;
// only add to buffer if within 10% of nominal
// (don't want to pollute buffer with flukes / incorrect results)
if(fabs(hrt_est_freq / hrt_nominal_freq - 1.0) < 0.10)
{
samples.push_back(hrt_est_freq);
// average all samples in buffer
double freq_sum = std::accumulate(samples.begin(), samples.end(), 0.0);
hrt_freq = freq_sum / (int)samples.size();
}
else
{
samples.clear();
hrt_freq = (double)hrt_nominal_freq;
}
unlock();
}
#ifdef _WIN32
// setup calibration thread
// note: winmm event is better than a thread or just checking elapsed time
// in hrt_ticks, because it's called right after TGT is updated;
// otherwise, we may be in the middle of a tick.
static UINT mm_event;
// keep calibrate() portable, don't need args anyway
static void CALLBACK trampoline(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
calibrate();
}
#endif
static void init_calibration_thread()
{
#ifdef _WIN32
// choosing resolution of winmm timer. don't want to increase the
// system clock interrupt rate (=> higher system load),
// so set res to current tick rate.
DWORD adj, incr;
BOOL adj_disabled;
GetSystemTimeAdjustment(&adj, &incr, &adj_disabled);
DWORD res = adj / 10000;
mm_event = timeSetEvent(1000, res, trampoline, 0, TIME_PERIODIC);
atexit2(timeKillEvent, mm_event);
#else
// TODO: port thread. no big deal, and the timer works without.
#endif
}

78
source/lib/sysdep/win/hrt.h Executable file
View File

@ -0,0 +1,78 @@
// Windows-specific high resolution timer
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "types.h"
// possible high resolution timers, in order of preference.
// see source for timer properties + problems.
// used as index into hrt_overrides.
enum HRTImpl
{
// CPU timestamp counter
HRT_TSC,
// Windows QueryPerformanceCounter
HRT_QPC,
// Windows timeGetTime
HRT_TGT,
// there will always be a valid timer in use.
// this is only used with hrt_override_impl.
HRT_NONE,
HRT_NUM_IMPLS
};
// while we do our best to work around timer problems or avoid them if unsafe,
// future requirements and problems may be different:
// allow the user or app to override our decisions (via hrt_override_impl)
enum HRTOverride
{
// allow use of this implementation if available,
// and we can work around its problems
HRT_DEFAULT,
// override our 'safe to use' recommendation
// set by hrt_override_impl (via command line arg or console function)
HRT_DISABLE,
HRT_FORCE
};
// return ticks since first call.
extern i64 hrt_ticks();
// return seconds since first call.
extern double hrt_time();
// return seconds between start and end timestamps (returned by hrt_ticks).
// negative if end comes before start.
extern double hrt_delta_s(i64 start, i64 end);
// return current timer implementation and its nominal (rated) frequency.
// nominal_freq is never 0.
// implementation only changes after hrt_override_impl.
extern void hrt_query_impl(HRTImpl& impl, i64& nominal_freq);
// override our 'safe to use' decision.
// resets (and chooses another, if applicable) implementation;
// the timer may jump after doing so.
// call with HRT_DEFAULT, HRT_NONE to re-evaluate implementation choice
// after system info becomes available.
extern int hrt_override_impl(HRTOverride ovr, HRTImpl impl);

563
source/lib/sysdep/win/waio.cpp Executable file
View File

@ -0,0 +1,563 @@
// POSIX asynchronous I/O for Win32
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <io.h>
#include "lib.h"
#include "win_internal.h"
#include "misc.h"
#include "types.h"
//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
// async-capable handles to each lowio file
// current implementation: open both versions of the file on open()
// wastes 1 handle/file, but we don't have to remember the filename/mode
//
// note: current Windows lowio handle limit is 2k
class AioHandles
{
public:
AioHandles() : hs(0), size(0) {}
~AioHandles();
HANDLE get(int fd);
int set(int fd, HANDLE h);
private:
HANDLE* hs;
uint size;
};
static AioHandles* aio_hs;
AioHandles::~AioHandles()
{
win_lock(WAIO_CS);
for(int i = 0; (unsigned)i < size; i++)
if(hs[i] != INVALID_HANDLE_VALUE)
{
CloseHandle(hs[i]);
hs[i] = INVALID_HANDLE_VALUE;
}
free(hs);
hs = 0;
size = 0;
win_unlock(WAIO_CS);
}
// get async capable handle to file <fd>
HANDLE AioHandles::get(int fd)
{
HANDLE h = INVALID_HANDLE_VALUE;
win_lock(WAIO_CS);
if((unsigned)fd < size)
h = hs[fd];
win_unlock(WAIO_CS);
return h;
}
int AioHandles::set(int fd, HANDLE h)
{
win_lock(WAIO_CS);
// grow hs array to at least fd+1 entries
if((unsigned)fd >= size)
{
uint size2 = (uint)round_up(fd+8, 8);
HANDLE* hs2 = (HANDLE*)realloc(hs, size2*sizeof(HANDLE));
if(!hs2)
goto fail;
for(uint i = size; i < size2; i++)
hs2[i] = INVALID_HANDLE_VALUE;
hs = hs2;
size = size2;
}
if(hs[fd] != INVALID_HANDLE_VALUE)
{
assert("set_aio_h: handle already set!");
goto fail;
}
hs[fd] = h;
win_unlock(WAIO_CS);
return 0;
fail:
win_unlock(WAIO_CS);
return -1;
}
int aio_assign_handle(uintptr_t handle)
{
//
// CRT stores osfhandle. if we pass an invalid handle (say, 0),
// we get an exception when closing the handle if debugging.
// events can be created relatively quickly (~1800 clocks = 1µs),
// and are also freed with CloseHandle, so just pass that.
HANDLE h = CreateEvent(0,0,0,0);
if(h == INVALID_HANDLE_VALUE)
return -1;
int fd = _open_osfhandle((intptr_t)h, 0);
if(fd < 0)
return fd;
return aio_hs->set(fd, (HANDLE)handle);
}
static void init();
// open fn in async mode; associate with fd (retrieve via aio_h(fd))
int aio_open(const char* fn, int mode, int fd)
{
WIN_ONCE(init()); // TODO: need to do this elsewhere in case other routines called first?
// interpret mode
DWORD access = GENERIC_READ; // assume O_RDONLY
DWORD share = 0;
if(mode & O_WRONLY)
access = GENERIC_WRITE;
else if(mode & O_RDWR)
access = GENERIC_READ|GENERIC_WRITE;
else
share = FILE_SHARE_READ;
DWORD create = OPEN_EXISTING;
if(mode & O_CREAT)
create = (mode & O_EXCL)? CREATE_NEW : CREATE_ALWAYS;
DWORD flags = FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING|FILE_FLAG_SEQUENTIAL_SCAN;
// open file
HANDLE h = CreateFile(fn, access, share, 0, create, flags, 0);
if(h == INVALID_HANDLE_VALUE)
return -1;
if(aio_hs->set(fd, h) < 0)
{
CloseHandle(h);
return -1;
}
return 0;
}
int aio_close(int fd)
{
HANDLE h = aio_hs->get(fd);
if(h == INVALID_HANDLE_VALUE) // out of bounds or already closed
return -1;
CloseHandle(h);
aio_hs->set(fd, INVALID_HANDLE_VALUE);
return 0;
}
//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
// information about active transfers (reused)
struct Req
{
// used to identify this request; != 0 <==> request valid
aiocb* cb;
OVERLAPPED ovl;
// hEvent signals when transfer complete
// read into a separate align buffer if necessary
// (note: unaligned writes aren't supported. see aio_rw)
size_t pad; // offset from starting sector
void* buf; // reused; resize if too small
size_t buf_size;
};
class Reqs
{
public:
Reqs();
~Reqs();
Req* find(const aiocb* cb);
Req* alloc(const aiocb* cb);
private:
enum { MAX_REQS = 8 };
Req reqs[MAX_REQS];
};
Reqs::Reqs()
{
for(int i = 0; i < MAX_REQS; i++)
{
memset(&reqs[i], 0, sizeof(Req));
reqs[i].ovl.hEvent = CreateEvent(0,1,0,0); // manual reset
}
}
Reqs::~Reqs()
{
Req* r = reqs;
for(int i = 0; i < MAX_REQS; i++, r++)
{
r->cb = 0;
HANDLE& h = r->ovl.hEvent;
if(h != INVALID_HANDLE_VALUE)
{
CloseHandle(h);
h = INVALID_HANDLE_VALUE;
}
free(r->buf);
r->buf = 0;
}
}
// find request slot currently in use by cb
// cb = 0 => search for empty slot
Req* Reqs::find(const aiocb* cb)
{
Req* r = reqs;
for(int i = 0; i < MAX_REQS; i++, r++)
if(r->cb == cb)
return r;
return 0;
}
Req* Reqs::alloc(const aiocb* cb)
{
win_lock(WAIO_CS);
// find free request slot
Req* r = find(0);
if(!r)
{
win_unlock(WAIO_CS);
assert(0 && "Reqs::alloc: no request slot available!");
return 0;
}
r->cb = (aiocb*)cb;
win_unlock(WAIO_CS);
return r;
}
// Win32 functions require sector aligned transfers.
// max of all drives' size is checked in init().
static size_t sector_size = 4096; // minimum: one page
//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
static Reqs* reqs;
// caller ensures this is not re-entered!
static void init()
{
reqs = new Reqs;
aio_hs = new AioHandles;
// Win32 requires transfers to be sector aligned.
// find maximum of all drive's sector sizes, then use that.
// (it's good to know this up-front, and checking every open() is slow).
DWORD drives = GetLogicalDrives();
char drive_str[4] = "?:\\";
for(int drive = 2; drive <= 26; drive++) // C: .. Z:
{
// avoid BoundsChecker warning by skipping invalid drives
if(!(drives & (1ul << drive)))
continue;
drive_str[0] = 'A'+drive;
DWORD spc, nfc, tnc; // don't need these
DWORD sector_size2;
if(GetDiskFreeSpace(drive_str, &spc, &sector_size2, &nfc, &tnc))
{
if(sector_size < sector_size2)
sector_size = sector_size2;
}
// otherwise, it's probably an empty CD drive. ignore the
// BoundsChecker error; GetDiskFreeSpace seems to be the
// only way of getting at the sector size.
}
assert(is_pow2((long)sector_size));
}
// called by aio_read, aio_write, and lio_listio
// cb->aio_lio_opcode specifies desired operation
//
// if cb->aio_fildes doesn't support seeking (e.g. a socket),
// cb->aio_offset must be 0.
static int aio_rw(struct aiocb* cb)
{
if(!cb)
return -EINVAL;
if(cb->aio_lio_opcode == LIO_NOP)
return 0;
HANDLE h = aio_hs->get(cb->aio_fildes);
if(h == INVALID_HANDLE_VALUE)
{
assert(0 && "aio_rw: associated handle is invalid");
return -EINVAL;
}
Req* r = reqs->alloc(cb);
if(!r)
return -1;
size_t ofs = 0;
size_t size = cb->aio_nbytes;
void* buf = cb->aio_buf;
#define SOL_SOCKET 0xffff
#define SO_TYPE 0x1008
unsigned long opt = 0;
socklen_t optlen = sizeof(opt);
if (getsockopt((int)(intptr_t)h, SOL_SOCKET, SO_TYPE, &opt, &optlen) != -1)
// || (WSAGetLastError() != WSAENOTSOCK))
cb->aio_offset = 0;
else
{
// calculate alignment
r->pad = cb->aio_offset % sector_size; // offset to start of sector
ofs = cb->aio_offset - r->pad;
size += r->pad + sector_size-1;
size &= ~(sector_size-1); // align (sector_size = 2**n)
// not aligned
if(r->pad || (uintptr_t)buf % sector_size)
{
// current align buffer is too small - resize
if(r->buf_size < size)
{
void* buf2 = realloc(r->buf, size);
if(!buf2)
return -ENOMEM;
r->buf = buf2;
r->buf_size = size;
}
// unaligned writes are not supported -
// we'd have to read padding, then write our data. ugh.
if(cb->aio_lio_opcode == LIO_WRITE)
{
return -EINVAL;
}
buf = r->buf;
}
}
#if _MSC_VER >= 1300
r->ovl.Pointer = (void*)ofs;
#else
r->ovl.Offset = ofs;
#endif
assert(cb->aio_buf != 0);
DWORD size32 = (DWORD)(size & 0xffffffff);
ResetEvent(r->ovl.hEvent);
BOOL ok = (cb->aio_lio_opcode == LIO_READ)?
ReadFile(h, buf, size32, 0, &r->ovl) : WriteFile(h, buf, size32, 0, &r->ovl);
if(ok || GetLastError() == ERROR_IO_PENDING)
return 0;
return -1;
}
int aio_read(struct aiocb* cb)
{
cb->aio_lio_opcode = LIO_READ;
return aio_rw(cb);
}
int aio_write(struct aiocb* cb)
{
cb->aio_lio_opcode = LIO_WRITE;
return aio_rw(cb);
}
int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* se)
{
UNUSED(se)
for(int i = 0; i < n; i++)
aio_rw(cbs[i]); // aio_rw checks for 0 param
if(mode == LIO_WAIT)
aio_suspend(cbs, n, 0);
return 0;
}
// return status of transfer
int aio_error(const struct aiocb* cb)
{
Req* const r = reqs->find(cb);
if(!r)
return -1;
switch(r->ovl.Internal) // I/O status
{
case 0:
return 0;
case STATUS_PENDING:
return -EINPROGRESS;
// TODO: errors
default:
return -1;
}
}
// get bytes transferred. call exactly once for each op.
ssize_t aio_return(struct aiocb* cb)
{
Req* const r = reqs->find(cb);
if(!r)
return -1;
assert(r->ovl.Internal == 0 && "aio_return with transfer in progress");
// read wasn't aligned - need to copy to user's buffer
const size_t _buf = (char*)cb->aio_buf - (char*)0;
if(r->pad || _buf % sector_size)
memcpy(cb->aio_buf, (u8*)r->buf + r->pad, cb->aio_nbytes);
// free this request slot
r->cb = 0;
return (ssize_t)cb->aio_nbytes;
}
int aio_cancel(int fd, struct aiocb* cb)
{
UNUSED(cb)
const HANDLE h = aio_hs->get(fd);
if(h == INVALID_HANDLE_VALUE)
return -1;
// Win32 limitation: can't cancel single transfers -
// all pending reads on this file are cancelled.
CancelIo(h);
return AIO_CANCELED;
}
int aio_fsync(int, struct aiocb*)
{
return -1;
}
int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* ts)
{
int i;
if(n <= 0 || n > MAXIMUM_WAIT_OBJECTS)
return -1;
int cnt = 0; // actual number of valid cbs
HANDLE hs[MAXIMUM_WAIT_OBJECTS];
for(i = 0; i < n; i++)
{
// ignore NULL list entries
if(!cbs[i])
continue;
Req* r = reqs->find(cbs[i]);
if(r)
{
if(r->ovl.Internal == STATUS_PENDING)
hs[cnt++] = r->ovl.hEvent;
}
}
// no valid, pending transfers - done
if(!cnt)
return 0;
// timeout: convert timespec to ms (NULL ptr -> no timeout)
DWORD timeout = INFINITE;
if(ts)
timeout = (DWORD)(ts->tv_sec*1000 + ts->tv_nsec/1000000);
DWORD result = WaitForMultipleObjects(cnt, hs, 0, timeout);
for(i = 0; i < cnt; i++)
ResetEvent(hs[i]);
if(result == WAIT_TIMEOUT)
{
//errno = -EAGAIN;
return -1;
}
else
return (result == WAIT_FAILED)? -1 : 0;
}

68
source/lib/sysdep/win/waio.h Executable file
View File

@ -0,0 +1,68 @@
// POSIX asynchronous I/O for Win32
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
// included by lib.h
// Note: for maximum efficiency, transfer buffers, offsets, and lengths
// should be sector aligned (otherwise, buffer is copied).
#include "types.h"
struct aiocb
{
int aio_fildes; // File descriptor.
off_t aio_offset; // File offset.
void* aio_buf; // Location of buffer.
size_t aio_nbytes; // Length of transfer.
int aio_reqprio; // Request priority offset.
struct sigevent aio_sigevent; // Signal number and value.
int aio_lio_opcode; // Operation to be performed.
};
enum
{
// aio_cancel return
AIO_ALLDONE, // None of the requested operations could be canceled since they are already complete.
AIO_CANCELED, // All requested operations have been canceled.
AIO_NOTCANCELED, // Some of the requested operations could not be canceled since they are in progress.
// lio_listio mode
LIO_WAIT, // wait until all I/O is complete
LIO_NOWAIT,
// lio_listio ops
LIO_NOP,
LIO_READ,
LIO_WRITE
};
extern int aio_cancel(int, struct aiocb*);
extern int aio_error(const struct aiocb*);
extern int aio_fsync(int, struct aiocb*);
extern int aio_read(struct aiocb*);
extern ssize_t aio_return(struct aiocb*);
extern int aio_suspend(const struct aiocb* const[], int, const struct timespec*);
extern int aio_write(struct aiocb*);
extern int lio_listio(int, struct aiocb* const[], int, struct sigevent*);
extern int aio_close(int fd);
extern int aio_open(const char* fn, int mode, int fd);
// allocate and return a file descriptor
extern int aio_assign_handle(uintptr_t handle);

178
source/lib/sysdep/win/wdetect.cpp Executable file
View File

@ -0,0 +1,178 @@
// Windows-specific system detect
//
// Copyright (c) 2004 Jan Wassenberg
//
// This file is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This file 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <stdio.h>
#include <stdlib.h>
#include "detect.h"
#include "win_internal.h"
#ifdef _MSC_VER
#pragma comment(lib, "version.lib")
#pragma comment(lib, "advapi32.lib")
// powrprof is loaded manually - we only need 1 function.
#endif
void get_cur_resolution(int& xres, int& yres)
{
static DEVMODE dm;
dm.dmSize = sizeof(dm);
EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm);
xres = dm.dmPelsWidth;
yres = dm.dmPelsHeight;
}
int win_get_gfx_card()
{
// EnumDisplayDevices is not available on Win95 or NT
HMODULE h = LoadLibrary("user32.dll");
int (WINAPI *pEnumDisplayDevicesA)(void*, DWORD, void*, DWORD);
*(void**)&pEnumDisplayDevicesA = GetProcAddress(h, "EnumDisplayDevicesA");
if(pEnumDisplayDevicesA)
{
DISPLAY_DEVICEA dev;
dev.cb = sizeof(dev);
if(pEnumDisplayDevicesA(0, 0, &dev, 0))
{
strcpy(gfx_card, (const char*)dev.DeviceString);
return 0;
}
}
return -1;
}
// split out so we can return on failure, instead of goto
// method: http://www.opengl.org/discussion_boards/ubb/Forum3/HTML/009679.html
int win_get_gfx_drv()
{
// get driver DLL name
static DEVMODE dm; // note: dmDriverVersion is something else
dm.dmSize = sizeof(dm);
if(!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm))
return -1;
char drv_name[CCHDEVICENAME+5]; // we add ".dll"
strcpy(drv_name, (const char*)dm.dmDeviceName);
strcat(drv_name, ".dll");
// don't want to return 0 on success - we'd need to duplicate free(buf).
// instead, set this variable and return that.
int ret = -1;
// read the DLL's version info
DWORD unused;
DWORD ver_size = GetFileVersionInfoSize(drv_name, &unused);
if(!ver_size)
return -1;
void* buf = malloc(ver_size);
if(!buf)
return -1;
if(GetFileVersionInfo(drv_name, 0, ver_size, buf))
{
u16* lang; // -> 16 bit language ID, 16 bit codepage
uint lang_len;
BOOL ok = VerQueryValue(buf, "\\VarFileInfo\\Translation", (void**)&lang, &lang_len);
if(ok && lang && lang_len == 4)
{
char subblock[64];
sprintf(subblock, "\\StringFileInfo\\%04X%04X\\ProductName", lang[0], lang[1]);
const char* ver;
uint ver_len;
if(VerQueryValue(buf, subblock, (void**)&ver, &ver_len))
{
strncpy(gfx_drv, ver, sizeof(gfx_drv)-1);
ret = 0; // success
}
}
}
free(buf);
return ret;
}
int win_get_cpu_info()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
cpus = si.dwNumberOfProcessors;
HW_PROFILE_INFO hi;
GetCurrentHwProfile(&hi);
bool is_laptop = !(hi.dwDockInfo & DOCKINFO_DOCKED) ^
!(hi.dwDockInfo & DOCKINFO_UNDOCKED);
// both flags set <==> this is a desktop machine.
// both clear is unspecified; we assume it's not a laptop.
// NOTE: ! is necessary (converts expression to bool)
//
// check for speedstep
//
// CallNtPowerInformation
// (manual import because it's not supported on Win95)
NTSTATUS (WINAPI *pCNPI)(POWER_INFORMATION_LEVEL, PVOID, ULONG, PVOID, ULONG) = 0;
HMODULE hPowrProfDll = LoadLibrary("powrprof.dll");
*(void**)&pCNPI = GetProcAddress(hPowrProfDll, "CallNtPowerInformation");
if(pCNPI)
{
// most likely not speedstep-capable if these aren't supported
SYSTEM_POWER_CAPABILITIES spc;
if(pCNPI(SystemPowerCapabilities, 0, 0, &spc, sizeof(spc)) == STATUS_SUCCESS)
if(!spc.ProcessorThrottle || !spc.ThermalControl)
cpu_speedstep = 0;
// probably speedstep if cooling mode active.
// the documentation of PO_TZ_* is unclear, so we can't be sure.
SYSTEM_POWER_INFORMATION spi;
if(pCNPI(SystemPowerInformation, 0, 0, &spi, sizeof(spi)) == STATUS_SUCCESS)
if(spi.CoolingMode != PO_TZ_INVALID_MODE)
cpu_speedstep = 1;
// definitely speedstep if a CPU has thermal throttling active.
// note that we don't care about user-defined throttles
// (see ppi.CurrentMhz) - they don't change often.
ULONG ppi_buf_size = cpus * sizeof(PROCESSOR_POWER_INFORMATION);
void* ppi_buf = malloc(ppi_buf_size);
if(pCNPI(ProcessorInformation, 0, 0, ppi_buf, ppi_buf_size) == STATUS_SUCCESS)
{
PROCESSOR_POWER_INFORMATION* ppi = (PROCESSOR_POWER_INFORMATION*)ppi_buf;
for(int i = 0; i < cpus; i++)
// thermal throttling currently active
if(ppi[i].MaxMhz != ppi[i].MhzLimit)
{
cpu_speedstep = 1;
break;
}
}
free(ppi_buf);
// none of the above => don't know yet (for certain, at least).
if(cpu_speedstep == -1)
// guess speedstep active if on a laptop.
// ia32 code gets a second crack at it.
cpu_speedstep = (is_laptop)? 1 : 0;
}
FreeLibrary(hPowrProfDll);
return 0;
}

178
source/lib/sysdep/win/win.cpp Executable file
View File

@ -0,0 +1,178 @@
// Windows-specific code
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "lib.h"
#include "win_internal.h"
#include <stdlib.h> // __argc
#include <cstdio>
#include <cassert>
#include <crtdbg.h> // malloc debug
//
// these override the portable stdio versions in sysdep.cpp
// (they're more convenient)
//
void display_msg(const wchar_t* caption, const wchar_t* msg)
{
MessageBoxW(0, msg, caption, MB_ICONEXCLAMATION);
}
// fixing Win32 _vsnprintf to return # characters that would be written,
// as required by C99, looks difficult and unnecessary. if any other code
// needs that, generalize the following code into vasprintf.
void debug_out(const char* fmt, ...)
{
size_t size = 256;
void* buf = 0;
for(;;)
{
void* buf2 = realloc(buf, size);
// out of mem - free old buf and quit
if(!buf2)
goto fail;
buf = buf2;
// don't assign directly from realloc - if it fails,
// we'd leak memory.
{
va_list args;
va_start(args, fmt);
// have to re-create every time. va_copy isn't portable.
int ret = vsnprintf((char*)buf, size, fmt, args);
// success
if(ret > -1 && ret < (int)size)
break;
// return value was required buffer size (without trailing '\0')
if(ret > (int)size)
size = ret+1;
// -1: increase buffer size
else
size *= 2;
va_end(args);
}
// prevent infinite loop
if(size > 64*KB)
goto fail;
}
OutputDebugString((const char*)buf);
fail:
free(buf);
}
#ifndef NO_WINSOCK
#pragma comment(lib, "ws2_32.lib")
#endif
// locking for win-specific code
// several init functions are called on-demand, possibly from constructors.
// can't guarantee POSIX static mutex init has been done by then.
static CRITICAL_SECTION cs[NUM_CS];
void win_lock(uint idx)
{
assert(idx < NUM_CS && "win_lock: invalid critical section index");
EnterCriticalSection(&cs[idx]);
}
void win_unlock(uint idx)
{
assert(idx < NUM_CS && "win_unlock: invalid critical section index");
LeaveCriticalSection(&cs[idx]);
}
// entry -> pre_libc -> WinMainCRTStartup -> WinMain -> pre_main -> main
// at_exit is called as the last of the atexit handlers
// (assuming, as documented in lib.cpp, constructors don't use atexit!)
//
// note: this way of getting control before main adds overhead
// (setting up the WinMain parameters), but is simpler and safer than
// SDL-style #define main SDL_main.
static void at_exit(void)
{
for(int i = 0; i < NUM_CS; i++)
DeleteCriticalSection(&cs[i]);
}
// be very careful to avoid non-stateless libc functions!
static inline void pre_libc_init()
{
#ifndef NO_WINSOCK
char d[1024];
WSAStartup(0x0002, d); // want 2.0
atexit2(WSACleanup, 0, CC_STDCALL_0);
#endif
for(int i = 0; i < NUM_CS; i++)
InitializeCriticalSection(&cs[i]);
}
static inline void pre_main_init()
{
#ifdef PARANOIA
// force malloc et al to check the heap every call.
// slower, but reports errors closer to where they occur.
int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
flags |= _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_DELAY_FREE_MEM_DF;
_CrtSetDbgFlag(flags);
#endif
atexit(at_exit);
// SDL will do this as well. no matter.
// ignore BoundsChecker warning here.
freopen("stdout.txt", "wt", stdout);
}
int entry()
{
pre_libc_init();
return WinMainCRTStartup(); // calls _cinit, and then WinMain
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
pre_main_init();
return main(__argc, __argv);
}

19
source/lib/sysdep/win/win.h Executable file
View File

@ -0,0 +1,19 @@
#if !defined(__WIN_H__) && defined(_WIN32)
#define __WIN_H__
// C99
#define snprintf _snprintf
#define snwprintf _snwprintf
#define vsnprintf _vsnprintf
#include <stddef.h> // wchar_t
// libpng.h -> zlib.h -> zconf.h includes <windows.h>, which causes conflicts.
// inhibit this, and define what they actually need from windows.h
// incidentally, this requires all windows dependencies to include
// sysdep/win_internal.h
#define _WINDOWS_ // windows.h include guard
#define WINAPI __stdcall
#define WINAPIV __cdecl
#endif // #ifndef __WIN_H__

View File

@ -0,0 +1,272 @@
#ifndef _WIN32
#error "including win_internal.h without _WIN32 defined"
#endif
#ifndef WIN_INTERNAL_H
#define WIN_INTERNAL_H
// Win32 socket decls aren't portable (e.g. problems with socklen_t)
// => skip winsock.h; lib.h should be used instead
#define _WINSOCKAPI_
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
// the public header, win.h, has defined _WINDOWS_ so that
// other code doesn't include <windows.h> when it shouldn't (e.g. zconf.h)
#undef _WINDOWS_
// set version; needed for EnumDisplayDevices
#define _WIN32_WINNT 0x0500
#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_
//#define NOVIRTUALKEYCODES // VK_*
//#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_*
//#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_*
#define NOSYSMETRICS // SM_*
#define NOMENUS // MF_*
#define NOICONS // IDI_*
#define NOKEYSTATES // MK_*
//#define NOSYSCOMMANDS // SC_*
#define NORASTEROPS // Binary and Tertiary raster ops
//#define NOSHOWWINDOW // SW_*
#define OEMRESOURCE // OEM Resource values
#define NOATOM // Atom Manager routines
//#define NOCLIPBOARD // Clipboard routines
#define NOCOLOR // Screen colors
#define NOCTLMGR // Control and Dialog routines
#define NODRAWTEXT // DrawText() and DT_*
//#define NOGDI // All GDI defines and routines
//#define NOKERNEL // All KERNEL defines and routines
//#define NOUSER // All USER defines and routines
#define NONLS // All NLS defines and routines
//#define NOMB // MB_* and MessageBox()
#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines
#define NOMETAFILE // typedef METAFILEPICT
#define NOMINMAX // Macros min(a,b) and max(a,b)
//#define NOMSG // typedef MSG and associated routines
#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_*
#define NOSCROLL // SB_* and scrolling routines
#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc.
//#define NOSOUND // Sound driver routines
#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines
//#define NOWH // SetWindowsHook and WH_*
#define NOWINOFFSETS // GWL_*, GCL_*, associated routines
//#define NOCOMM // COMM driver routines
#define NOKANJI // Kanji support stuff.
#define NOHELP // Help engine interface.
#define NOPROFILER // Profiler interface.
#define NODEFERWINDOWPOS // DeferWindowPos routines
#define NOMCX // Modem Configuration Extensions
#include <windows.h>
// VC6 windows.h doesn't define these
#ifndef DWORD_PTR
#define DWORD_PTR DWORD
#endif
#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
#ifndef PROCESSOR_ARCHITECTURE_AMD64
#define PROCESSOR_ARCHITECTURE_AMD64 9
#endif
// end VC6 fixes
// powrprof.h (not there at all in VC6, missing some parts in VC7)
#ifndef NTSTATUS
#define NTSTATUS long
#endif
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0
#endif
#if WINVER < 0x500
typedef enum {
SystemPowerPolicyAc,
SystemPowerPolicyDc,
VerifySystemPolicyAc,
VerifySystemPolicyDc,
SystemPowerCapabilities,
SystemBatteryState,
SystemPowerStateHandler,
ProcessorStateHandler,
SystemPowerPolicyCurrent,
AdministratorPowerPolicy,
SystemReserveHiberFile,
ProcessorInformation,
SystemPowerInformation,
ProcessorStateHandler2,
LastWakeTime, // Compare with KeQueryInterruptTime()
LastSleepTime, // Compare with KeQueryInterruptTime()
SystemExecutionState,
SystemPowerStateNotifyHandler,
ProcessorPowerPolicyAc,
ProcessorPowerPolicyDc,
VerifyProcessorPowerPolicyAc,
VerifyProcessorPowerPolicyDc,
ProcessorPowerPolicyCurrent,
SystemPowerStateLogging,
SystemPowerLoggingEntry
} POWER_INFORMATION_LEVEL;
typedef struct {
DWORD Granularity;
DWORD Capacity;
} BATTERY_REPORTING_SCALE, *PBATTERY_REPORTING_SCALE;
typedef enum _SYSTEM_POWER_STATE {
PowerSystemUnspecified = 0,
PowerSystemWorking = 1,
PowerSystemSleeping1 = 2,
PowerSystemSleeping2 = 3,
PowerSystemSleeping3 = 4,
PowerSystemHibernate = 5,
PowerSystemShutdown = 6,
PowerSystemMaximum = 7
} SYSTEM_POWER_STATE, *PSYSTEM_POWER_STATE;
typedef struct {
// Misc supported system features
BOOLEAN PowerButtonPresent;
BOOLEAN SleepButtonPresent;
BOOLEAN LidPresent;
BOOLEAN SystemS1;
BOOLEAN SystemS2;
BOOLEAN SystemS3;
BOOLEAN SystemS4; // hibernate
BOOLEAN SystemS5; // off
BOOLEAN HiberFilePresent;
BOOLEAN FullWake;
BOOLEAN VideoDimPresent;
BOOLEAN ApmPresent;
BOOLEAN UpsPresent;
// Processors
BOOLEAN ThermalControl;
BOOLEAN ProcessorThrottle;
BYTE ProcessorMinThrottle;
BYTE ProcessorMaxThrottle;
BYTE spare2[4];
// Disk
BOOLEAN DiskSpinDown;
BYTE spare3[8];
// System Battery
BOOLEAN SystemBatteriesPresent;
BOOLEAN BatteriesAreShortTerm;
BATTERY_REPORTING_SCALE BatteryScale[3];
// Wake
SYSTEM_POWER_STATE AcOnLineWake;
SYSTEM_POWER_STATE SoftLidWake;
SYSTEM_POWER_STATE RtcWake;
SYSTEM_POWER_STATE MinDeviceWakeState; // note this may change on driver load
SYSTEM_POWER_STATE DefaultLowLatencyWake;
} SYSTEM_POWER_CAPABILITIES, *PSYSTEM_POWER_CAPABILITIES;
#endif // WINVER < 0x500
typedef struct _PROCESSOR_POWER_INFORMATION
{
ULONG Number;
ULONG MaxMhz;
ULONG CurrentMhz;
ULONG MhzLimit;
ULONG MaxIdleState;
ULONG CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
typedef struct _SYSTEM_POWER_INFORMATION
{
ULONG MaxIdlenessAllowed;
ULONG Idleness;
ULONG TimeRemaining;
UCHAR CoolingMode;
} SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION;
// SPI.CoolingMode
#define PO_TZ_INVALID_MODE 0 // The system does not support CPU throttling,
// or there is no thermal zone defined [..]
// end powrprof.h fixes
#include "types.h" // intptr_t
extern "C" {
extern intptr_t _get_osfhandle(int);
extern int _open_osfhandle(intptr_t, int);
extern int _open(const char* fn, int mode, ...);
extern int _close(int);
#ifndef NO_WINSOCK
extern __declspec(dllimport) int __stdcall WSAStartup(unsigned short, void*);
extern __declspec(dllimport) int __stdcall WSACleanup();
extern __declspec(dllimport) int __stdcall WSAAsyncSelect(int s, HANDLE hWnd, unsigned int wMsg, long lEvent);
extern __declspec(dllimport) int __stdcall WSAGetLastError();
#endif // #ifndef NO_WINSOCK
extern int WinMainCRTStartup();
extern int main(int, char*[]);
}
#define BIT(n) (1ul << (n))
#define FD_READ BIT(0)
#define FD_WRITE BIT(1)
#define FD_ACCEPT BIT(3)
#define FD_CONNECT BIT(4)
#define FD_CLOSE BIT(5)
#include "types.h"
//
// locking
//
// critical sections used by win-specific code
enum
{
ONCE_CS,
HRT_CS,
WAIO_CS,
NUM_CS
};
extern void win_lock(uint idx);
extern void win_unlock(uint idx);
// thread safe, useable in constructors
#define WIN_ONCE(code) \
{ \
win_lock(ONCE_CS); \
static bool ONCE_init_; /* avoid name conflict */ \
if(!ONCE_init_) \
{ \
ONCE_init_ = true; \
code; \
} \
win_unlock(ONCE_CS); \
}
#endif // #ifndef WIN_INTERNAL_H

677
source/lib/sysdep/win/wposix.cpp Executable file
View File

@ -0,0 +1,677 @@
// misc. POSIX routines for Win32
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
// collection of hacks :P
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <process.h>
#include "lib.h"
#include "win_internal.h"
#include "hrt.h"
//////////////////////////////////////////////////////////////////////////////
//
// file
//
//////////////////////////////////////////////////////////////////////////////
/*
extern int aio_open(const char*, int, int);
extern int aio_close(int);
*/
int open(const char* fn, int mode, ...)
{
// /dev/tty? => COM?
if(!strncmp(fn, "/dev/tty", 8))
{
static char port[] = "COM ";
port[3] = (char)(fn[8]+1);
fn = port;
}
int fd = _open(fn, mode);
// open it for async I/O as well (_open defaults to deny_none sharing)
if(fd > 2)
aio_open(fn, mode, fd);
return fd;
}
int close(int fd)
{
aio_close(fd);
return _close(fd);
}
int ioctl(int fd, int op, int* data)
{
HANDLE h = (HANDLE)((char*)0 + _get_osfhandle(fd));
switch(op)
{
case TIOCMGET:
/* TIOCM_* mapped directly to MS_*_ON */
GetCommModemStatus(h, (DWORD*)data);
break;
case TIOCMBIS:
/* only RTS supported */
if(*data & TIOCM_RTS)
EscapeCommFunction(h, SETRTS);
else
EscapeCommFunction(h, CLRRTS);
break;
case TIOCMIWAIT:
static DWORD mask;
DWORD new_mask = 0;
if(*data & TIOCM_CD)
new_mask |= EV_RLSD;
if(*data & TIOCM_CTS)
new_mask |= EV_CTS;
if(new_mask != mask)
SetCommMask(h, mask = new_mask);
WaitCommEvent(h, &mask, 0);
break;
}
return 0;
}
// currently only sets st_mode (file or dir) and st_size.
int stat(const char* fn, struct stat* s)
{
memset(s, 0, sizeof(struct stat));
WIN32_FILE_ATTRIBUTE_DATA fad;
if(!GetFileAttributesEx(fn, GetFileExInfoStandard, &fad))
return -1;
// dir
if(fad.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
s->st_mode = S_IFDIR;
else
{
s->st_mode = S_IFREG;
s->st_size = (off_t)((((u64)fad.nFileSizeHigh) << 32) | fad.nFileSizeLow);
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////
//
// dir
//
//////////////////////////////////////////////////////////////////////////////
char* realpath(const char* fn, char* path)
{
if(!GetFullPathName(fn, PATH_MAX, path, 0))
return 0;
return path;
}
int mkdir(const char* path, mode_t)
{
return CreateDirectory(path, 0)? 0 : -1;
}
struct _DIR
{
WIN32_FIND_DATA fd;
HANDLE handle;
struct dirent ent; // must not be overwritten by calls for different dirs
bool not_first;
};
DIR* opendir(const char* name)
{
DWORD fa = GetFileAttributes(name);
if(fa == INVALID_FILE_ATTRIBUTES || !(fa & FILE_ATTRIBUTE_DIRECTORY))
return 0;
size_t size = round_up(sizeof(_DIR), 32);
// be nice to allocator.
_DIR* d = (_DIR*)calloc(size, 1);
char path[MAX_PATH+1];
strncpy(path, name, MAX_PATH-2);
strcat(path, "\\*");
d->handle = FindFirstFile(path, &d->fd);
return d;
}
struct dirent* readdir(DIR* dir)
{
_DIR* d = (_DIR*)dir;
if(d->not_first)
if(!FindNextFile(d->handle, &d->fd))
return 0;
d->not_first = true;
d->ent.d_ino = 0;
d->ent.d_name = &d->fd.cFileName[0];
return &d->ent;
}
int closedir(DIR* dir)
{
_DIR* d = (_DIR*)dir;
FindClose(d->handle);
free(dir);
return 0;
}
//////////////////////////////////////////////////////////////////////////////
//
// terminal
//
//////////////////////////////////////////////////////////////////////////////
static HANDLE std_h[2] = { (HANDLE)(((char*)0) + 3), (HANDLE)(((char*)0) + 7) };
__declspec(naked) void _get_console()
{ __asm jmp dword ptr [AllocConsole] }
__declspec(naked) void _hide_console()
{ __asm jmp dword ptr [FreeConsole] }
int tcgetattr(int fd, struct termios* termios_p)
{
if(fd > 2)
return -1;
HANDLE h = std_h[fd];
DWORD mode;
GetConsoleMode(h, &mode);
termios_p->c_lflag = mode & (ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT);
return 0;
}
int tcsetattr(int fd, int /* optional_actions */, const struct termios* termios_p)
{
if(fd > 2)
return -1;
HANDLE h = std_h[fd];
SetConsoleMode(h, (DWORD)termios_p->c_lflag);
FlushConsoleInputBuffer(h);
return 0;
}
int poll(struct pollfd /* fds */[], int /* nfds */, int /* timeout */)
{
return -1;
}
//////////////////////////////////////////////////////////////////////////////
//
// thread
//
//////////////////////////////////////////////////////////////////////////////
__declspec(naked) pthread_t pthread_self(void)
{ __asm jmp dword ptr [GetCurrentThread] }
int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param)
{
if(policy)
{
DWORD pc = GetPriorityClass(GetCurrentProcess());
*policy = (pc >= HIGH_PRIORITY_CLASS)? SCHED_FIFO : SCHED_RR;
}
if(param)
{
HANDLE hThread = (HANDLE)((char*)0 + thread);
param->sched_priority = GetThreadPriority(hThread);
}
return 0;
}
int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param)
{
if(policy == SCHED_FIFO)
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
HANDLE hThread = (HANDLE)((char*)0 + thread);
SetThreadPriority(hThread, param->sched_priority);
return 0;
}
int pthread_create(pthread_t* /* thread */, const void* /* attr */, void*(*func)(void*), void* arg)
{
/* can't use asm 'cause _beginthread might be a func ptr (with libc) */
return (int)_beginthread((void(*)(void*))func, 0, arg);
}
pthread_mutex_t pthread_mutex_initializer()
{
return CreateMutex(0, 0, 0);
}
int pthread_mutex_init(pthread_mutex_t* m, const pthread_mutexattr_t*)
{
if(!m)
return -1;
*m = pthread_mutex_initializer();
return 0;
}
int pthread_mutex_lock(pthread_mutex_t* m)
{
return WaitForSingleObject(*m, INFINITE) == WAIT_OBJECT_0? 0 : -1;
}
int pthread_mutex_trylock(pthread_mutex_t* m)
{
return WaitForSingleObject(*m, 0) == WAIT_OBJECT_0? 0 : -1;
}
int pthread_mutex_unlock(pthread_mutex_t* m)
{
return ReleaseMutex(*m)? 0 : -1;
}
int pthread_mutex_timedlock(pthread_mutex_t* m, const struct timespec* abs_timeout)
{
DWORD ms_timeout = 0;
if(abs_timeout)
{
struct timespec cur_ts;
clock_gettime(CLOCK_REALTIME, &cur_ts);
ms_timeout = (cur_ts.tv_sec - abs_timeout->tv_sec ) * 1000 +
(cur_ts.tv_nsec - abs_timeout->tv_nsec) / 1000000;
}
return WaitForSingleObject(*m, ms_timeout) == WAIT_OBJECT_0? 0 : -1;
}
//////////////////////////////////////////////////////////////////////////////
//
// memory mapping
//
//////////////////////////////////////////////////////////////////////////////
void* mmap(void* start, unsigned int len, int prot, int flags, int fd, long offset)
{
if(!(flags & MAP_FIXED))
start = 0;
/* interpret protection/mapping flags. */
SECURITY_ATTRIBUTES sec = { sizeof(SECURITY_ATTRIBUTES), 0, 0 };
DWORD flProtect = PAGE_READONLY; /* mapping object permission */
DWORD dwAccess = FILE_MAP_READ; /* file map access permission */
if(prot & PROT_WRITE)
{
flProtect = PAGE_READWRITE;
/* changes are shared & written to file */
if(flags & MAP_SHARED)
{
sec.bInheritHandle = 1;
dwAccess = FILE_MAP_ALL_ACCESS;
}
/* private copy on write mapping */
else if(flags & MAP_PRIVATE)
{
flProtect = PAGE_WRITECOPY;
dwAccess = FILE_MAP_COPY;
}
}
DWORD len_hi = (DWORD)((u64)len >> 32), len_lo = (DWORD)len & 0xffffffff;
HANDLE hFile = (HANDLE)((char*)0 + _get_osfhandle(fd));
HANDLE hMap = CreateFileMapping(hFile, &sec, flProtect, len_hi, len_lo, 0);
void* ptr = MapViewOfFileEx(hMap, dwAccess, len_hi, offset, len_lo, start);
/* file mapping object will be freed when ptr is unmapped */
CloseHandle(hMap);
if(!ptr || (flags & MAP_FIXED && ptr != start))
return MAP_FAILED;
return ptr;
}
int munmap(void* start, unsigned int /* len */)
{
return UnmapViewOfFile(start) - 1; /* 0: success; -1: fail */
}
//////////////////////////////////////////////////////////////////////////////
//
// time
//
//////////////////////////////////////////////////////////////////////////////
static const long _1e6 = 1000000;
static const i64 _1e9 = 1000000000;
// return nanoseconds since posix epoch
// currently only 10 or 15 ms resolution! use HRT for finer timing
static i64 st_time_ns()
{
union
{
FILETIME ft;
i64 i;
}
t;
GetSystemTimeAsFileTime(&t.ft);
// Windows system time is hectonanoseconds since Jan. 1, 1601
return (t.i - 0x019DB1DED53E8000) * 100;
}
static inline long st_res_ns()
{
DWORD adjust, interval;
BOOL adj_disabled;
GetSystemTimeAdjustment(&adjust, &interval, &adj_disabled);
return interval * 100; // hns -> ns
}
static void sleep_ns(i64 ns)
{
DWORD ms = DWORD(ns / _1e6);
if(ms != 0)
Sleep(ms);
else
{
i64 t0 = hrt_ticks(), t1;
do
t1 = hrt_ticks();
while(hrt_delta_s(t0, t1) * _1e9 < ns);
}
}
int clock_gettime(clockid_t clock, struct timespec* t)
{
#ifndef NDEBUG
if(clock != CLOCK_REALTIME || !t)
{
assert(0 && "clock_gettime: invalid clock or t param");
return -1;
}
#endif
const i64 ns = st_time_ns();
t->tv_sec = (time_t)(ns / _1e9);
t->tv_nsec = (long) (ns % _1e9);
return 0;
}
int clock_getres(clockid_t clock, struct timespec* res)
{
#ifndef NDEBUG
if(clock != CLOCK_REALTIME || !res)
{
assert(0 && "clock_getres: invalid clock or res param");
return -1;
}
#endif
res->tv_sec = 0;
res->tv_nsec = st_res_ns();;
return 0;
}
int nanosleep(const struct timespec* rqtp, struct timespec* /* rmtp */)
{
i64 ns = rqtp->tv_sec; // make sure we don't overflow
ns *= _1e9;
ns += rqtp->tv_nsec;
sleep_ns(ns);
return 0;
}
int gettimeofday(struct timeval* tv, void* tzp)
{
#ifndef NDEBUG
if(!tv)
{
assert(0 && "gettimeofday: invalid t param");
return -1;
}
#endif
const i64 us = st_time_ns() / 1000;
tv->tv_sec = (time_t) (us / _1e6);
tv->tv_usec = (suseconds_t)(us % _1e6);
return 0;
}
uint sleep(uint sec)
{
Sleep(sec * 1000);
return sec;
}
int usleep(useconds_t us)
{
// can't overflow, because us < 1e6
sleep_ns(us * 1000);
return 0;
}
int uname(struct utsname* un)
{
if(!un)
return -1;
static OSVERSIONINFO vi;
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&vi);
// OS implementation name
const char* family = "??";
int ver = (vi.dwMajorVersion << 8) | vi.dwMinorVersion;
if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
family = (ver == 0x045a)? "ME" : "9x";
if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
{
if(ver == 0x0500)
family = "2k";
else if(ver == 0x0501)
family = "XP";
else
family = "NT";
}
sprintf(un->sysname, "Win%s", family);
// release info
const char* vs = vi.szCSDVersion;
int sp;
if(sscanf(vs, "Service Pack %d", &sp) == 1)
sprintf(un->release, "SP %d", sp);
else
{
const char* release = "";
if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
if(!strcmp(vs, " C"))
release = "OSR2";
else if(!strcmp(vs, " A"))
release = "SE";
}
strcpy(un->release, release);
}
// version
sprintf(un->version, "%lu.%02lu.%lu", vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber & 0xffff);
// node name
DWORD buf_size = sizeof(un->nodename);
GetComputerName(un->nodename, &buf_size);
// hardware type
static SYSTEM_INFO si;
GetSystemInfo(&si);
if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
strcpy(un->machine, "AMD64");
else
strcpy(un->machine, "IA-32");
return 0;
}
long sysconf(int name)
{
// used by _SC_*_PAGES
static DWORD page_size;
switch(name)
{
case _SC_PAGESIZE:
if(page_size)
return page_size;
SYSTEM_INFO si;
GetSystemInfo(&si); // can't fail => page_size always > 0.
return page_size = si.dwPageSize;
case _SC_PHYS_PAGES:
case _SC_AVPHYS_PAGES:
if(!page_size)
sysconf(_SC_PAGESIZE); // sets page_size
MEMORYSTATUS ms;
GlobalMemoryStatus(&ms);
if(name == _SC_PHYS_PAGES)
return (long)(round_up(ms.dwTotalPhys, 2*MB) / page_size);
// this is 528 KB less than it should be on Win2k and WinXP.
// is it DOS low mem? mem command shows more free, though.
else
return (long)(ms.dwAvailPhys / page_size);
default:
return -1;
}
}
u16_t htons(u16_t s)
{
return (s >> 8) | ((s & 0xff) << 8);
}
/******************************************************************/
/* socket dynamic functions */
fp_getnameinfo_t getnameinfo;
fp_getaddrinfo_t getaddrinfo;
fp_freeaddrinfo_t freeaddrinfo;
/* IPv6 globals
These are included in the linux C libraries, and in newer platform SDK's, so
should only be needed in VC++6 or earlier.
*/
#if _MSC_VER <= 1200 /* VC++6 or earlier */
const struct in6_addr in6addr_any=IN6ADDR_ANY_INIT; /* :: */
const struct in6_addr in6addr_loopback=IN6ADDR_LOOPBACK_INIT; /* ::1 */
#endif
/*
void entry(void)
{
// note: winsock header is also removed by this define
#ifndef NO_WINSOCK
char d[1024];
WSAStartup(0x0002, d); // want 2.0
#endif
HMODULE h=LoadLibrary("ws2_32.dll");
if (h)
{
getaddrinfo=(fp_getaddrinfo_t)GetProcAddress(h, "getaddrinfo");
getnameinfo=(fp_getnameinfo_t)GetProcAddress(h, "getnameinfo");
freeaddrinfo=(fp_freeaddrinfo_t)GetProcAddress(h, "freeaddrinfo");
}
else
{
getaddrinfo=NULL;
getnameinfo=NULL;
freeaddrinfo=NULL;
}
mainCRTStartup();
}
*/

602
source/lib/sysdep/win/wposix.h Executable file
View File

@ -0,0 +1,602 @@
// misc. POSIX routines for Win32
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef __WPOSIX_H__
#define __WPOSIX_H__
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define IMP(ret, name, param) extern "C" __declspec(dllimport) ret __stdcall name param;
//
// <inttypes.h>
//
typedef unsigned short u16_t;
//
// <sys/types.h>
//
typedef unsigned long useconds_t;
typedef long suseconds_t;
typedef long ssize_t;
//
// <limits.h>
//
#define PATH_MAX 260
//
// <errno.h>
//
#define EINPROGRESS 100000
#include <errno.h>
/*
enum
{
EINPROGRESS = 1000, // Operation in progress.
ENOMEM // Not enough space.
EACCES, // Permission denied.
EADDRINUSE, // Address in use.
EADDRNOTAVAIL, // Address not available.
EAGAIN, // Resource unavailable, try again (may be the same value as EWOULDBLOCK]).
EALREADY, // Connection already in progress.
EBADF, // Bad file descriptor.
ECANCELED, // Operation canceled.
ECONNABORTED, // Connection aborted.
ECONNREFUSED, // Connection refused.
ECONNRESET, // Connection reset.
EDOM, // Mathematics argument out of domain of function.
EEXIST, // File exists.
EFAULT, // Bad address.
EHOSTUNREACH, // Host is unreachable.
EINTR, // Interrupted function.
EINVAL, // Invalid argument.
EISCONN, // Socket is connected.
EISDIR, // Is a directory.
ENAMETOOLONG, // Filename too long.
ENETDOWN, // Network is down.
ENETRESET, // Connection aborted by network.
ENETUNREACH, // Network unreachable.
ENOENT, // No such file or directory.
ENOEXEC, // Executable file format error.
ENOSPC, // No space left on device.
ENOSYS, // Function not supported.
ENOTCONN, // The socket is not connected.
ENOTDIR, // Not a directory.
ENOTEMPTY, // Directory not empty.
ENOTSOCK, // Not a socket.
ENOTSUP, // Not supported.
EOVERFLOW, // Value too large to be stored in data type.
EPERM, // Operation not permitted.
EPIPE, // Broken pipe.
EPROTO, // Protocol error.
ERANGE, // Result too large.
ETIMEDOUT, // Connection timed out.
EWOULDBLOCK // Operation would block (may be the same value as EAGAIN]).
};
*/
//
// sys/stat.h
//
typedef unsigned int mode_t;
// VC libc includes stat, but it's quite slow.
// we implement our own, but use the CRT struct definition.
#include <sys/stat.h>
extern int mkdir(const char*, mode_t);
// currently only sets st_mode (file or dir) and st_size.
extern int stat(const char*, struct stat*);
//
// dirent.h
//
typedef void DIR;
struct dirent
{
ino_t d_ino;
char* d_name;
};
extern DIR* opendir(const char* name);
extern struct dirent* readdir(DIR*);
extern int closedir(DIR*);
//
// <sys/mman.h>
//
#define PROT_READ 0x01 // page can be read
#define PROT_WRITE 0x02 // page can be written
#define MAP_SHARED 0x01 // share changes across processes
#define MAP_PRIVATE 0x02 // private copy on write mapping
#define MAP_FIXED 0x04
#define MAP_FAILED 0
extern void* mmap(void* start, unsigned int len, int prot, int flags, int fd, long offset);
extern int munmap(void* start, unsigned int len);
//
// <fcntl.h>
//
// values from MS _open - do not change!
#define O_RDONLY 0x0000 // open for reading only
#define O_WRONLY 0x0001 // open for writing only
#define O_RDWR 0x0002 // open for reading and writing
#define O_APPEND 0x0008 // writes done at eof
#define O_CREAT 0x0100 // create and open file
#define O_TRUNC 0x0200 // open and truncate
#define O_EXCL 0x0400 // open only if file doesn't already exist
#define O_BINARY 0x8000 // file mode is binary (untranslated)
#define O_NONBLOCK 0x1000000
extern int open(const char* fn, int mode, ...);
//
// <unistd.h>
//
#define F_OK 0
#define R_OK 1
#define W_OK 2
#define X_OK 4
#define read _read
#define write _write
extern int close(int);
extern int access(const char*, int);
extern int chdir(const char*);
extern unsigned int sleep(unsigned int sec);
extern int usleep(useconds_t us);
// user tests if available via #ifdef; can't use enum.
#define _SC_PAGESIZE 1
#define _SC_PAGE_SIZE 1
#define _SC_PHYS_PAGES 2
#define _SC_AVPHYS_PAGES 3
extern long sysconf(int name);
#ifndef _WINSOCKAPI_
IMP(int, gethostname, (char* name, size_t namelen))
#endif
//
// <stdlib.h>
//
extern char* realpath(const char*, char*);
//
// <signal.h>
//
union sigval
{
int sival_int; // Integer signal value.
void* sival_ptr; // Pointer signal value.
};
struct sigevent
{
int sigev_notify; // notification mode
int sigev_signo; // signal number
union sigval sigev_value; // signal value
void(*sigev_notify_function)(union sigval);
};
//
// <termios.h>
//
#define TCSANOW 0
struct termios
{
long c_lflag;
};
#define ICANON 2 // do not change - correspond to ENABLE_LINE_INPUT / ENABLE_ECHO_INPUT
#define ECHO 4
extern int tcgetattr(int fd, struct termios* termios_p);
extern int tcsetattr(int fd, int optional_actions, const struct termios* termios_p);
//
// <sched.h>
//
struct sched_param
{
int sched_priority;
};
enum
{
SCHED_RR,
SCHED_FIFO,
SCHED_OTHER
};
#define sched_get_priority_max(policy) 15 // TIME_CRITICAL
#define sched_get_priority_min(policy) -15 // IDLE
//
// <pthread.h>
//
typedef unsigned int pthread_t;
extern pthread_t pthread_self();
extern int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param);
extern int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param);
extern int pthread_create(pthread_t* thread, const void* attr, void*(*func)(void*), void* arg);
typedef void* pthread_mutex_t;
typedef void pthread_mutexattr_t;
extern pthread_mutex_t pthread_mutex_initializer();
#define PTHREAD_MUTEX_INITIALIZER pthread_mutex_initializer()
extern int pthread_mutex_init(pthread_mutex_t*, const pthread_mutexattr_t*);
extern int pthread_mutex_destroy(pthread_mutex_t*);
extern int pthread_mutex_lock(pthread_mutex_t*);
extern int pthread_mutex_trylock(pthread_mutex_t*);
extern int pthread_mutex_unlock(pthread_mutex_t*);
extern int pthread_mutex_timedlock(pthread_mutex_t*, const struct timespec*);
//
// <time.h>
//
typedef long time_t;
typedef enum
{
CLOCK_REALTIME
}
clockid_t;
// BSD gettimeofday
struct timeval
{
time_t tv_sec;
suseconds_t tv_usec;
};
// POSIX realtime clock_*
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
extern time_t time(time_t*);
extern int gettimeofday(struct timeval* tv, void* tzp);
extern int nanosleep(const struct timespec* rqtp, struct timespec* rmtp);
extern int clock_gettime(clockid_t clock, struct timespec* ts);
extern int clock_getres(clockid_t clock, struct timespec* res);
//
// <sys/socket.h>
//
typedef unsigned long socklen_t;
typedef unsigned short sa_family_t;
// Win32 values - do not change
#ifndef _WINSOCKAPI_
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define AF_INET 2
#define PF_INET AF_INET
#define AF_INET6 23
#define PF_INET6 AF_INET6
#define SOL_SOCKET 0xffff /* options for socket level */
#define TCP_NODELAY 0x0001
/* This is the slightly unreadable encoded form of the windows ioctl that sets
non-blocking mode for a socket */
#define FIONBIO 0x8004667E
enum {
SHUT_RD=0,
SHUT_WR=1,
SHUT_RDWR=2
};
struct sockaddr;
IMP(int, socket, (int, int, int))
IMP(int, setsockopt, (int, int, int, const void*, socklen_t))
IMP(int, getsockopt, (int, int, int, void*, socklen_t*))
IMP(int, ioctlsocket, (int, int, const void *))
IMP(int, shutdown, (int, int))
IMP(int, closesocket, (int))
//
// <netinet/in.h>
//
typedef unsigned long in_addr_t;
typedef unsigned short in_port_t;
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
#define INET_ADDRSTRLEN 16
#define INADDR_ANY 0
#define INADDR_NONE ((in_addr_t)-1)
#define IPPROTO_IP 0
#define IP_ADD_MEMBERSHIP 5
#define IP_DROP_MEMBERSHIP 6
struct ip_mreq
{
struct in_addr imr_multiaddr; /* multicast group to join */
struct in_addr imr_interface; /* interface to join on */
};
// ==== IPv6 ====
extern const struct in6_addr in6addr_any; /* :: */
extern const struct in6_addr in6addr_loopback; /* ::1 */
#define IN6ADDR_ANY_INIT { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } }
#define IN6ADDR_LOOPBACK_INIT { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } }
// struct of array => 2 braces.
struct in6_addr
{
unsigned char s6_addr[16];
};
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* Transport level port number */
unsigned long sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
unsigned long sin6_scope_id; /* set of interfaces for a scope */
};
//
// <netdb.h>
//
struct hostent
{
char* h_name; // Official name of the host.
char** h_aliases; // A pointer to an array of pointers to
// alternative host names, terminated by a
// null pointer.
short h_addrtype; // Address type.
short h_length; // The length, in bytes, of the address.
char** h_addr_list; // A pointer to an array of pointers to network
// addresses (in network byte order) for the host,
// terminated by a null pointer.
};
IMP(struct hostent*, gethostbyname, (const char *name))
#define h_error WSAGetLastError()
#define HOST_NOT_FOUND 11001
#define TRY_AGAIN 11002
// addrinfo struct */
struct addrinfo
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST
int ai_family; // PF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
size_t ai_addrlen; // Length of ai_addr
char *ai_canonname; // Canonical name for nodename
struct sockaddr *ai_addr; // Binary address
struct addrinfo *ai_next; // Next structure in linked list
};
// Hint flags for getaddrinfo
#define AI_PASSIVE 0x1 // Socket address will be used in bind() call
// Flags for getnameinfo()
#define NI_NUMERICHOST 0x02 // Return numeric form of the host's address
#define NI_MAXHOST 1025
#define NI_MAXSERV 32
/* Note that these are function pointers. They will be initialized by the
entry point function in posix.cpp */
typedef int (*fp_getnameinfo_t)(const struct sockaddr *sa, socklen_t salen, char *node,
socklen_t nodelen, char *serv, socklen_t servlen, unsigned int flags);
typedef int (*fp_getaddrinfo_t)(const char *nodename, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
typedef void (*fp_freeaddrinfo_t)(struct addrinfo *ai);
extern fp_getnameinfo_t p_getnameinfo;
extern fp_getaddrinfo_t p_getaddrinfo;
extern fp_freeaddrinfo_t p_freeaddrinfo;
#define getnameinfo p_getnameinfo
#define getaddrinfo p_getaddrinfo
#define freeaddrinfo p_freeaddrinfo
// getaddr/nameinfo error codes
#define EAI_NONAME HOST_NOT_FOUND
//
// <arpa/inet.h>
//
extern u16_t htons(u16_t hostshort);
#define ntohs htons
IMP(unsigned long, htonl, (unsigned long hostlong))
IMP(in_addr_t, inet_addr, (const char*))
IMP(char*, inet_ntoa, (in_addr))
IMP(int, accept, (int, struct sockaddr*, socklen_t*))
IMP(int, bind, (int, const struct sockaddr*, socklen_t))
IMP(int, connect, (int, const struct sockaddr*, socklen_t))
IMP(int, listen, (int, int))
IMP(ssize_t, recv, (int, void*, size_t, int))
IMP(ssize_t, send, (int, const void*, size_t, int))
IMP(ssize_t, sendto, (int, const void*, size_t, int, const struct sockaddr*, socklen_t))
IMP(ssize_t, recvfrom, (int, void*, size_t, int, struct sockaddr*, socklen_t*))
#endif /* _WINSOCKAPI_ */
//
// <poll.h>
//
struct pollfd
{
int fd;
short int events, revents;
};
#define POLLIN 1
extern int poll(struct pollfd[], int, int);
//
// <sys/utsname.h>
//
struct utsname
{
char sysname[9]; // Name of this implementation of the operating system.
char nodename[16]; // Name of this node within an implementation-defined
// communications network.
// Win9x requires this minimum buffer size.
char release[9]; // Current release level of this implementation.
char version[16]; // Current version level of this release.
char machine[9]; // Name of the hardware type on which the system is running.
};
extern int uname(struct utsname*);
//
// serial port IOCTL
//
// use with TIOCMBIS
#define TIOCM_RTS 1
// use with TIOCMGET or TIOCMIWAIT
#define TIOCM_CD 0x80 // MS_RLSD_ON
#define TIOCM_CTS 0x10 // MS_CTS_ON
enum
{
TIOCMBIS, // set control line
TIOCMGET, // get line state
TIOCMIWAIT // wait for status change
};
extern int ioctl(int fd, int op, int* data);
#ifndef _WINSOCKAPI_
#define FIONREAD 0
#endif
extern void _get_console();
extern void _hide_console();
// split out of this module
#include "waio.h"
#ifdef __cplusplus
}
#endif
#endif // #ifndef __WPOSIX_H__

639
source/lib/sysdep/win/wsdl.cpp Executable file
View File

@ -0,0 +1,639 @@
/*
* emulation of a subset of SDL and GLUT for Win32
*
* Copyright (c) 2003 Jan Wassenberg
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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. See the GNU
* General Public License for more details.
*
* Contact info:
* Jan.Wassenberg@stud.uni-karlsruhe.de
* http://www.stud.uni-karlsruhe.de/~urkt/
*/
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <process.h>
#include "sdl.h"
#include "lib.h"
#include "win_internal.h"
#include "misc.h"
#ifdef _MSC_VER
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#endif
/* state */
static bool app_active; /* is window active & on top?
if not, msg loop should yield */
static bool fullscreen; /* in fullscreen mode?
if so, restore mode when app is deactivated */
HWND hWnd = 0; /* available to the app for ShowWindow calls, etc. */
static DEVMODE dm; /* current video mode */
static HDC hDC;
static HGLRC hGLRC;
static int z_depth = 24; /* depth buffer size; set via SDL_GL_SetAttribute */
static u16 mouse_x, mouse_y;
/*
* shared msg handler
* SDL and GLUT have separate pumps; messages are handled there
*/
static LRESULT CALLBACK wndproc(HWND hWnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
return 0;
case WM_ERASEBKGND:
return 0;
// prevent screensaver / monitor power off
case WM_SYSCOMMAND:
if(wParam == SC_SCREENSAVE || wParam == SC_MONITORPOWER)
return 0;
break;
case WM_ACTIVATE:
app_active = (wParam & 0xffff) != 0;
if(fullscreen)
{
if(app_active)
ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
else
ChangeDisplaySettings(0, 0);
}
break;
case WM_CLOSE:
exit(0);
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int SDL_EnableUNICODE(int enable)
{
UNUSED(enable)
return 1;
}
// note: keysym.unicode is only returned for SDL_KEYDOWN, and is otherwise 0.
int SDL_PollEvent(SDL_Event* ev)
{
if(!ev)
return -1;
//
// buffer for unicode support / dead char
//
const int CHAR_BUF_SIZE = 8;
static wchar_t char_buf[CHAR_BUF_SIZE];
static uint next_char_idx;
static uint num_chars;
// input is waiting in buffer
return_char:
assert(num_chars <= CHAR_BUF_SIZE);
if(num_chars != 0)
{
num_chars--;
if(next_char_idx < CHAR_BUF_SIZE)
{
// ToUnicode returns lower case chars - make it uppercase
// to match the VK codes from WM_KEYDOWN.
wchar_t c = towupper(char_buf[next_char_idx]);
next_char_idx++;
ev->type = SDL_KEYDOWN;
ev->key.keysym.sym = (SDLKey)((c < 0x80)? c : 0);
ev->key.keysym.unicode = c;
return 1;
}
else
assert(0 && "SDL_PollEvent: next_char_idx invalid");
}
// events that trigger messages (mouse done below)
MSG msg;
while(PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
int sdl_btn = -1;
switch(msg.message)
{
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
{
UINT vk = (UINT)msg.wParam;
if(vk >= 0x6a && vk < 0x6f)
;
else
{
UINT scancode = (UINT)((msg.lParam >> 16) & 0xff);
u8 key_states[256];
GetKeyboardState(key_states);
num_chars = ToUnicode(vk, scancode, key_states, char_buf, CHAR_BUF_SIZE, 0);
next_char_idx = 0;
if(num_chars)
goto return_char;
}
ev->type = SDL_KEYDOWN;
ev->key.keysym.sym = (SDLKey)vk;
ev->key.keysym.unicode = 0;
return 1;
}
case WM_SYSKEYUP:
case WM_KEYUP:
ev->type = SDL_KEYUP;
ev->key.keysym.sym = (SDLKey)msg.wParam;
ev->key.keysym.unicode = 0;
return 1;
case WM_ACTIVATE:
ev->type = SDL_ACTIVE;
ev->active.gain = app_active;
ev->active.state = 0;
return 1;
case WM_MOUSEWHEEL:
sdl_btn = (msg.wParam & BIT(31))? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
break; // event filled in mouse code below
}
// mouse button
// map Win L(up,down,double),R(),M() to L,R,M with up flag
uint btn = msg.message-0x201; // 0..8 if it's a valid button;
if(btn < 9 && btn%3 != 2) // every third msg is dblclick
sdl_btn = SDL_BUTTON_LEFT + btn/3; // assumes L,R,M
if(sdl_btn != -1)
{
ev->type = SDL_MOUSEBUTTONDOWN + btn%3;
ev->button.button = (u8)sdl_btn;
ev->button.x = (u16)(msg.lParam & 0xffff);
ev->button.y = (u16)((msg.lParam >> 16) & 0xffff);
return 1;
}
}
// mouse motion
//
// don't use DirectInput, because we want to respect the user's mouse
// sensitivity settings. Windows messages are laggy, so poll instead.
POINT p;
GetCursorPos(&p);
if(mouse_x != p.x || mouse_y != p.y)
{
ev->type = SDL_MOUSEMOTION;
ev->motion.x = mouse_x = (u16)p.x;
ev->motion.y = mouse_y = (u16)p.y;
return 1;
}
return 0;
}
int SDL_GL_SetAttribute(SDL_GLattr attr, int value)
{
if(attr == SDL_GL_DEPTH_SIZE)
z_depth = value;
return 0;
}
/*
* set video mode wxh:bpp if necessary.
* w = h = bpp = 0 => no change.
*/
int SDL_SetVideoMode(int w, int h, int bpp, unsigned long flags)
{
fullscreen = (flags & SDL_FULLSCREEN);
/* get current mode settings */
dm.dmSize = sizeof(DEVMODE);
EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm);
int cur_w = dm.dmPelsWidth, cur_h = dm.dmPelsHeight;
dm.dmBitsPerPel = bpp;
dm.dmFields = DM_BITSPERPEL;
/*
* check if mode needs to be changed
* (could split this out, but depends on fullscreen and dm)
*/
if(w != 0 && h != 0 && bpp != 0)
if(/* higher res mode needed */
(w > cur_w || h > cur_h) ||
/* fullscreen, and not exact mode */
(fullscreen && (w != cur_w || h != cur_h)))
{
dm.dmPelsWidth = w;
dm.dmPelsHeight = h;
dm.dmFields |= DM_PELSWIDTH|DM_PELSHEIGHT;
}
// mode set at first WM_ACTIVATE
/*
* window init
* create new window every time (instead of once at startup), 'cause
* pixel format isn't supposed to be changed more than once
*/
HINSTANCE hInst = GetModuleHandle(0);
/* register window class */
static WNDCLASS wc;
wc.style = CS_OWNDC;
wc.lpfnWndProc = wndproc;
wc.lpszClassName = "ogl";
wc.hInstance = hInst;
RegisterClass(&wc);
hWnd = CreateWindowEx(0, "ogl", APP_NAME, WS_POPUP|WS_VISIBLE, 0, 0, w, h, 0, 0, hInst, 0);
if(!hWnd)
return 0;
hDC = GetDC(hWnd);
/* set pixel format */
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_SUPPORT_OPENGL|PFD_DRAW_TO_WINDOW|PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
(BYTE)bpp,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
(BYTE)z_depth,
0, 0,
PFD_MAIN_PLANE,
0, 0, 0, 0
};
int pf = ChoosePixelFormat(hDC, &pfd);
if(!SetPixelFormat(hDC, pf, &pfd))
return 0;
hGLRC = wglCreateContext(hDC);
if(!hGLRC)
return 0;
if(!wglMakeCurrent(hDC, hGLRC))
return 0;
return 1;
}
inline void SDL_GL_SwapBuffers()
{
SwapBuffers(hDC);
}
void SDL_Quit()
{
if(hWnd != INVALID_HANDLE_VALUE)
{
DestroyWindow(hWnd);
hWnd = (HWND)INVALID_HANDLE_VALUE;
}
if(hGLRC != INVALID_HANDLE_VALUE)
{
wglMakeCurrent(0, 0);
wglDeleteContext(hGLRC);
ChangeDisplaySettings(0, 0);
hGLRC = (HGLRC)INVALID_HANDLE_VALUE;
}
}
void SDL_WM_SetCaption(const char *title, const char *icon)
{
SetWindowText(hWnd, title);
}
#define DDRAW
#ifdef DDRAW
#include <ddraw.h>
#ifdef _MSC_VER
#pragma comment(lib, "ddraw.lib")
// for DirectDrawCreate. don't bother with dynamic linking -
// DirectX is present in all Windows versions since Win95.
#endif
#endif
SDL_VideoInfo* SDL_GetVideoInfo()
{
static SDL_VideoInfo video_info;
#ifdef DDRAW
static bool init;
if(!init)
{
IDirectDraw* dd = 0;
HRESULT hr = DirectDrawCreate(0, &dd, 0);
if(SUCCEEDED(hr) && dd != 0)
{
static DDCAPS caps;
caps.dwSize = sizeof(caps);
hr = dd->GetCaps(&caps, 0);
if(SUCCEEDED(hr))
video_info.video_mem = caps.dwVidMemTotal;
dd->Release();
}
init = true;
}
#endif
return &video_info;
}
SDL_Surface* SDL_GetVideoSurface()
{
return 0;
}
__declspec(naked) u32 SDL_GetTicks()
{
__asm jmp dword ptr [GetTickCount]
}
void* SDL_GL_GetProcAddress(const char* name)
{
return wglGetProcAddress(name);
}
SDL_sem* SDL_CreateSemaphore(int cnt)
{
return (SDL_sem*)CreateSemaphore(0, cnt, 0x7fffffff, 0);
}
void __stdcall SDL_DestroySemaphore(SDL_sem*)
{
__asm jmp dword ptr [CloseHandle]
}
int SDL_SemPost(SDL_sem* sem)
{
return ReleaseSemaphore(sem, 1, 0);
}
int SDL_SemWait(SDL_sem* sem)
{
return WaitForSingleObject(sem, INFINITE);
}
SDL_Thread* SDL_CreateThread(int(*func)(void*), void* param)
{
return (SDL_Thread*)_beginthread((void(*)(void*))func, 0, param);
}
int SDL_KillThread(SDL_Thread* thread)
{
return TerminateThread(thread, 0);
}
__declspec(naked) int __stdcall SDL_WarpMouse(int, int)
{
__asm jmp dword ptr [SetCursorPos]
}
static bool need_redisplay; /* display callback should be called in next main loop iteration */
/* glut callbacks */
static void (*idle)();
static void (*display)();
static void (*key)(int, int, int);
static void (*special)(int, int, int);
static void (*mouse)(int, int, int, int);
void glutIdleFunc(void (*func)())
{ idle = func; }
void glutDisplayFunc(void (*func)())
{ display = func; }
void glutKeyboardFunc(void (*func)(int, int, int))
{ key = func; }
void glutSpecialFunc(void (*func)(int, int, int))
{ special = func; }
void glutMouseFunc(void (*func)(int, int, int, int))
{ mouse = func; }
void glutInit(int* argc, char* argv[])
{
UNUSED(argc)
UNUSED(argv)
SDL_Init(0);
atexit(SDL_Quit);
}
int glutGet(int arg)
{
if(arg == GLUT_ELAPSED_TIME)
return GetTickCount();
dm.dmSize = sizeof(DEVMODE);
EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm);
if(arg == GLUT_SCREEN_WIDTH)
return dm.dmPelsWidth;
if(arg == GLUT_SCREEN_HEIGHT)
return dm.dmPelsHeight;
return 0;
}
static int w, h, bpp, refresh;
int glutGameModeString(const char* str)
{
/* default = "don't care", in case string doesn't specify all values */
w = 0, h = 0, bpp = 0, refresh = 0;
sscanf(str, "%dx%d:%d@%d", &w, &h, &bpp, &refresh);
return 1;
}
/*
*/
int glutEnterGameMode()
{
return SDL_SetVideoMode(w, h, bpp, SDL_OPENGL|SDL_FULLSCREEN);
}
inline void glutPostRedisplay()
{
need_redisplay = true;
}
void glutSetCursor(int cursor)
{
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(cursor)));
}
/*
* GLUT message handler
* message also goes to the shared wndproc
*
* not done in wndproc to separate GLUT and SDL;
* split out of glutMainLoop for clarity.
*/
static void glut_process_msg(MSG* msg)
{
switch(msg->message)
{
case WM_PAINT:
need_redisplay = true;
break;
case WM_CHAR:
if(key)
key((int)msg->wParam, mouse_x, mouse_y);
break;
case WM_KEYDOWN:
if(special)
special((int)msg->wParam, mouse_x, mouse_y);
break;
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN: /* FIXME: only left/right clicks, assume GLUT_LEFT|RIGHT_BUTTON == 0, 1 */
if(mouse)
mouse(msg->message == WM_RBUTTONDOWN, GLUT_DOWN, (int)(msg->lParam & 0xffff), (int)(msg->lParam >> 16));
break;
case WM_MOUSEWHEEL:
if(mouse)
mouse(GLUT_MIDDLE_BUTTON, ((short)(msg->wParam >> 16) > 0)? GLUT_UP : GLUT_DOWN, 0, 0);
break;
}
}
void glutMainLoop()
{
for(;;)
{
if(!app_active)
WaitMessage();
MSG msg;
if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
glut_process_msg(&msg);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(idle)
idle();
if(need_redisplay)
{
need_redisplay = false;
display();
}
}
}

305
source/lib/sysdep/win/wsdl.h Executable file
View File

@ -0,0 +1,305 @@
#ifndef __WSDL_H__
#define __WSDL_H__
#include "types.h"
/* allow apps to override window name */
#ifndef APP_NAME
#define APP_NAME "ogl"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* SDL_Init flags */
#define SDL_INIT_VIDEO 0
#define SDL_INIT_AUDIO 0
#define SDL_INIT_TIMER 0
#define SDL_INIT_NOPARACHUTE 0
extern void SDL_Quit();
typedef enum
{
SDL_GL_DEPTH_SIZE,
SDL_GL_DOUBLEBUFFER /* ignored - always double buffered */
}
SDL_GLattr;
extern int SDL_GL_SetAttribute(SDL_GLattr attr, int value);
/* SDL_SetVideoMode() flags */
#define SDL_OPENGL 0
#define SDL_FULLSCREEN 1
extern int SDL_SetVideoMode(int w, int h, int bpp, unsigned long flags);
typedef struct
{
int w, h;
}
SDL_Surface;
extern SDL_Surface* SDL_GetVideoSurface();
typedef struct
{
int video_mem;
}
SDL_VideoInfo;
extern SDL_VideoInfo* SDL_GetVideoInfo();
/*
* threads / sync
*/
typedef void SDL_sem;
typedef void SDL_Thread;
extern void* SDL_GL_GetProcAddress(const char*);
extern void SDL_GL_SwapBuffers();
extern u32 SDL_GetTicks();
extern SDL_sem* SDL_CreateSemaphore(int cnt);
extern void __stdcall SDL_DestroySemaphore(SDL_sem*);
extern int SDL_SemPost(SDL_sem*);
extern int SDL_SemWait(SDL_sem* sem);
extern SDL_Thread* SDL_CreateThread(int(*)(void*), void*);
extern int SDL_KillThread(SDL_Thread*);
extern int __stdcall SDL_WarpMouse(int, int);
/* macros */
#define SDL_Init
#define SDL_GRAB_ON 0
#define SDL_WM_GrabInput(a)
#define SDL_GetError() ""
/************************************************************************************************
* events
************************************************************************************************/
/* SDLKey (mapped to VK_* codes) */
typedef enum
{
SDLK_ESCAPE = 0x1b,
SDLK_8 = '8',
SDLK_9 = '9',
SDLK_0 = '0',
SDLK_p = 'P',
SDLK_r = 'R',
SDLK_s = 'S',
SDLK_KP_PLUS = 0x6b,
SDLK_KP_MINUS = 0x6d,
SDLK_LEFT = 0x25,
SDLK_UP = 0x26,
SDLK_RIGHT = 0x27,
SDLK_DOWN = 0x28,
SDLK_KP0 = 0x60,
SDLK_KP1 = 0x61,
SDLK_KP2 = 0x62,
SDLK_KP3 = 0x63,
SDLK_KP4 = 0x64,
SDLK_KP5 = 0x65,
SDLK_KP6 = 0x66,
SDLK_KP7 = 0x67,
SDLK_KP8 = 0x68,
SDLK_KP9 = 0x69,
__SDLK // hack to allow trailing comma
}
SDLKey;
typedef struct
{
SDLKey sym;
u16 unicode;
}
SDL_keysym;
typedef struct
{
SDL_keysym keysym;
}
SDL_KeyboardEvent;
typedef struct
{
u16 x, y;
}
SDL_MouseMotionEvent;
/* SDL_MouseButtonEvent.button */
enum
{
// do not change order
SDL_BUTTON_LEFT,
SDL_BUTTON_RIGHT,
SDL_BUTTON_MIDDLE,
SDL_BUTTON_WHEELUP,
SDL_BUTTON_WHEELDOWN
};
typedef struct
{
u8 button;
u8 state;
u16 x, y;
}
SDL_MouseButtonEvent;
typedef struct
{
u8 gain;
u8 state;
}
SDL_ActiveEvent;
/* SDL_Event.type */
enum
{
SDL_KEYDOWN,
SDL_KEYUP,
SDL_MOUSEMOTION,
SDL_MOUSEBUTTONDOWN,
SDL_MOUSEBUTTONUP,
SDL_ACTIVE
};
typedef struct
{
u8 type;
union
{
SDL_KeyboardEvent key;
SDL_MouseMotionEvent motion;
SDL_MouseButtonEvent button;
SDL_ActiveEvent active;
};
}
SDL_Event;
extern int SDL_EnableUNICODE(int enable);
extern int SDL_PollEvent(SDL_Event* ev);
extern void SDL_WM_SetCaption(const char *title, const char *icon);
/* glutInitDisplayMode */
#define GLUT_RGB 0
#define GLUT_DOUBLE 0
#define GLUT_DEPTH 0
/* mouse buttons */
enum
{
GLUT_LEFT_BUTTON,
GLUT_RIGHT_BUTTON,
GLUT_MIDDLE_BUTTON /* also wheel, if avail */
};
/* mouse button state */
enum
{
GLUT_DOWN,
GLUT_UP
};
/* keys */
enum
{
GLUT_KEY_LEFT = 0x25, /* VK_* */
GLUT_KEY_RIGHT = 0x27,
GLUT_KEY_UP = 0x26,
GLUT_KEY_DOWN = 0x28
};
/* glutSetCursor */
#define GLUT_CURSOR_INHERIT 32512 /* IDC_* */
#define GLUT_CURSOR_WAIT 32514
#define GLUT_CURSOR_DESTROY 32648
#define GLUT_CURSOR_NONE 0
/* glutGet */
enum
{
GLUT_ELAPSED_TIME,
GLUT_SCREEN_WIDTH,
GLUT_SCREEN_HEIGHT,
GLUT_GAME_MODE_WIDTH,
GLUT_GAME_MODE_HEIGHT,
GLUT_GAME_MODE_PIXEL_DEPTH,
GLUT_GAME_MODE_REFRESH_RATE
};
extern void glutIdleFunc(void(*)());
extern void glutDisplayFunc(void(*)());
extern void glutKeyboardFunc(void(*)(int, int, int));
extern void glutSpecialFunc(void(*)(int, int, int));
extern void glutMouseFunc(void(*)(int, int, int, int));
#define glutInitDisplayMode(a) /* pixel format is hardwired */
extern int glutGameModeString(const char* str);
extern void glutInit(int* argc, char* argv[]);
extern int glutGet(int arg);
extern int glutEnterGameMode();
extern void glutMainLoop();
extern void glutPostRedisplay();
extern void glutSetCursor(int);
#define glutSwapBuffers SDL_GL_SwapBuffers
#ifdef __cplusplus
}
#endif
#endif // #ifndef __WSDL_H__

39
source/lib/sysdep/x.cpp Executable file
View File

@ -0,0 +1,39 @@
// X Window System-specific code
// Copyright (c) 2004 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifdef HAVE_X
#include <Xlib.h>
// useful for choosing a video mode. not called by detect().
void get_cur_resolution(int& xres, int& yres)
{
Display* disp = XOpenDisplay(NULL);
if(!disp)
{
xres = 1024;
yres = 768;
return;
}
int screen = XDefaultScreen(disp);
xres = XDisplayWidth (disp, screen);
yres = XDisplayHeight(disp, screen);
XCloseDisplay(disp);
}
#endif // #ifdef HAVE_X

166
source/lib/timer.cpp Executable file
View File

@ -0,0 +1,166 @@
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include <cmath>
#include "timer.h"
#include "types.h"
#include "misc.h"
#include "lib.h"
#ifdef _WIN32
#include "sysdep/win/hrt.h"
#endif
// wrapper over gettimeofday, instead of emulating it for Windows,
// because double return type allows higher resolution (e.g. if using TSC),
// and gettimeofday is not guaranteed to be monotonic.
double get_time()
{
double t;
#ifdef _WIN32
t = hrt_time();
#elif defined(HAVE_CLOCK_GETTIME)
static struct timespec start;
struct timespec ts;
if(!start.tv_sec)
clock_gettime(CLOCK_REALTIME, &start);
clock_gettime(CLOCK_REALTIME, &ts);
t = (ts.tv_sec - start.tv_sec) + (ts.tv_nsec - start.tv_nsec)*1e-9;
#elif defined(HAVE_GETTIMEOFDAY)
static struct timeval start;
struct timeval cur;
if(!start.tv_sec)
gettimeofday(&start, 0);
gettimeofday(&cur, 0);
t = (cur.tv_sec - start.tv_sec) + (cur.tv_nsec - start.tv_nsec)*1e-6;
#else
#error "get_time: add timer implementation for this platform!"
#endif
// make sure time is monotonic (never goes backwards)
static double t_last;
if(t_last != 0.0 && t < t_last)
t = t_last;
return t;
}
double timer_res()
{
#ifdef _WIN32
HRTImpl impl;
i64 nominal_freq;
hrt_query_impl(impl, nominal_freq);
return 1.0 / nominal_freq;
#elif defined(HAVE_CLOCK_GETTIME)
struct timespec res;
clock_getres(CLOCK_REALTIME, res);
return res.tv_nsec * 1e-9;
#else
// guess millisecond-class
return 1e-3;
#endif
}
// calculate fps (call once per frame)
// several smooth filters:
// - throw out single spikes / dips
// - average via small history buffer
// - update final value iff the difference (% or absolute) is too great,
// or if the change is consistent with the trend over the last few frames.
//
// => less fluctuation, but rapid tracking.
// filter values are tuned for 100 FPS.
int fps = 0;
void calc_fps()
{
// history buffer - smooth out slight variations
#define H 10 // # buffer entries
static float fps_sum = 0; // sum of last H frames' cur_fps
static float fps_hist[H]; // last H frames' cur_fps
// => don't need to re-average every time
static uint head = 0; // oldest entry in fps_hist
// must be unsigned, b/c we do (head-1)%H
// get elapsed time [s] since last frame; approximate current fps
static double last_t;
double t = get_time();
float cur_fps = 30.0f; // start value => history converges faster
if(last_t != 0.0)
cur_fps = 1.0f / (float)(t-last_t); // = 1 / elapsed time
last_t = t;
// calculate fps activity over 3 frames (used below to prevent fluctuation)
// -1: decreasing, +1: increasing, 0: neither or fluctuating
float h1 = fps_hist[(head-1)%H]; // last frame's cur_fps
float h2 = fps_hist[(head-2)%H]; // 2nd most recent frame's cur_fps
int trend = 0;
if(h2 > h1 && h1 > cur_fps) // decreasing
trend = -1;
else if(cur_fps < h1 && h1 < h2) // increasing
trend = 1;
// ignore onetime skips in fps (probably page faults or similar)
static int ignored;
if(fabs(h1-cur_fps) > .05f*h1 && // > 5% difference
!ignored++) // was it first value we're discarding?
return; // yes: don't update fps_hist/fps
ignored = 0; // either value ok, or it wasn't a fluke - reset counter
// remove oldest cur_fps value in fps_hist from the sum
// and add cur_fps; also insert cur_fps in fps_hist
fps_sum -= fps_hist[head];
fps_sum += (fps_hist[head] = cur_fps);
head = (head+1)%H;
// update fps counter if update threshold is exceeded
const float avg_fps = fps_sum / H;
const float d_avg = avg_fps-fps;
const float max_diff = fminf(5.f, 0.05f*fps);
if((trend > 0 && (avg_fps > fps || d_avg < -4.f)) || // going up, or large drop
(trend < 0 && (avg_fps < fps || d_avg > 4.f)) || // going down, or large raise
(fabs(d_avg) > max_diff)) // significant difference
fps = (int)avg_fps;
}

46
source/lib/timer.h Executable file
View File

@ -0,0 +1,46 @@
// platform independent high resolution timer
//
// Copyright (c) 2003 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// 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. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef TIMER_H
#define TIMER_H
#ifdef __cplusplus
extern "C" {
#endif
// high resolution (> 1 µs) timestamp [s], starting at or near 0 s.
extern double get_time();
extern double timer_res();
// calculate fps (call once per frame)
// several smooth filters (tuned for ~100 FPS)
// => less fluctuation, but rapid tracking
extern int fps;
extern void calc_fps();
#ifdef __cplusplus
}
#endif
#endif // #ifndef TIMER_H