From 49398698b57e25353f1f97de8059d8b97d11c825 Mon Sep 17 00:00:00 2001 From: ToadKing Date: Wed, 17 Jul 2013 20:26:01 -0400 Subject: [PATCH] initial emscripten port (no audio/files, input broken) --- Makefile.emscripten | 152 ++++++++++++++++++ config.def.h | 4 + driver.c | 2 +- frontend/frontend_emscripten.c | 127 +++++++++++++++ gfx/context/emscriptenegl_ctx.c | 268 ++++++++++++++++++++++++++++++++ gfx/gfx_context.c | 10 +- gfx/gfx_context.h | 1 + input/input_common.c | 3 +- input/sdl_input.c | 15 +- performance.c | 6 + 10 files changed, 581 insertions(+), 7 deletions(-) create mode 100644 Makefile.emscripten create mode 100644 frontend/frontend_emscripten.c create mode 100644 gfx/context/emscriptenegl_ctx.c diff --git a/Makefile.emscripten b/Makefile.emscripten new file mode 100644 index 0000000000..79f77f2c56 --- /dev/null +++ b/Makefile.emscripten @@ -0,0 +1,152 @@ +TARGET = retroarch.html + +OBJ = frontend/frontend_emscripten.o \ + retroarch.o \ + file.o \ + file_path.o \ + driver.o \ + conf/config_file.o \ + settings.o \ + hash.o \ + dynamic.o \ + dynamic_dummy.o \ + message.o \ + rewind.o \ + movie.o \ + gfx/gfx_common.o \ + input/input_common.o \ + core_options.o \ + patch.o \ + compat/compat.o \ + screenshot.o \ + cheats.o \ + audio/utils.o \ + input/overlay.o \ + fifo_buffer.o \ + gfx/scaler/scaler.o \ + gfx/scaler/pixconv.o \ + gfx/scaler/scaler_int.o \ + gfx/scaler/filter.o \ + gfx/state_tracker.o \ + gfx/shader_parse.o \ + gfx/fonts/fonts.o \ + gfx/fonts/bitmapfont.o \ + gfx/image.o \ + audio/resampler.o \ + audio/sinc.o \ + audio/null.o \ + performance.o + +HAVE_OPENGL = 1 +HAVE_RGUI = 1 +HAVE_SDL = 1 +HAVE_SDL_IMAGE = 1 +HAVE_FREETYPE = 1 +HAVE_ZLIB = 1 +HAVE_FBO = 1 + +libretro ?= -lretro + +LIBS = -lm +DEFINES = -I. -DHAVE_SCREENSHOTS -DHAVE_NULLAUDIO -DHAVE_BSV_MOVIE -DPACKAGE_VERSION=\"0.9.9.3\" +LDFLAGS = -L. -static-libgcc -s TOTAL_MEMORY=268435456 -s FULL_ES2=1 + +ifeq ($(SCALER_NO_SIMD), 1) + DEFINES += -DSCALER_NO_SIMD +endif + +ifeq ($(PERF_TEST), 1) + DEFINES += -DPERF_TEST +endif + +ifeq ($(HAVE_RGUI), 1) + DEFINES += -DHAVE_RGUI + OBJ += frontend/menu/menu_common.o frontend/menu/rgui.o frontend/menu/history.o +endif + +ifeq ($(HAVE_SDL), 1) + OBJ += input/sdl_input.o + LIBS += -lSDL + DEFINES += -ISDL -DHAVE_SDL +endif + +ifeq ($(HAVE_THREADS), 1) + OBJ += autosave.o thread.o gfx/thread_wrapper.o + DEFINES += -DHAVE_THREADS +endif + +ifeq ($(HAVE_OPENGL), 1) + OBJ += gfx/gl.o gfx/math/matrix.o gfx/fonts/gl_font.o gfx/fonts/gl_raster_font.o gfx/gfx_context.o gfx/context/emscriptenegl_ctx.o gfx/shader_glsl.o + DEFINES += -DHAVE_OPENGL -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DHAVE_EGL -DHAVE_OVERLAY -DHAVE_GLSL +endif + +ifeq ($(HAVE_ZLIB), 1) + OBJ += gfx/rpng/rpng.o file_extract.o + DEFINES += -DHAVE_ZLIB + ifeq ($(WANT_MINIZ), 1) + OBJ += deps/miniz/miniz.o + DEFINES += -DWANT_MINIZ + else + LIBS += -lz + DEFINES += -DHAVE_ZLIB_DEFLATE + endif +endif + +LIBS += $(libretro) + +ifeq ($(HAVE_FBO), 1) + DEFINES += -DHAVE_FBO +endif + +ifneq ($(V), 1) + Q := @ +endif + +ifeq ($(DEBUG), 1) + LDFLAGS += -O0 -g -s LABEL_DEBUG=1 +else + LDFLAGS += -O2 -ffast-math +endif + +CFLAGS += -Wall -Wno-unused-result -Wno-unused-variable -I. -std=gnu99 + +all: $(TARGET) + +$(TARGET): $(OBJ) + @$(if $(Q), $(shell echo echo LD $@),) + $(Q)$(LD) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + +%.o: %.c + @$(if $(Q), $(shell echo echo CC $<),) + $(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + +%.o: %.cpp + @$(if $(Q), $(shell echo echo CXX $<),) + $(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $< + +clean: + rm -f *.o + rm -f deps/miniz/*.o + rm -f frontend/*.o + rm -f frontend/menu/*.o + rm -f audio/*.o + rm -f audio/xaudio-c/*.o + rm -f compat/*.o + rm -f compat/rxml/*.o + rm -f conf/*.o + rm -f gfx/scaler/*.o + rm -f gfx/*.o + rm -f gfx/d3d9/*.o + rm -f gfx/context/*.o + rm -f gfx/math/*.o + rm -f gfx/fonts/*.o + rm -f gfx/py_state/*.o + rm -f gfx/rpng/*.o + rm -f record/*.o + rm -f input/*.o + rm -f $(TARGET) + rm -f retroarch-joyconfig.exe + rm -f tools/*.o + +.PHONY: all clean + diff --git a/config.def.h b/config.def.h index c11957648c..ae2f7523b7 100644 --- a/config.def.h +++ b/config.def.h @@ -128,6 +128,8 @@ enum #define AUDIO_DEFAULT_DRIVER AUDIO_SL #elif defined(HAVE_DSOUND) #define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND +#elif defined(EMSCRIPTEN) +#define AUDIO_DEFAULT_DRIVER AUDIO_NULL #elif defined(HAVE_SDL) #define AUDIO_DEFAULT_DRIVER AUDIO_SDL #elif defined(HAVE_XAUDIO) @@ -195,6 +197,8 @@ enum #define EXT_EXECUTABLES "xex|XEX" #elif defined(GEKKO) #define EXT_EXECUTABLES "dol|DOL" +#else +#define EXT_EXECUTABLES "???" #endif #endif diff --git a/driver.c b/driver.c index 790e23e1f6..338dd28ff6 100644 --- a/driver.c +++ b/driver.c @@ -62,7 +62,7 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_JACK &audio_jack, #endif -#ifdef HAVE_SDL +#if defined(HAVE_SDL) && !defined(EMSCRIPTEN) &audio_sdl, #endif #ifdef HAVE_XAUDIO diff --git a/frontend/frontend_emscripten.c b/frontend/frontend_emscripten.c new file mode 100644 index 0000000000..949870e0c5 --- /dev/null +++ b/frontend/frontend_emscripten.c @@ -0,0 +1,127 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include "../general.h" +#include "../conf/config_file.h" +#include "../file.h" + +#ifdef HAVE_RGUI +#include "../frontend/menu/rgui.h" +#endif + +#if defined(HAVE_RGUI) || defined(HAVE_RMENU) || defined(HAVE_RMENU_XUI) +#define HAVE_MENU +#else +#undef HAVE_MENU +#endif + +static bool menuloop; + +void mainloop(void) +{ + if (g_extern.system.shutdown) + { + RARCH_ERR("Exit...\n"); + emscripten_cancel_main_loop(); + } + else if (menuloop) + { + if (!menu_iterate()) + { + menuloop = false; + driver_set_nonblock_state(driver.nonblock_state); + + if (driver.audio_data && !audio_start_func()) + { + RARCH_ERR("Failed to resume audio driver. Will continue without audio.\n"); + g_extern.audio_active = false; + } + + g_extern.lifecycle_mode_state &= ~(1ULL << MODE_MENU); + } + } + else if (g_extern.lifecycle_mode_state & (1ULL << MODE_LOAD_GAME)) + { + load_menu_game_prepare(); + + // If ROM load fails, we exit RetroArch. On console it might make more sense to go back to menu though ... + if (load_menu_game()) + g_extern.lifecycle_mode_state |= (1ULL << MODE_GAME); + else + { +#ifdef RARCH_CONSOLE + g_extern.lifecycle_mode_state |= (1ULL << MODE_MENU); +#else + return; +#endif + } + + g_extern.lifecycle_mode_state &= ~(1ULL << MODE_LOAD_GAME); + } + else if (g_extern.lifecycle_mode_state & (1ULL << MODE_GAME)) + { + bool r; + if (g_extern.is_paused && !g_extern.is_oneshot) + r = rarch_main_idle_iterate(); + else + r = rarch_main_iterate(); + if (!r) + g_extern.lifecycle_mode_state &= ~(1ULL << MODE_GAME); + } + else if (g_extern.lifecycle_mode_state & (1ULL << MODE_MENU)) + { + g_extern.lifecycle_mode_state |= 1ULL << MODE_MENU_PREINIT; + // Menu should always run with vsync on. + video_set_nonblock_state_func(false); + + if (driver.audio_data) + audio_stop_func(); + + menuloop = true; + } + else + { + g_extern.system.shutdown = true; + } +} + +int main(int argc, char *argv[]) +{ + emscripten_set_canvas_size(800, 600); + + rarch_main_clear_state(); + rarch_init_msg_queue(); + + char *_argv[] = { "retroarch", "--menu", "-v" }; + + int init_ret; + if ((init_ret = rarch_main_init(3, _argv))) return init_ret; + +#ifdef HAVE_MENU + menu_init(); + g_extern.lifecycle_mode_state |= 1ULL << MODE_GAME; + + // If we started a ROM directly from command line, + // push it to ROM history. + if (!g_extern.libretro_dummy) + menu_rom_history_push_current(); +#endif + + emscripten_set_main_loop(mainloop, 0, 0); + + return 0; +} diff --git a/gfx/context/emscriptenegl_ctx.c b/gfx/context/emscriptenegl_ctx.c new file mode 100644 index 0000000000..2674e8bcc6 --- /dev/null +++ b/gfx/context/emscriptenegl_ctx.c @@ -0,0 +1,268 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2012 - Michael Lelli + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +// VideoCore context, for Rasperry Pi. + +#include "../../driver.h" +#include "../gfx_context.h" +#include "../gl_common.h" +#include "../gfx_common.h" + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + +#include +#include + +#include +#include +#include +#include + +static EGLContext g_egl_ctx; +static EGLSurface g_egl_surf; +static EGLDisplay g_egl_dpy; +static EGLConfig g_config; +static bool g_quit; + +static bool g_inited; + +static unsigned g_fb_width; +static unsigned g_fb_height; + +static void gfx_ctx_swap_interval(unsigned interval) +{ + // no way to control vsync in WebGL + (void)interval; +} + +static void gfx_ctx_check_window(bool *quit, + bool *resize, unsigned *width, unsigned *height, unsigned frame_count) +{ + (void)frame_count; + (void)width; + (void)height; + + *resize = false; + *quit = g_quit; +} + +static void gfx_ctx_swap_buffers(void) +{ + eglSwapBuffers(g_egl_dpy, g_egl_surf); +} + +static void gfx_ctx_set_resize(unsigned width, unsigned height) +{ + (void)width; + (void)height; +} + +static void gfx_ctx_update_window_title(void) +{ + char buf[128]; + gfx_get_fps(buf, sizeof(buf), false); +} + +static void gfx_ctx_get_video_size(unsigned *width, unsigned *height) +{ + *width = g_fb_width; + *height = g_fb_height; +} + +static void gfx_ctx_destroy(void); + +static bool gfx_ctx_init(void) +{ + EGLint width; + EGLint height; + + RARCH_LOG("[VC/EMSCRIPTEN]: Initializing...\n"); + if (g_inited) + { + RARCH_ERR("[VC/EMSCRIPTEN]: Attempted to re-initialize driver.\n"); + return true; + } + + EGLint num_config; + + static const EGLint attribute_list[] = + { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + static const EGLint context_attributes[] = + { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + // get an EGL display connection + g_egl_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!g_egl_dpy) + goto error; + + // initialize the EGL display connection + if (!eglInitialize(g_egl_dpy, NULL, NULL)) + goto error; + + // get an appropriate EGL frame buffer configuration + if (!eglChooseConfig(g_egl_dpy, attribute_list, &g_config, 1, &num_config)) + goto error; + + // create an EGL rendering context + g_egl_ctx = eglCreateContext(g_egl_dpy, g_config, EGL_NO_CONTEXT, context_attributes); + if (!g_egl_ctx) + goto error; + + // create an EGL window surface + g_egl_surf = eglCreateWindowSurface(g_egl_dpy, g_config, 0, NULL); + if (!g_egl_surf) + goto error; + + // connect the context to the surface + if (!eglMakeCurrent(g_egl_dpy, g_egl_surf, g_egl_surf, g_egl_ctx)) + goto error; + + eglQuerySurface(g_egl_dpy, g_egl_surf, EGL_WIDTH, &width); + eglQuerySurface(g_egl_dpy, g_egl_surf, EGL_HEIGHT, &height); + g_fb_width = width; + g_fb_height = height; + RARCH_LOG("[VC/EMSCRIPTEN]: Dimensions: %ux%u\n", width, height); + + return true; + +error: + gfx_ctx_destroy(); + return false; +} + +static bool gfx_ctx_set_video_mode( + unsigned width, unsigned height, + bool fullscreen) +{ + if (g_inited) + return false; + + g_inited = true; + return true; +} + +static bool gfx_ctx_bind_api(enum gfx_ctx_api api) +{ + switch (api) + { + case GFX_CTX_OPENGL_ES_API: + return eglBindAPI(EGL_OPENGL_ES_API); + default: + return false; + } +} + +static void gfx_ctx_destroy(void) +{ + if (g_egl_dpy) + { + if (g_egl_ctx) + { + eglMakeCurrent(g_egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(g_egl_dpy, g_egl_ctx); + } + + if (g_egl_surf) + { + eglDestroySurface(g_egl_dpy, g_egl_surf); + } + + eglMakeCurrent(g_egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglMakeCurrent(g_egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglTerminate(g_egl_dpy); + } + + g_egl_ctx = NULL; + g_egl_surf = NULL; + g_egl_dpy = NULL; + g_config = 0; + g_inited = false; +} + +static void gfx_ctx_input_driver(const input_driver_t **input, void **input_data) +{ + *input = NULL; + + if (SDL_Init(SDL_INIT_VIDEO) != 0) + return; + + void *sdlinput = input_sdl.init(); + + if (sdlinput) + { + *input = &input_sdl; + *input_data = sdlinput; + } +} + +static bool gfx_ctx_has_focus(void) +{ + return g_inited; +} + +static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) +{ + return SDL_GL_GetProcAddress(symbol); +} + +static float gfx_ctx_translate_aspect(unsigned width, unsigned height) +{ + return (float)width / height; +} + +static bool gfx_ctx_init_egl_image_buffer(const video_info_t *video) +{ + return false; +} + +static bool gfx_ctx_write_egl_image(const void *frame, unsigned width, unsigned height, unsigned pitch, bool rgb32, unsigned index, void **image_handle) +{ + return false; +} + +const gfx_ctx_driver_t gfx_ctx_emscripten = { + gfx_ctx_init, + gfx_ctx_destroy, + gfx_ctx_bind_api, + gfx_ctx_swap_interval, + gfx_ctx_set_video_mode, + gfx_ctx_get_video_size, + gfx_ctx_translate_aspect, + gfx_ctx_update_window_title, + gfx_ctx_check_window, + gfx_ctx_set_resize, + gfx_ctx_has_focus, + gfx_ctx_swap_buffers, + gfx_ctx_input_driver, + gfx_ctx_get_proc_address, + gfx_ctx_init_egl_image_buffer, + gfx_ctx_write_egl_image, + NULL, + "emscripten", +}; diff --git a/gfx/gfx_context.c b/gfx/gfx_context.c index 47745ca3f3..ba0aedd0fe 100644 --- a/gfx/gfx_context.c +++ b/gfx/gfx_context.c @@ -13,6 +13,7 @@ * If not, see . */ +#include "../general.h" #include "gfx_context.h" #include @@ -51,14 +52,17 @@ static const gfx_ctx_driver_t *gfx_ctx_drivers[] = { #if defined(IOS) || defined(OSX) //< Don't use __APPLE__ as it breaks basic SDL builds &gfx_ctx_apple, #endif -#if defined(HAVE_SDL) && defined(HAVE_OPENGL) +#if defined(HAVE_SDL) && defined(HAVE_OPENGL) && !defined(EMSCRIPTEN) &gfx_ctx_sdl_gl, #endif +#ifdef EMSCRIPTEN + &gfx_ctx_emscripten, +#endif }; const gfx_ctx_driver_t *gfx_ctx_find_driver(const char *ident) { - for (unsigned i = 0; i < sizeof(gfx_ctx_drivers) / sizeof(gfx_ctx_drivers[0]); i++) + for (unsigned i = 0; i < ARRAY_SIZE(gfx_ctx_drivers); i++) { if (strcmp(gfx_ctx_drivers[i]->ident, ident) == 0) return gfx_ctx_drivers[i]; @@ -69,7 +73,7 @@ const gfx_ctx_driver_t *gfx_ctx_find_driver(const char *ident) const gfx_ctx_driver_t *gfx_ctx_init_first(enum gfx_ctx_api api) { - for (unsigned i = 0; i < sizeof(gfx_ctx_drivers) / sizeof(gfx_ctx_drivers[0]); i++) + for (unsigned i = 0; i < ARRAY_SIZE(gfx_ctx_drivers); i++) { if (gfx_ctx_drivers[i]->bind_api(api)) { diff --git a/gfx/gfx_context.h b/gfx/gfx_context.h index fa70e76e73..6e0219f382 100644 --- a/gfx/gfx_context.h +++ b/gfx/gfx_context.h @@ -108,6 +108,7 @@ extern const gfx_ctx_driver_t gfx_ctx_wgl; extern const gfx_ctx_driver_t gfx_ctx_videocore; extern const gfx_ctx_driver_t gfx_ctx_bbqnx; extern const gfx_ctx_driver_t gfx_ctx_apple; +extern const gfx_ctx_driver_t gfx_ctx_emscripten; extern const gfx_ctx_driver_t gfx_ctx_null; const gfx_ctx_driver_t *gfx_ctx_find_driver(const char *ident); // Finds driver with ident. Does not initialize. diff --git a/input/input_common.c b/input/input_common.c index 12be6eaf76..e58eede808 100644 --- a/input/input_common.c +++ b/input/input_common.c @@ -16,6 +16,7 @@ #include "input_common.h" #include #include +#include #include "../general.h" #include "../driver.h" @@ -47,7 +48,7 @@ static const rarch_joypad_driver_t *joypad_drivers[] = { #if defined(__linux) && !defined(ANDROID) &linuxraw_joypad, #endif -#ifdef HAVE_SDL +#if defined(HAVE_SDL) && !defined(EMSCRIPTEN) &sdl_joypad, #endif #endif diff --git a/input/sdl_input.c b/input/sdl_input.c index d438c3f8a5..1c332b8396 100644 --- a/input/sdl_input.c +++ b/input/sdl_input.c @@ -24,6 +24,10 @@ #include "../libretro.h" #include "input_common.h" +#if !(SDL_MAJOR_VERSION <= 1 && SDL_MINOR_VERSION <= 2) +#define SDL_GetKeyState SDL_GetKeyboardState +#endif + typedef struct sdl_input { const rarch_joypad_driver_t *joypad; @@ -51,9 +55,8 @@ static bool sdl_key_pressed(int key) int sym = input_translate_rk_to_keysym((enum retro_key)key); - int num_keys; + int num_keys = 0xFFFF; Uint8 *keymap = SDL_GetKeyState(&num_keys); - if (sym < 0 || sym >= num_keys) return false; return keymap[sym]; @@ -216,16 +219,24 @@ static void sdl_input_free(void *data) static void sdl_poll_mouse(sdl_input_t *sdl) { + (void)sdl; +#ifndef EMSCRIPTEN Uint8 btn = SDL_GetRelativeMouseState(&sdl->mouse_x, &sdl->mouse_y); SDL_GetMouseState(&sdl->mouse_abs_x, &sdl->mouse_abs_y); sdl->mouse_l = SDL_BUTTON(SDL_BUTTON_LEFT) & btn ? 1 : 0; sdl->mouse_r = SDL_BUTTON(SDL_BUTTON_RIGHT) & btn ? 1 : 0; sdl->mouse_m = SDL_BUTTON(SDL_BUTTON_MIDDLE) & btn ? 1 : 0; +#endif } static void sdl_input_poll(void *data) { +#ifdef EMSCRIPTEN + SDL_Event event; + while (SDL_PollEvent(&event)); +#else SDL_PumpEvents(); +#endif sdl_input_t *sdl = (sdl_input_t*)data; input_joypad_poll(sdl->joypad); diff --git a/performance.c b/performance.c index 7829fe265d..faad97135d 100644 --- a/performance.c +++ b/performance.c @@ -58,6 +58,10 @@ #include #endif +#ifdef EMSCRIPTEN +#include +#endif + #ifdef PERF_TEST #define MAX_COUNTERS 64 static struct rarch_perf_counter *perf_counters[MAX_COUNTERS]; @@ -145,6 +149,8 @@ rarch_time_t rarch_get_time_usec(void) if (clock_gettime(CLOCK_MONOTONIC, &tv) < 0) return 0; return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000; +#elif defined(EMSCRIPTEN) + return emscripten_get_now() * 1000; #else #error "Your platform does not have a timer function implemented in rarch_get_time_usec(). Cannot continue." #endif