From 89c5b0d88cc3b28d45e86180695047715f379797 Mon Sep 17 00:00:00 2001 From: janwas Date: Tue, 2 Mar 2004 23:56:51 +0000 Subject: [PATCH] the big merge (tm). see forum post for changes made. This was SVN commit r158. --- source/lib/adts.cpp | 111 ++++ source/lib/adts.h | 146 ++++ source/lib/config.h | 26 + source/lib/lib.cpp | 106 +++ source/lib/lib.h | 175 +++++ source/lib/res/file.cpp | 953 +++++++++++++++++++++++++++ source/lib/res/file.h | 79 +++ source/lib/res/font.cpp | 239 +++++++ source/lib/res/font.h | 81 +++ source/lib/res/h_mgr.cpp | 518 +++++++++++++++ source/lib/res/h_mgr.h | 188 ++++++ source/lib/res/mem.cpp | 290 ++++++++ source/lib/res/mem.h | 43 ++ source/lib/res/res.cpp | 0 source/lib/res/res.h | 5 + source/lib/res/tex.cpp | 888 +++++++++++++++++++++++++ source/lib/res/tex.h | 43 ++ source/lib/res/vfs.cpp | 711 ++++++++++++++++++++ source/lib/res/vfs.h | 62 ++ source/lib/res/zip.cpp | 536 +++++++++++++++ source/lib/res/zip.h | 110 ++++ source/lib/sdl.h | 6 + source/lib/sysdep/ia32.cpp | 320 +++++++++ source/lib/sysdep/ia32.h | 66 ++ source/lib/sysdep/sysdep.cpp | 26 + source/lib/sysdep/sysdep.h | 17 + source/lib/sysdep/win/hrt.cpp | 485 ++++++++++++++ source/lib/sysdep/win/hrt.h | 78 +++ source/lib/sysdep/win/waio.cpp | 563 ++++++++++++++++ source/lib/sysdep/win/waio.h | 68 ++ source/lib/sysdep/win/wdetect.cpp | 178 +++++ source/lib/sysdep/win/win.cpp | 178 +++++ source/lib/sysdep/win/win.h | 19 + source/lib/sysdep/win/win_internal.h | 272 ++++++++ source/lib/sysdep/win/wposix.cpp | 677 +++++++++++++++++++ source/lib/sysdep/win/wposix.h | 602 +++++++++++++++++ source/lib/sysdep/win/wsdl.cpp | 639 ++++++++++++++++++ source/lib/sysdep/win/wsdl.h | 305 +++++++++ source/lib/sysdep/x.cpp | 39 ++ source/lib/timer.cpp | 166 +++++ source/lib/timer.h | 46 ++ 41 files changed, 10060 insertions(+) create mode 100755 source/lib/adts.cpp create mode 100755 source/lib/adts.h create mode 100755 source/lib/config.h create mode 100755 source/lib/lib.cpp create mode 100755 source/lib/lib.h create mode 100755 source/lib/res/file.cpp create mode 100755 source/lib/res/file.h create mode 100755 source/lib/res/font.cpp create mode 100755 source/lib/res/font.h create mode 100755 source/lib/res/h_mgr.cpp create mode 100755 source/lib/res/h_mgr.h create mode 100755 source/lib/res/mem.cpp create mode 100755 source/lib/res/mem.h create mode 100755 source/lib/res/res.cpp create mode 100755 source/lib/res/res.h create mode 100755 source/lib/res/tex.cpp create mode 100755 source/lib/res/tex.h create mode 100755 source/lib/res/vfs.cpp create mode 100755 source/lib/res/vfs.h create mode 100755 source/lib/res/zip.cpp create mode 100755 source/lib/res/zip.h create mode 100755 source/lib/sdl.h create mode 100755 source/lib/sysdep/ia32.cpp create mode 100755 source/lib/sysdep/ia32.h create mode 100755 source/lib/sysdep/sysdep.cpp create mode 100755 source/lib/sysdep/sysdep.h create mode 100755 source/lib/sysdep/win/hrt.cpp create mode 100755 source/lib/sysdep/win/hrt.h create mode 100755 source/lib/sysdep/win/waio.cpp create mode 100755 source/lib/sysdep/win/waio.h create mode 100755 source/lib/sysdep/win/wdetect.cpp create mode 100755 source/lib/sysdep/win/win.cpp create mode 100755 source/lib/sysdep/win/win.h create mode 100755 source/lib/sysdep/win/win_internal.h create mode 100755 source/lib/sysdep/win/wposix.cpp create mode 100755 source/lib/sysdep/win/wposix.h create mode 100755 source/lib/sysdep/win/wsdl.cpp create mode 100755 source/lib/sysdep/win/wsdl.h create mode 100755 source/lib/sysdep/x.cpp create mode 100755 source/lib/timer.cpp create mode 100755 source/lib/timer.h diff --git a/source/lib/adts.cpp b/source/lib/adts.cpp new file mode 100755 index 0000000000..abece67375 --- /dev/null +++ b/source/lib/adts.cpp @@ -0,0 +1,111 @@ +#include "adts.h" + +#include + +template 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; +} diff --git a/source/lib/adts.h b/source/lib/adts.h new file mode 100755 index 0000000000..508ef96772 --- /dev/null +++ b/source/lib/adts.h @@ -0,0 +1,146 @@ +#include "lib.h" + +#include +#include + + + +template 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 List; + typedef List::iterator List_iterator; + List lru_list; + + typedef std::map 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); +}; diff --git a/source/lib/config.h b/source/lib/config.h new file mode 100755 index 0000000000..91d899d684 --- /dev/null +++ b/source/lib/config.h @@ -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 + + diff --git a/source/lib/lib.cpp b/source/lib/lib.cpp new file mode 100755 index 0000000000..2060382623 --- /dev/null +++ b/source/lib/lib.cpp @@ -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 +#include + + +// 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); +} diff --git a/source/lib/lib.h b/source/lib/lib.h new file mode 100755 index 0000000000..fabdba1de3 --- /dev/null +++ b/source/lib/lib.h @@ -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 + +#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 diff --git a/source/lib/res/file.cpp b/source/lib/res/file.cpp new file mode 100755 index 0000000000..3e8882c0aa --- /dev/null +++ b/source/lib/res/file.cpp @@ -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 + +#include +#include +#include + + +// 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 containing . +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 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 +{ + 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); +} diff --git a/source/lib/res/file.h b/source/lib/res/file.h new file mode 100755 index 0000000000..bc8c17847f --- /dev/null +++ b/source/lib/res/file.h @@ -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 \ No newline at end of file diff --git a/source/lib/res/font.cpp b/source/lib/res/font.cpp new file mode 100755 index 0000000000..5bb71c0e17 --- /dev/null +++ b/source/lib/res/font.cpp @@ -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 +#include +#include +#include + +#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 +//#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); +} diff --git a/source/lib/res/font.h b/source/lib/res/font.h new file mode 100755 index 0000000000..0ffb26370c --- /dev/null +++ b/source/lib/res/font.h @@ -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 +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 + +*/ diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp new file mode 100755 index 0000000000..09ba60331c --- /dev/null +++ b/source/lib/res/h_mgr.cpp @@ -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 +#include +#include +#include +#include + +#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 ) 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; + diff --git a/source/lib/res/h_mgr.h b/source/lib/res/h_mgr.h new file mode 100755 index 0000000000..6cdbb7a50e --- /dev/null +++ b/source/lib/res/h_mgr.h @@ -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 // 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; + +// * = H_USER_DATA(, ) +#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 ) 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__ diff --git a/source/lib/res/mem.cpp b/source/lib/res/mem.cpp new file mode 100755 index 0000000000..2a88b16dd1 --- /dev/null +++ b/source/lib/res/mem.cpp @@ -0,0 +1,290 @@ +// malloc layer for less fragmentation, alignment, and automatic release + +#include +#include + +#include "lib.h" +#include "types.h" +#include "mem.h" +#include "h_mgr.h" +#include "misc.h" + +#include + + + + +////////////////////////////////////////////////////////////////////////////// + + +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 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; +} \ No newline at end of file diff --git a/source/lib/res/mem.h b/source/lib/res/mem.h new file mode 100755 index 0000000000..5ad07ade10 --- /dev/null +++ b/source/lib/res/mem.h @@ -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 diff --git a/source/lib/res/res.cpp b/source/lib/res/res.cpp new file mode 100755 index 0000000000..e69de29bb2 diff --git a/source/lib/res/res.h b/source/lib/res/res.h new file mode 100755 index 0000000000..f5325e9acf --- /dev/null +++ b/source/lib/res/res.h @@ -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" \ No newline at end of file diff --git a/source/lib/res/tex.cpp b/source/lib/res/tex.cpp new file mode 100755 index 0000000000..580f2428e2 --- /dev/null +++ b/source/lib/res/tex.cpp @@ -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 +#include +#include +#include + +#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 +#endif + + +#define _WINDOWS_ +#define WINAPI __stdcall +#define WINAPIV __cdecl + +#ifndef NO_PNG +#include +#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; +} diff --git a/source/lib/res/tex.h b/source/lib/res/tex.h new file mode 100755 index 0000000000..7f742ce8ab --- /dev/null +++ b/source/lib/res/tex.h @@ -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 . +// 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__ diff --git a/source/lib/res/vfs.cpp b/source/lib/res/vfs.cpp new file mode 100755 index 0000000000..ff192e6fbd --- /dev/null +++ b/source/lib/res/vfs.cpp @@ -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 +#include +#include +#include + +#include "lib.h" +#include "file.h" +#include "zip.h" +#include "misc.h" +#include "vfs.h" +#include "mem.h" + +#include +#include +#include +#include + +// 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 + std::vector 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); +} + diff --git a/source/lib/res/vfs.h b/source/lib/res/vfs.h new file mode 100755 index 0000000000..992f275355 --- /dev/null +++ b/source/lib/res/vfs.h @@ -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__ \ No newline at end of file diff --git a/source/lib/res/zip.cpp b/source/lib/res/zip.cpp new file mode 100755 index 0000000000..b54e7f955f --- /dev/null +++ b/source/lib/res/zip.cpp @@ -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 +#include +#include + +#include "zip.h" +#include "file.h" +#include "lib.h" +#include "misc.h" +#include "h_mgr.h" +#include "mem.h" +#include "vfs.h" + +#include +#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 +inline Handle zip_archive_open(const char* const fn) +{ + return h_alloc(H_ZArchive, fn); +} + +// close the archive 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 in archive +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); +} diff --git a/source/lib/res/zip.h b/source/lib/res/zip.h new file mode 100755 index 0000000000..4784b45518 --- /dev/null +++ b/source/lib/res/zip.h @@ -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 +extern Handle zip_archive_open(const char* fn); + +// close the archive 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 (may include path) in archive +extern int zip_stat(Handle ha, const char* fn, struct stat* s); + +// open the file in archive , and fill *zf with information about it. +extern int zip_open(Handle ha, const char* fn, ZFile* zf); + +// close the file +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 , starting at offset 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__ diff --git a/source/lib/sdl.h b/source/lib/sdl.h new file mode 100755 index 0000000000..67c2f274f8 --- /dev/null +++ b/source/lib/sdl.h @@ -0,0 +1,6 @@ +#if defined(_WIN32) && !defined(NO_WSDL) +#include "sysdep/win/wsdl.h" +#else +#include +#include +#endif \ No newline at end of file diff --git a/source/lib/sysdep/ia32.cpp b/source/lib/sysdep/ia32.cpp new file mode 100755 index 0000000000..5084abde4a --- /dev/null +++ b/source/lib/sysdep/ia32.cpp @@ -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 +#include // sscanf +#include +#include +#include + +#include +#include + +#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 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 \ No newline at end of file diff --git a/source/lib/sysdep/ia32.h b/source/lib/sysdep/ia32.h new file mode 100755 index 0000000000..ba7a31f290 --- /dev/null +++ b/source/lib/sysdep/ia32.h @@ -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 diff --git a/source/lib/sysdep/sysdep.cpp b/source/lib/sysdep/sysdep.cpp new file mode 100755 index 0000000000..ca2fedbf19 --- /dev/null +++ b/source/lib/sysdep/sysdep.cpp @@ -0,0 +1,26 @@ +#include +#include + +#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 \ No newline at end of file diff --git a/source/lib/sysdep/sysdep.h b/source/lib/sysdep/sysdep.h new file mode 100755 index 0000000000..123b87ea13 --- /dev/null +++ b/source/lib/sysdep/sysdep.h @@ -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__ \ No newline at end of file diff --git a/source/lib/sysdep/win/hrt.cpp b/source/lib/sysdep/win/hrt.cpp new file mode 100755 index 0000000000..4dcc545994 --- /dev/null +++ b/source/lib/sysdep/win/hrt.cpp @@ -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 +#include +#include + +#include + +#include "hrt.h" +#include "lib.h" +#include "adts.h" +#include "sysdep/ia32.h" +#include "detect.h" + +#include "win_internal.h" +#include // 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 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 +} diff --git a/source/lib/sysdep/win/hrt.h b/source/lib/sysdep/win/hrt.h new file mode 100755 index 0000000000..05bd52dd10 --- /dev/null +++ b/source/lib/sysdep/win/hrt.h @@ -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); diff --git a/source/lib/sysdep/win/waio.cpp b/source/lib/sysdep/win/waio.cpp new file mode 100755 index 0000000000..ccbeffaa60 --- /dev/null +++ b/source/lib/sysdep/win/waio.cpp @@ -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 +#include +#include + +#include + +#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 +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; +} diff --git a/source/lib/sysdep/win/waio.h b/source/lib/sysdep/win/waio.h new file mode 100755 index 0000000000..4d03e8d799 --- /dev/null +++ b/source/lib/sysdep/win/waio.h @@ -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); diff --git a/source/lib/sysdep/win/wdetect.cpp b/source/lib/sysdep/win/wdetect.cpp new file mode 100755 index 0000000000..9535f41f96 --- /dev/null +++ b/source/lib/sysdep/win/wdetect.cpp @@ -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 +#include + +#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; +} diff --git a/source/lib/sysdep/win/win.cpp b/source/lib/sysdep/win/win.cpp new file mode 100755 index 0000000000..37cc45e9e7 --- /dev/null +++ b/source/lib/sysdep/win/win.cpp @@ -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 // __argc +#include +#include + +#include // 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); +} \ No newline at end of file diff --git a/source/lib/sysdep/win/win.h b/source/lib/sysdep/win/win.h new file mode 100755 index 0000000000..f7d7b17a27 --- /dev/null +++ b/source/lib/sysdep/win/win.h @@ -0,0 +1,19 @@ +#if !defined(__WIN_H__) && defined(_WIN32) +#define __WIN_H__ + +// C99 +#define snprintf _snprintf +#define snwprintf _snwprintf +#define vsnprintf _vsnprintf + +#include // wchar_t + +// libpng.h -> zlib.h -> zconf.h includes , 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__ \ No newline at end of file diff --git a/source/lib/sysdep/win/win_internal.h b/source/lib/sysdep/win/win_internal.h new file mode 100755 index 0000000000..f381031ebf --- /dev/null +++ b/source/lib/sysdep/win/win_internal.h @@ -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 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 + +// 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 \ No newline at end of file diff --git a/source/lib/sysdep/win/wposix.cpp b/source/lib/sysdep/win/wposix.cpp new file mode 100755 index 0000000000..1cab502760 --- /dev/null +++ b/source/lib/sysdep/win/wposix.cpp @@ -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 +#include +#include +#include + +#include + +#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(); +} +*/ \ No newline at end of file diff --git a/source/lib/sysdep/win/wposix.h b/source/lib/sysdep/win/wposix.h new file mode 100755 index 0000000000..3810c34464 --- /dev/null +++ b/source/lib/sysdep/win/wposix.h @@ -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 + +#ifdef __cplusplus +extern "C" { +#endif + + +#define IMP(ret, name, param) extern "C" __declspec(dllimport) ret __stdcall name param; + + + +// +// +// + +typedef unsigned short u16_t; + + +// +// +// + +typedef unsigned long useconds_t; +typedef long suseconds_t; +typedef long ssize_t; + + +// +// +// + +#define PATH_MAX 260 + + +// +// +// + +#define EINPROGRESS 100000 + +#include + +/* +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 + +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*); + + +// +// +// + +#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); + + +// +// +// + +// 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, ...); + + +// +// +// + +#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 + +// +// +// + +extern char* realpath(const char*, char*); + + +// +// +// + +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); +}; + + +// +// +// + +#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); + + +// +// +// + +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 + + +// +// +// + +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*); + + +// +// +// + +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); + + +// +// +// + +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)) + + +// +// +// + +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 */ +}; + + +// +// +// + +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 + + + +// +// +// + +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_ */ + + +// +// +// + +struct pollfd +{ + int fd; + short int events, revents; +}; + +#define POLLIN 1 + +extern int poll(struct pollfd[], int, int); + + +// +// +// + +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__ diff --git a/source/lib/sysdep/win/wsdl.cpp b/source/lib/sysdep/win/wsdl.cpp new file mode 100755 index 0000000000..df61c625db --- /dev/null +++ b/source/lib/sysdep/win/wsdl.cpp @@ -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 +#include +#include + +#include + +#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 +#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(); + } + } +} diff --git a/source/lib/sysdep/win/wsdl.h b/source/lib/sysdep/win/wsdl.h new file mode 100755 index 0000000000..693357b69c --- /dev/null +++ b/source/lib/sysdep/win/wsdl.h @@ -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__ diff --git a/source/lib/sysdep/x.cpp b/source/lib/sysdep/x.cpp new file mode 100755 index 0000000000..4cad2c7918 --- /dev/null +++ b/source/lib/sysdep/x.cpp @@ -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 + +// 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 \ No newline at end of file diff --git a/source/lib/timer.cpp b/source/lib/timer.cpp new file mode 100755 index 0000000000..710f8b43f8 --- /dev/null +++ b/source/lib/timer.cpp @@ -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 + +#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; +} diff --git a/source/lib/timer.h b/source/lib/timer.h new file mode 100755 index 0000000000..86247ec3d3 --- /dev/null +++ b/source/lib/timer.h @@ -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