Files
perfect_dark/port/src/system.c
T

313 lines
6.7 KiB
C

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sys/time.h>
#include <SDL.h>
#include <PR/ultratypes.h>
#include "platform.h"
#include "system.h"
#ifdef PLATFORM_WIN32
#include <windows.h>
// on win32 we use waitable timers instead of nanosleep
typedef HANDLE WINAPI (*CREATEWAITABLETIMEREXAFN)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD);
static HANDLE timer;
static CREATEWAITABLETIMEREXAFN pfnCreateWaitableTimerExA;
// winapi also provides a yield macro
#define DO_YIELD() YieldProcessor()
// ask system for high performance GPU, if any
__attribute__((dllexport)) u32 NvOptimusEnablement = 1;
__attribute__((dllexport)) u32 AmdPowerXpressRequestHighPerformance = 1;
#else
#include <unistd.h>
// figure out how to yield
#if defined(PLATFORM_X86) || defined(PLATFORM_X86_64)
// this should work even if the code is not built with SSE enabled, at least on gcc and clang,
// but if it doesn't we'll have to use __builtin_ia32_pause() or something
#include <immintrin.h>
#define DO_YIELD() _mm_pause()
#elif defined(PLATFORM_ARM)
// same as YieldProcessor() on ARM Windows
#define DO_YIELD() __asm__ volatile("dmb ishst\n\tyield":::"memory")
#else
// fuck it
#define DO_YIELD() do { } while (0)
#endif
#endif
#define LOG_FNAME "pd.log"
#define CRASHLOG_FNAME "pd.crash.log"
#define USEC_IN_SEC 1000000ULL
static u64 startTick = 0;
static char logPath[2048];
static s32 sysArgc;
static const char **sysArgv;
static inline void sysLogSetPath(const char *fname)
{
// figure out where the log is and clear it
// try working dir first
snprintf(logPath, sizeof(logPath), "./%s", fname);
FILE *f = fopen(logPath, "wb");
if (!f) {
// try home dir
sysGetHomePath(logPath, sizeof(logPath) - 1);
strncat(logPath, "/", sizeof(logPath) - 1);
strncat(logPath, fname, sizeof(logPath) - 1);
f = fopen(logPath, "wb");
}
if (f) {
fclose(f);
}
}
void sysInitArgs(s32 argc, const char **argv)
{
sysArgc = argc;
sysArgv = argv;
}
void sysInit(void)
{
startTick = sysGetMicroseconds();
if (sysArgCheck("--log")) {
sysLogSetPath(LOG_FNAME);
}
#ifdef VERSION_HASH
sysLogPrintf(LOG_NOTE, "version: " VERSION_BRANCH " " VERSION_HASH " (" VERSION_TARGET ")");
#endif
char timestr[256];
const time_t curtime = time(NULL);
strftime(timestr, sizeof(timestr), "%d %b %Y %H:%M:%S", localtime(&curtime));
sysLogPrintf(LOG_NOTE, "startup date: %s", timestr);
#ifdef PLATFORM_WIN32
// this function is only present on Vista+, so try to import it from kernel32 by hand
pfnCreateWaitableTimerExA = (CREATEWAITABLETIMEREXAFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateWaitableTimerExA");
if (pfnCreateWaitableTimerExA) {
// function exists, try to create a hires timer
timer = pfnCreateWaitableTimerExA(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
}
if (!timer) {
// no function or hires timers not supported, fallback to lower resolution timer
sysLogPrintf(LOG_WARNING, "SYS: hires waitable timers not available");
timer = CreateWaitableTimerA(NULL, FALSE, NULL);
}
#endif
}
s32 sysArgCheck(const char *arg)
{
for (s32 i = 1; i < sysArgc; ++i) {
if (!strcasecmp(sysArgv[i], arg)) {
return 1;
}
}
return 0;
}
const char *sysArgGetString(const char *arg)
{
for (s32 i = 1; i < sysArgc; ++i) {
if (!strcasecmp(sysArgv[i], arg)) {
if (i < sysArgc - 1) {
return sysArgv[i + 1];
}
}
}
return NULL;
}
s32 sysArgGetInt(const char *arg, s32 defval)
{
for (s32 i = 1; i < sysArgc; ++i) {
if (!strcasecmp(sysArgv[i], arg)) {
if (i < sysArgc - 1) {
return strtol(sysArgv[i + 1], NULL, 0);
}
}
}
return defval;
}
u64 sysGetMicroseconds(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return ((u64)tv.tv_sec * USEC_IN_SEC + (u64)tv.tv_usec) - startTick;
}
s32 sysLogIsOpen(void)
{
return (logPath[0] != '\0');
}
void sysLogPrintf(s32 level, const char *fmt, ...)
{
static const char *prefix[3] = {
"", "WARNING: ", "ERROR: "
};
char logmsg[2048];
va_list ap;
va_start(ap, fmt);
vsnprintf(logmsg, sizeof(logmsg), fmt, ap);
va_end(ap);
if (logPath[0]) {
FILE *f = fopen(logPath, "ab");
if (f) {
fprintf(f, "%s%s\n", prefix[level], logmsg);
fclose(f);
}
}
FILE *fout = (level == LOG_NOTE) ? stdout : stderr;
fprintf(fout, "%s%s\n", prefix[level], logmsg);
}
void sysFatalError(const char *fmt, ...)
{
static s32 alreadyCrashed = 0;
if (alreadyCrashed) {
abort();
}
char errmsg[2048] = { 0 };
alreadyCrashed = 1;
va_list ap;
va_start(ap, fmt);
vsnprintf(errmsg, sizeof(errmsg), fmt, ap);
va_end(ap);
sysLogPrintf(LOG_ERROR, "FATAL: %s", errmsg);
fflush(stdout);
fflush(stderr);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", errmsg, NULL);
exit(1);
}
void sysGetExecutablePath(char *outPath, const u32 outLen)
{
// try asking SDL
char *sdlPath = SDL_GetBasePath();
if (sdlPath && *sdlPath) {
// -1 to trim trailing slash
const u32 len = strlen(sdlPath) - 1;
if (len < outLen) {
memcpy(outPath, sdlPath, len);
outPath[len] = '\0';
}
} else if (sysArgc && sysArgv[0] && sysArgv[0][0]) {
// get exe path from argv[0]
strncpy(outPath, sysArgv[0], outLen - 1);
outPath[outLen - 1] = '\0';
} else if (outLen > 1) {
// give up, use working directory instead
outPath[0] = '.';
outPath[1] = '\0';
}
#ifdef PLATFORM_WIN32
// replace all backslashes with forward slashes, windows supports both
for (u32 i = 0; i < outLen && outPath[i]; ++i) {
if (outPath[i] == '\\') {
outPath[i] = '/';
}
}
#endif
SDL_free(sdlPath);
}
void sysGetHomePath(char *outPath, const u32 outLen)
{
// try asking SDL
char *sdlPath = SDL_GetPrefPath("", "perfectdark");
if (sdlPath && *sdlPath) {
// -1 to trim trailing slash
const u32 len = strlen(sdlPath) - 1;
if (len < outLen) {
memcpy(outPath, sdlPath, len);
outPath[len] = '\0';
}
} else if (outLen > 1) {
// give up, use working directory instead
outPath[0] = '.';
outPath[1] = '\0';
}
#ifdef PLATFORM_WIN32
// replace all backslashes with forward slashes, windows supports both
for (u32 i = 0; i < outLen && outPath[i]; ++i) {
if (outPath[i] == '\\') {
outPath[i] = '/';
}
}
#endif
SDL_free(sdlPath);
}
void *sysMemAlloc(const u32 size)
{
return malloc(size);
}
void *sysMemZeroAlloc(const u32 size)
{
return calloc(1, size);
}
void sysMemFree(void *ptr)
{
free(ptr);
}
void sysSleep(const s64 hns)
{
#ifdef PLATFORM_WIN32
static LARGE_INTEGER li;
li.QuadPart = -hns;
SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE);
WaitForSingleObject(timer, INFINITE);
#else
const struct timespec spec = { 0, hns * 100 };
nanosleep(&spec, NULL);
#endif
}
void sysCpuRelax(void)
{
DO_YIELD();
}