diff --git a/src/character.h b/src/character.h index dbcb940..4e0ec19 100644 --- a/src/character.h +++ b/src/character.h @@ -310,10 +310,16 @@ struct Character : Controller { void bakeEnvironment(Texture *&environment) { flags.invisible = true; if (!environment) { - environment = new Texture(256, 256, 1, FMT_RGBA, OPT_CUBEMAP | OPT_MIPMAPS | OPT_TARGET); + uint32 opt = OPT_CUBEMAP | OPT_TARGET; + #ifdef USE_CUBEMAP_MIPS + opt |= OPT_MIPMAPS; + #endif + environment = new Texture(256, 256, 1, FMT_RGB16, opt); } game->renderEnvironment(getRoomIndex(), pos - vec3(0.0f, 384.0f, 0.0f), &environment); - environment->generateMipMap(); + #ifdef USE_CUBEMAP_MIPS + environment->generateMipMap(); + #endif flags.invisible = false; } }; diff --git a/src/core.h b/src/core.h index 6ffc4ba..0b1dd1d 100644 --- a/src/core.h +++ b/src/core.h @@ -12,6 +12,8 @@ #define OS_FILEIO_CACHE #define OS_PTHREAD_MT +#define USE_CUBEMAP_MIPS + #ifdef WIN32 #define _OS_WIN 1 #define _GAPI_GL 1 @@ -64,6 +66,15 @@ #define _OS_BITTBOY 1 #define _OS_LINUX 1 #define _GAPI_SW 1 +#elif __GCW0__ + #define _OS_GCW0 1 + #define _GAPI_GL 1 + #define _GAPI_GLES 1 + + #define DYNGEOM_NO_VBO + + // etnaviv driver has a bug with cubemap mips generator + #undef USE_CUBEMAP_MIPS #elif __linux__ #define _OS_LINUX 1 #define _GAPI_GL 1 @@ -129,7 +140,12 @@ #define SPLIT_BY_CLUT #endif #else - #define MERGE_MODELS + // current etnaviv driver implementation uses uncompatible Mesa GLSL compiler + // it produce unimplemented TRUNC/ARL instructions instead of F2I + // so we can't use joints indexing in the shader (see MESH_SKINNING) + #ifndef _OS_GCW0 + #define MERGE_MODELS + #endif #define MERGE_SPRITES #define GENERATE_WATER_PLANE #endif @@ -137,8 +153,10 @@ #include "utils.h" // muse be equal with base shader -#ifdef __OS_3DS +#if defined(_OS_3DS) #define SHADOW_TEX_SIZE 512 +#elif defined(_OS_GCW0) + #define SHADOW_TEX_SIZE 256 #else #define SHADOW_TEX_SIZE 2048 #endif @@ -235,6 +253,7 @@ namespace Core { bool tex3D; bool texRG; bool texBorder; + bool texMaxLevel; bool colorFloat, texFloat, texFloatLinear; bool colorHalf, texHalf, texHalfLinear; #ifdef PROFILE @@ -454,7 +473,11 @@ struct PSO { uint32 renderState; }; -typedef uint32 Index; +#if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_MAC) || defined(_OS_WEB) + typedef uint32 Index; +#else + typedef uint16 Index; +#endif struct Edge { Index a, b; @@ -738,8 +761,9 @@ namespace Core { lightStackCount = 0; memset(&support, 0, sizeof(support)); - support.texMinSize = 1; - Core::support.derivatives = true; + support.texMinSize = 1; + support.texMaxLevel = true; + support.derivatives = true; #ifdef USE_INFLATE tinf_init(); @@ -765,6 +789,7 @@ namespace Core { LOG(" 3D textures : %s\n", support.tex3D ? "true" : "false"); LOG(" RG textures : %s\n", support.texRG ? "true" : "false"); LOG(" border color : %s\n", support.texBorder ? "true" : "false"); + LOG(" max level : %s\n", support.texMaxLevel ? "true" : "false"); LOG(" anisotropic : %d\n", support.maxAniso); LOG(" float textures : float = %s, half = %s\n", support.colorFloat ? "full" : (support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false"), @@ -899,6 +924,13 @@ namespace Core { settings.detail.setLighting (Core::Settings::MEDIUM); #endif + #if defined(_OS_GCW0) + settings.detail.setFilter (Core::Settings::MEDIUM); + settings.detail.setShadows (Core::Settings::MEDIUM); + settings.detail.setLighting (Core::Settings::MEDIUM); + settings.audio.subtitles = false; + #endif + #ifdef _OS_PSC settings.detail.setLighting (Core::Settings::MEDIUM); settings.detail.setShadows (Core::Settings::LOW); diff --git a/src/game.h b/src/game.h index 4405393..cfa8e0d 100644 --- a/src/game.h +++ b/src/game.h @@ -58,7 +58,7 @@ namespace Game { level->init(playLogo, playVideo); UI::game = level; - #if !defined(_OS_PSP) && !defined(_OS_CLOVER) + #if !defined(INV_GAMEPAD_ONLY) UI::helpTipTime = 5.0f; #endif delete lvl; diff --git a/src/gapi/gl.h b/src/gapi/gl.h index 055df19..9ed2e83 100644 --- a/src/gapi/gl.h +++ b/src/gapi/gl.h @@ -116,6 +116,50 @@ #elif defined(_OS_PSC) #include #include + extern EGLDisplay display; +#elif defined(_OS_GCW0) + #include + #include + #include + #include + + #define GL_TEXTURE_MAX_LEVEL GL_TEXTURE_MAX_LEVEL_APPLE + #define GL_TEXTURE_3D 0 + #define GL_TEXTURE_WRAP_R 0 + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E + + #undef GL_RG + #undef GL_RG32F + #undef GL_RG16F + #undef GL_RGBA32F + #undef GL_RGBA16F + #undef GL_HALF_FLOAT + + #define GL_RG GL_RGBA + #define GL_RGBA32F GL_RGBA + #define GL_RGBA16F GL_RGBA + #define GL_RG32F GL_RGBA + #define GL_RG16F GL_RGBA + #define GL_HALF_FLOAT GL_HALF_FLOAT_OES + + #define glTexImage3D(...) 0 + + #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES + + #define PFNGLGENVERTEXARRAYSPROC PFNGLGENVERTEXARRAYSOESPROC + #define PFNGLDELETEVERTEXARRAYSPROC PFNGLDELETEVERTEXARRAYSOESPROC + #define PFNGLBINDVERTEXARRAYPROC PFNGLBINDVERTEXARRAYOESPROC + #define PFNGLGETPROGRAMBINARYPROC PFNGLGETPROGRAMBINARYOESPROC + #define PFNGLPROGRAMBINARYPROC PFNGLPROGRAMBINARYOESPROC + + #define glGenVertexArrays glGenVertexArraysOES + #define glDeleteVertexArrays glDeleteVertexArraysOES + #define glBindVertexArray glBindVertexArrayOES + #define glGetProgramBinary glGetProgramBinaryOES + #define glProgramBinary glProgramBinaryOES + extern EGLDisplay display; #elif defined(_OS_RPI) || defined(_OS_CLOVER) #include @@ -158,7 +202,7 @@ #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES #define glGetProgramBinary(...) #define glProgramBinary(...) - + extern EGLDisplay display; #elif _OS_SWITCH #define GL_GLEXT_PROTOTYPES @@ -248,21 +292,17 @@ #define glProgramBinary(...) #endif -#if defined(_OS_WIN) || defined(_OS_LINUX) +#if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_GCW0) - #ifdef _OS_ANDROID - #define GetProc(x) dlsym(libGL, x); - #else - void* GetProc(const char *name) { - #ifdef _OS_WIN - return (void*)wglGetProcAddress(name); - #elif _OS_LINUX - return (void*)glXGetProcAddress((GLubyte*)name); - #else // EGL - return (void*)eglGetProcAddress(name); - #endif - } - #endif + void* GetProc(const char *name) { + #ifdef _OS_WIN + return (void*)wglGetProcAddress(name); + #elif _OS_LINUX + return (void*)glXGetProcAddress((GLubyte*)name); + #else // EGL + return (void*)eglGetProcAddress(name); + #endif + } #define GetProcOGL(x) x=(decltype(x))GetProc(#x); @@ -341,15 +381,17 @@ PFNGLBUFFERSUBDATAARBPROC glBufferSubData; #endif +// Vertex Arrays PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; PFNGLBINDVERTEXARRAYPROC glBindVertexArray; +// Binary shaders PFNGLGETPROGRAMBINARYPROC glGetProgramBinary; PFNGLPROGRAMBINARYPROC glProgramBinary; -#endif -#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) && !defined(_OS_PSC) && !defined(_OS_IOS) && !defined(_OS_ANDROID) && !defined(__SDL2__) - PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; + #if defined(_GAPI_GLES) + PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; + #endif #endif #ifdef PROFILE @@ -560,7 +602,18 @@ namespace GAPI { sprintf(defines + strlen(defines), "#define %s\n", DefineName[def[i]]); } - #if defined(_OS_RPI) || defined(_OS_CLOVER) || (defined (__SDL2__) && defined (_GAPI_GLES)) + sprintf(defines + strlen(defines), "#define SHADOW_SIZE %d.0\n", SHADOW_TEX_SIZE); + + #ifdef MERGE_MODELS + strcat(defines, "#define MESH_SKINNING\n"); + #endif + + // etnaviv driver has no sin/cos/abs implementation + #if !defined(_OS_GCW0) + strcat(defines, "#define VERT_CAUSTICS\n"); + #endif + + #if defined(_OS_RPI) || defined(_OS_CLOVER) || defined(_OS_GCW0) || (defined (__SDL2__) && defined(_GAPI_GLES)) strcat(defines, "#define OPT_VLIGHTPROJ\n"); strcat(defines, "#define OPT_VLIGHTVEC\n"); strcat(defines, "#define OPT_SHADOW_ONETAP\n"); @@ -858,9 +911,9 @@ namespace GAPI { glGenerateMipmap(target); if ((opt & (OPT_VOLUME | OPT_CUBEMAP | OPT_NEAREST)) == 0 && (Core::support.maxAniso > 0)) { glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(int(Core::support.maxAniso), 8)); - #if !defined(_OS_RPI) && !defined(_OS_CLOVER) && !(defined (__SDL2__) && defined (_GAPI_GLES))// TODO - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 3); - #endif + if (Core::support.texMaxLevel) { + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 3); + } } } @@ -1062,7 +1115,7 @@ namespace GAPI { //void *libGL = dlopen("libGLESv2.so", RTLD_LAZY); #endif - #if defined(_OS_WIN) || defined(_OS_LINUX) + #if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_GCW0) #ifdef _OS_WIN GetProcOGL(glActiveTexture); #endif @@ -1135,10 +1188,11 @@ namespace GAPI { GetProcOGL(glGenVertexArrays); GetProcOGL(glDeleteVertexArrays); GetProcOGL(glBindVertexArray); + GetProcOGL(glGetProgramBinary); GetProcOGL(glProgramBinary); - #ifdef _GAPI_GLES + #if defined(_GAPI_GLES) GetProcOGL(glDiscardFramebufferEXT); #endif #endif @@ -1162,7 +1216,6 @@ namespace GAPI { } } */ - #ifndef FFP bool GLES3 = false; #ifdef _OS_WEB @@ -1173,7 +1226,7 @@ namespace GAPI { #if defined(__SDL2__) SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &GLES_VERSION); #else - #if defined(_OS_RPI) || defined(_OS_CLOVER) + #if defined(_OS_RPI) || defined(_OS_CLOVER) || defined(_OS_GCW0) GLES_VERSION = 2; #else glGetIntegerv(GL_MAJOR_VERSION, &GLES_VERSION); @@ -1191,9 +1244,11 @@ namespace GAPI { support.VAO = GLES3 || extSupport(ext, "_vertex_array_object"); support.depthTexture = GLES3 || extSupport(ext, "_depth_texture"); support.shadowSampler = _GL_EXT_shadow_samplers || _GL_ARB_shadow; - support.discardFrame = extSupport(ext, "_discard_framebuffer"); + support.discardFrame = extSupport(ext, "_discard_framebuffer") && (glDiscardFramebufferEXT != NULL); support.texNPOT = GLES3 || extSupport(ext, "_texture_npot") || extSupport(ext, "_texture_non_power_of_two"); support.texRG = GLES3 || extSupport(ext, "_texture_rg "); // hope that isn't last extension in string ;) + support.texMaxLevel = GLES3 || extSupport(ext, "_texture_max_level"); + #ifdef _GAPI_GLES2 // TODO support.shaderBinary = false; support.VAO = false; @@ -1400,8 +1455,9 @@ namespace GAPI { glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); } else { GLenum texTarget = GL_TEXTURE_2D; - if (target->opt & OPT_CUBEMAP) + if (target->opt & OPT_CUBEMAP) { texTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + } bool depth = target->fmt == FMT_DEPTH || target->fmt == FMT_SHADOW; @@ -1412,7 +1468,7 @@ namespace GAPI { glFramebufferRenderbuffer (GL_FRAMEBUFFER, depth ? GL_COLOR_ATTACHMENT0 : GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rtCache[!depth].items[rtIndex].ID); GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { - LOG("status: %d\n", (int)status); + LOG("status: 0x%04X\n", (int)status); } } } @@ -1428,9 +1484,7 @@ namespace GAPI { #ifdef _OS_ANDROID glInvalidateFramebuffer(GL_FRAMEBUFFER, count, discard); #else - #if !defined(__SDL2__) && !defined(_OS_PSC) - glDiscardFramebufferEXT(GL_FRAMEBUFFER, count, discard); - #endif + glDiscardFramebufferEXT(GL_FRAMEBUFFER, count, discard); #endif } } diff --git a/src/inventory.h b/src/inventory.h index 720e22f..9695ce7 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -133,11 +133,13 @@ static const OptionItem optDetail[] = { OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_SHADOWS, SETTINGS( detail.shadows ), STR_QUALITY_LOW, 0, 2 ), OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_WATER, SETTINGS( detail.water ), STR_QUALITY_LOW, 0, 2 ), OptionItem( OptionItem::TYPE_PARAM, STR_OPT_SIMPLE_ITEMS, SETTINGS( detail.simple ), STR_OFF, 0, 1 ), +#if !defined(_OS_3DS) && !defined(_OS_GCW0) OptionItem( OptionItem::TYPE_PARAM, STR_OPT_RESOLUTION, SETTINGS( detail.scale ), STR_SCALE_100, 0, 3 ), +#endif #if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_PSP) || defined(_OS_RPI) || defined(_OS_PSV) OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_VSYNC, SETTINGS( detail.vsync ), STR_OFF, 0, 1 ), #endif -#if !defined(_OS_PSP) && !defined(_OS_PSV) && !defined(_OS_3DS) +#if !defined(_OS_PSP) && !defined(_OS_PSV) && !defined(_OS_3DS) && !defined(_OS_GCW0) OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_STEREO, SETTINGS( detail.stereo ), STR_NO_STEREO, 0, #if defined(_OS_WIN) || defined(_OS_ANDROID) 4 /* with VR option */ @@ -166,9 +168,12 @@ static const OptionItem optSound[] = { #define INV_GAMEPAD_ONLY #endif -#if defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) +#if defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) || defined(_OS_GCW0) #define INV_SINGLE_PLAYER #define INV_GAMEPAD_ONLY +#endif + +#ifdef INV_SINGLE_PLAYER #define INV_CTRL_START_OPTION 1 #else #define INV_CTRL_START_OPTION 2 @@ -1192,7 +1197,7 @@ struct Inventory { else if (Input::down[ikDown] || joy.down[jkDown] || joy.L.y > 0.5f) key = cDown; - #if defined(_OS_SWITCH) || defined(_OS_3DS) + #if defined(_OS_SWITCH) || defined(_OS_3DS) || defined(_OS_GCW0) // swap A/B keys for Nintendo (Japanese) UX style if (Input::touchTimerVis == 0.0f) { if (key == cAction) { @@ -1387,9 +1392,11 @@ struct Inventory { background[0] = NULL; } - for (int i = 0; i < COUNT(background); i++) - if (!background[i]) - background[i] = new Texture(INV_BG_SIZE, INV_BG_SIZE, 1, FMT_RGBA, OPT_TARGET); + for (int i = 0; i < COUNT(background); i++) { + if (!background[i]) { + background[i] = new Texture(INV_BG_SIZE, INV_BG_SIZE, 1, FMT_RGB16, OPT_TARGET); + } + } return background[view]; } @@ -2061,7 +2068,7 @@ struct Inventory { const char *bSelect = STR[STR_KEY_FIRST + ikEnter]; const char *bBack = STR[STR_KEY_FIRST + Core::settings.controls[playerIndex].keys[cInventory].key]; - #if defined(_OS_SWITCH) || defined(_OS_3DS) + #if defined(_OS_SWITCH) || defined(_OS_3DS) || defined(_OS_GCW0) bSelect = "A"; bBack = "B"; #endif diff --git a/src/level.h b/src/level.h index cc4b005..a89352c 100644 --- a/src/level.h +++ b/src/level.h @@ -1744,7 +1744,9 @@ struct Level : IGame { if (e.type == TR::Entity::CRYSTAL) { Crystal *c = (Crystal*)e.controller; renderEnvironment(c->getRoomIndex(), c->pos - vec3(0, 512, 0), &c->environment); - c->environment->generateMipMap(); + #ifdef USE_CUBEMAP_MIPS + c->environment->generateMipMap(); + #endif } } } diff --git a/src/mesh.h b/src/mesh.h index 4f9b09c..5ed463b 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -1565,6 +1565,8 @@ struct MeshBuilder { Core::mModel.identity(); Core::mModel.setRot(basis.rot); Core::mModel.setPos(basis.pos); + #else + Core::active.shader->setParam(uBasis, *(vec4*)&basis, 2); #endif #endif diff --git a/src/objects.h b/src/objects.h index a9ecb4f..1983340 100644 --- a/src/objects.h +++ b/src/objects.h @@ -850,7 +850,11 @@ struct Crystal : Controller { Texture *environment; Crystal(IGame *game, int entity) : Controller(game, entity) { - environment = new Texture(64, 64, 1, FMT_RGBA, OPT_CUBEMAP | OPT_MIPMAPS | OPT_TARGET); + uint32 opt = OPT_CUBEMAP | OPT_TARGET; + #ifdef USE_CUBEMAP_MIPS + opt |= OPT_MIPMAPS; + #endif + environment = new Texture(64, 64, 1, FMT_RGB16, opt); activate(); } diff --git a/src/platform/gcw0/build.sh b/src/platform/gcw0/build.sh new file mode 100755 index 0000000..020cb07 --- /dev/null +++ b/src/platform/gcw0/build.sh @@ -0,0 +1,3 @@ +set -e +/opt/gcw0-toolchain/usr/bin/mipsel-gcw0-linux-uclibc-g++ -o OpenLara -D__GCW0__ -std=c++11 -O3 -s -g0 -mips32r2 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -Wno-invalid-source-encoding main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c -I/opt/vc/include -I../../ -L/opt/vc/lib/ -lGLESv2 -lEGL -lm -lrt -lpthread -lasound -ludev +/opt/gcw0-toolchain/usr/bin/mipsel-gcw0-linux-uclibc-strip ../../../bin/OpenLara --strip-all --remove-section=.comment --remove-section=.note diff --git a/src/platform/gcw0/icon.png b/src/platform/gcw0/icon.png new file mode 100644 index 0000000..1c882e3 Binary files /dev/null and b/src/platform/gcw0/icon.png differ diff --git a/src/platform/gcw0/main.cpp b/src/platform/gcw0/main.cpp new file mode 100644 index 0000000..c2e12f5 --- /dev/null +++ b/src/platform/gcw0/main.cpp @@ -0,0 +1,425 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "game.h" + +#define WND_TITLE "OpenLara" + +// timing +unsigned int startTime; + +int osGetTimeMS() { + timeval t; + gettimeofday(&t, NULL); + return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000); +} + +// sound +snd_pcm_uframes_t SND_FRAMES = 512; +snd_pcm_t *sndOut; +Sound::Frame *sndData; +pthread_t sndThread; + +void* sndFill(void *arg) { + while (sndOut) { + Sound::fill(sndData, SND_FRAMES); + + int count = SND_FRAMES; + while (count > 0) { + int frames = snd_pcm_writei(sndOut, &sndData[SND_FRAMES - count], count); + if (frames < 0) { + frames = snd_pcm_recover(sndOut, frames, 0); + if (frames == -EAGAIN) { + LOG("snd_pcm_writei try again\n"); + sleep(1); + continue; + } + if (frames < 0) { + LOG("snd_pcm_writei failed: %s\n", snd_strerror(frames)); + sndOut = NULL; + return NULL; + } + } + count -= frames; + } + + snd_pcm_prepare(sndOut); + } + return NULL; +} + +bool sndInit() { + unsigned int freq = 44100; + + int err; + + // In the perfect world ReedPlayer-Clover process + // will release ALSA device before app running, but... + for (int i = 0; i < 20; i++) { // 20 * 0.1 = 2 secs + sndOut = NULL; + if ((err = snd_pcm_open(&sndOut, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + LOG("sound: try to snd_pcm_open #%d...\n", i); + usleep(100000); // wait for 100 ms + continue; + } + break; + } + + // I've bad news for you + if (!sndOut) { + LOG("! sound: snd_pcm_open %s\n", snd_strerror(err)); + return false; + } + + snd_pcm_hw_params_t *params; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(sndOut, params); + snd_pcm_hw_params_set_access(sndOut, params, SND_PCM_ACCESS_RW_INTERLEAVED); + + snd_pcm_hw_params_set_channels(sndOut, params, 2); + snd_pcm_hw_params_set_format(sndOut, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(sndOut, params, &freq, NULL); + + snd_pcm_hw_params_set_periods(sndOut, params, 4, 0); + snd_pcm_hw_params_set_period_size_near(sndOut, params, &SND_FRAMES, NULL); + snd_pcm_hw_params_get_period_size(params, &SND_FRAMES, 0); + + snd_pcm_hw_params(sndOut, params); + snd_pcm_prepare(sndOut); + + sndData = new Sound::Frame[SND_FRAMES]; + memset(sndData, 0, SND_FRAMES * sizeof(Sound::Frame)); + if ((err = snd_pcm_writei(sndOut, sndData, SND_FRAMES)) < 0) { + LOG("! sound: write %s\n", snd_strerror(err)); + sndOut = NULL; + } + + snd_pcm_start(sndOut); + pthread_create(&sndThread, NULL, sndFill, NULL); + + return true; +} + +void sndFree() { + pthread_cancel(sndThread); + snd_pcm_drop(sndOut); + snd_pcm_drain(sndOut); + snd_pcm_close(sndOut); + delete[] sndData; +} + +// Window +struct FrameBuffer { + unsigned short width; + unsigned short height; +} fb; + +EGLDisplay display; +EGLSurface surface; +EGLContext context; + +bool eglInit() { + LOG("EGL init context...\n"); + + fb_var_screeninfo vinfo; + int fd = open("/dev/fb0", O_RDWR); + if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) { + LOG("! can't get framebuffer size\n"); + return false; + } + close(fd); + + fb.width = vinfo.xres; + fb.height = vinfo.yres; + + const EGLint eglAttr[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 5, + EGL_GREEN_SIZE, 6, + EGL_RED_SIZE, 5, + EGL_DEPTH_SIZE, 16, + EGL_NONE + }; + + const EGLint ctxAttr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + LOG("eglGetDisplay = EGL_NO_DISPLAY\n"); + return false; + } + + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + LOG("eglInitialize = EGL_FALSE\n"); + return false; + } + + EGLConfig config; + EGLint configCount; + + if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE || configCount == 0) { + LOG("eglChooseConfig = EGL_FALSE\n"); + return false; + } + + surface = eglCreateWindowSurface(display, config, 0, NULL); + if (surface == EGL_NO_SURFACE) { + LOG("eglCreateWindowSurface = EGL_NO_SURFACE\n"); + return false; + } + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr); + if (context == EGL_NO_CONTEXT) { + LOG("eglCreateContext = EGL_NO_CONTEXT\n"); + return false; + } + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { + LOG("eglMakeCurrent = EGL_FALSE\n"); + return false; + } + + return true; +} + +void eglFree() { + LOG("EGL release context\n"); + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); +} + +// Input +#define MAX_INPUT_DEVICES 16 +struct InputDevice { + int fd; +} inputDevices[MAX_INPUT_DEVICES]; + +udev *udevObj; +udev_monitor *udevMon; +int udevMon_fd; + +vec2 joyL, joyR; + +bool osJoyReady(int index) { + return index == 0; // TODO +} + +void osJoyVibrate(int index, float L, float R) { + // TODO +} + +JoyKey codeToJoyKey(int code) { + switch (code) { + // gamepad + case KEY_LEFT : return jkLeft; + case KEY_RIGHT : return jkRight; + case KEY_UP : return jkUp; + case KEY_DOWN : return jkDown; + case KEY_LEFTCTRL : return jkB; + case KEY_LEFTALT : return jkA; + case KEY_SPACE : return jkY; + case KEY_LEFTSHIFT : return jkX; + case KEY_TAB : return jkLB; + case KEY_BACKSPACE : return jkRB; + case KEY_ESC : return jkSelect; + case KEY_ENTER : return jkStart; + case KEY_KPSLASH : return jkL; + case KEY_KPDOT : return jkR; + case KEY_PAGEUP : return jkLT; + case KEY_PAGEDOWN : return jkRT; + case KEY_POWER : Core::quit(); + } + return jkNone; +} + +int inputDevIndex(const char *node) { + const char *str = strstr(node, "/event"); + if (str) + return atoi(str + 6); + return -1; +} + +void inputDevAdd(const char *node, udev_device *device) { + int index = inputDevIndex(node); + if (index != -1) { + InputDevice &item = inputDevices[index]; + item.fd = open(node, O_RDONLY | O_NONBLOCK); + //ioctl(item.fd, EVIOCGRAB, 1); + //LOG("input: add %s (%d)\n", node, item.joyIndex); + } +} + +bool inputInit() { + joyL = joyR = vec2(0); + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + inputDevices[i].fd = -1; + } + + udevObj = udev_new(); + if (!udevObj) + return false; + + udevMon = udev_monitor_new_from_netlink(udevObj, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(udevMon, "input", NULL); + udev_monitor_enable_receiving(udevMon); + udevMon_fd = udev_monitor_get_fd(udevMon); + + udev_enumerate *e = udev_enumerate_new(udevObj); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry *devices = udev_enumerate_get_list_entry(e); + + udev_list_entry *entry; + udev_list_entry_foreach(entry, devices) { + const char *path, *node; + udev_device *device; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udevObj, path); + node = udev_device_get_devnode(device); + + if (node) + inputDevAdd(node, device); + } + udev_enumerate_unref(e); + + return true; +} + +void inputFree() { + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + if (inputDevices[i].fd != -1) + close(inputDevices[i].fd); + udev_monitor_unref(udevMon); + udev_unref(udevObj); +} + +float joyAxisValue(int value) { + return value / 1536.0f - 1.0f; +} + +vec2 joyDir(const vec2 &value) { + float dist = min(1.0f, value.length()); + return value.normal() * dist; +} + +void inputUpdate() { +// get input events + input_event events[16]; + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + if (inputDevices[i].fd == -1) continue; + int rb = read(inputDevices[i].fd, events, sizeof(events)); + + input_event *e = events; + while (rb > 0) { + switch (e->type) { + case EV_KEY : { + JoyKey key = codeToJoyKey(e->code); + Input::setJoyDown(0, key, e->value != 0); + break; + } + case EV_ABS : { + switch (e->code) { + // Left stick + case ABS_X : joyL.x = -joyAxisValue(e->value); break; + case ABS_Y : joyL.y = -joyAxisValue(e->value); break; + // Right stick + case ABS_RX : joyR.x = joyAxisValue(e->value); break; + case ABS_RY : joyR.y = joyAxisValue(e->value); break; + } + + Input::setJoyPos(0, jkL, joyDir(joyL)); + Input::setJoyPos(0, jkR, joyDir(joyR)); + } + } + e++; + rb -= sizeof(events[0]); + } + } +} + +int main(int argc, char **argv) { + if (!eglInit()) { + LOG("! can't initialize EGL context\n"); + return -1; + } + + Core::width = fb.width; + Core::height = fb.height; + + cacheDir[0] = saveDir[0] = contentDir[0] = 0; + + const char *home; + if (!(home = getenv("HOME"))) + home = getpwuid(getuid())->pw_dir; + strcpy(contentDir, home); + strcat(contentDir, "/.openlara/"); + + LOG("content dir: %s\n", contentDir); + + struct stat st = {0}; + + if (stat(contentDir, &st) == -1) { + LOG("no data directory found, please copy the original game content into /home/.openlara/\n"); + return -1; + } + + strcpy(saveDir, contentDir); + + strcpy(cacheDir, contentDir); + strcat(cacheDir, "cache/"); + + if (stat(cacheDir, &st) == -1 && mkdir(cacheDir, 0777) == -1) { + cacheDir[0] = 0; + LOG("can't create /home/.openlara/cache/\n"); + } + + timeval t; + gettimeofday(&t, NULL); + startTime = t.tv_sec; + + Game::init(); + inputInit(); + sndInit(); + + while (!Core::isQuit) { + inputUpdate(); + + if (Game::update()) { + Game::render(); + Core::waitVBlank(); + eglSwapBuffers(display, surface); + } else + usleep(9000); + }; + + inputFree(); + + Game::deinit(); + eglFree(); + + sndFree(); + + return 0; +} diff --git a/src/platform/gcw0/make_opk.sh b/src/platform/gcw0/make_opk.sh new file mode 100755 index 0000000..d06487d --- /dev/null +++ b/src/platform/gcw0/make_opk.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +OPK_NAME=OpenLara.opk + +echo ${OPK_NAME} + +# create default.gcw0.desktop +cat > default.gcw0.desktop <