the big merge (tm). see forum post for changes made.
This was SVN commit r158.
This commit is contained in:
parent
91cb7f7138
commit
89c5b0d88c
111
source/lib/adts.cpp
Executable file
111
source/lib/adts.cpp
Executable 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
146
source/lib/adts.h
Executable 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
26
source/lib/config.h
Executable 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
106
source/lib/lib.cpp
Executable 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
175
source/lib/lib.h
Executable 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
953
source/lib/res/file.cpp
Executable 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
79
source/lib/res/file.h
Executable 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
239
source/lib/res/font.cpp
Executable 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
81
source/lib/res/font.h
Executable 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
518
source/lib/res/h_mgr.cpp
Executable 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
188
source/lib/res/h_mgr.h
Executable 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
290
source/lib/res/mem.cpp
Executable 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
43
source/lib/res/mem.h
Executable 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
0
source/lib/res/res.cpp
Executable file
5
source/lib/res/res.h
Executable file
5
source/lib/res/res.h
Executable 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
888
source/lib/res/tex.cpp
Executable 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
43
source/lib/res/tex.h
Executable 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
711
source/lib/res/vfs.cpp
Executable 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
62
source/lib/res/vfs.h
Executable 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
536
source/lib/res/zip.cpp
Executable 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
110
source/lib/res/zip.h
Executable 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
6
source/lib/sdl.h
Executable 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
320
source/lib/sysdep/ia32.cpp
Executable 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
66
source/lib/sysdep/ia32.h
Executable 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
26
source/lib/sysdep/sysdep.cpp
Executable 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
17
source/lib/sysdep/sysdep.h
Executable 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
485
source/lib/sysdep/win/hrt.cpp
Executable 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
78
source/lib/sysdep/win/hrt.h
Executable 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
563
source/lib/sysdep/win/waio.cpp
Executable 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, §or_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
68
source/lib/sysdep/win/waio.h
Executable 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
178
source/lib/sysdep/win/wdetect.cpp
Executable 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
178
source/lib/sysdep/win/win.cpp
Executable 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
19
source/lib/sysdep/win/win.h
Executable 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__
|
272
source/lib/sysdep/win/win_internal.h
Executable file
272
source/lib/sysdep/win/win_internal.h
Executable 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
677
source/lib/sysdep/win/wposix.cpp
Executable 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
602
source/lib/sysdep/win/wposix.h
Executable 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
639
source/lib/sysdep/win/wsdl.cpp
Executable 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
305
source/lib/sysdep/win/wsdl.h
Executable 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
39
source/lib/sysdep/x.cpp
Executable 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
166
source/lib/timer.cpp
Executable 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
46
source/lib/timer.h
Executable 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
|
Loading…
Reference in New Issue
Block a user