From bccba2b42fb85769b0f0788644b0894dca6d9d13 Mon Sep 17 00:00:00 2001 From: Ryan 'Swingflip' Hamlin Date: Tue, 12 Mar 2019 12:09:52 +0000 Subject: [PATCH] Added PSC platform and builderv0.1 (#176) --- src/core.h | 7 + src/gapi_gl.h | 4 +- src/platform/psc/build.sh | 32 ++ src/platform/psc/main.cpp | 593 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 634 insertions(+), 2 deletions(-) create mode 100644 src/platform/psc/build.sh create mode 100644 src/platform/psc/main.cpp diff --git a/src/core.h b/src/core.h index ceba804..37c587a 100644 --- a/src/core.h +++ b/src/core.h @@ -44,6 +44,13 @@ #define _GAPI_GL 1 #define _GAPI_GLES 1 + #define DYNGEOM_NO_VBO +#elif __PSC__ + #define _OS_CLOVER 1 + #define _OS_PSC 1 + #define _GAPI_GL 1 + #define _GAPI_GLES 1 + #define DYNGEOM_NO_VBO #elif __linux__ #define _OS_LINUX 1 diff --git a/src/gapi_gl.h b/src/gapi_gl.h index 279fd34..030f9e1 100644 --- a/src/gapi_gl.h +++ b/src/gapi_gl.h @@ -299,7 +299,7 @@ PFNGLPROGRAMBINARYPROC glProgramBinary; #endif -#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) && !defined(_OS_IOS) && !defined(_OS_ANDROID) && !defined(__SDL2__) +#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) && !defined(_OS_PSC) && !defined(_OS_IOS) && !defined(_OS_ANDROID) && !defined(__SDL2__) PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #endif @@ -1359,7 +1359,7 @@ namespace GAPI { #ifdef _OS_ANDROID glInvalidateFramebuffer(GL_FRAMEBUFFER, count, discard); #else - #if !defined(__SDL2__) + #if !defined(__SDL2__) && !defined(_OS_PSC) glDiscardFramebufferEXT(GL_FRAMEBUFFER, count, discard); #endif #endif diff --git a/src/platform/psc/build.sh b/src/platform/psc/build.sh new file mode 100644 index 0000000..7989892 --- /dev/null +++ b/src/platform/psc/build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# This should really be a makefile with individual platforms but for now... +set -e +# General Info +GIT_COMMIT=$(echo "`git rev-parse --short HEAD``git diff-index --quiet HEAD -- || echo '-dirty'`") + +# Build Rules +[ -z "CXX" ] && CXX="arm-linux-gnueabihf-g++-8" +CFLAGS="-std=c++11 -Os -s -g -marm -march=armv8-a -mtune=cortex-a35 -mfpu=neon-fp-armv8 -mfloat-abi=hard -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections" +LDFLAGS="-Wl,--gc-sections -Wno-invalid-source-encoding -static-libgcc -static-libstdc++" +DEFINES="-DNDEBUG -D__PSC__" +SRC="main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c" +INCLUDES="-I/usr/arm-linux-gnueabihf/include/ -I../../" +LIBS="-L$LD_LIBRARY_PATH -lGLESv2 -lEGL -lm -lrt -lpthread -lasound -ludev" +TARGET="-o../../../bin/OpenLara" + +clear +echo "-------------------------------------------------------------------------------" +echo "OpenLara Builder v0.1 - OpenLara Build $GIT_COMMIT" +echo "-------------------------------------------------------------------------------" +echo "Build Rules:" +echo "CFLAGS: $CFLAGS" +echo "LDFLAGS: $LDFLAGS" +echo "DEFINES: $DEFINES" +echo "SRC: $SRC" +echo "INCLUDES: $INCLUDES" +echo "LIBS: $LIBS" +echo "TARGET: $TARGET" +echo "-------------------------------------------------------------------------------" +echo + +$CXX $CFLAGS $LDFLAGS $DEFINES $SRC $INCLUDES $LIBS $TARGET \ No newline at end of file diff --git a/src/platform/psc/main.cpp b/src/platform/psc/main.cpp new file mode 100644 index 0000000..0810d81 --- /dev/null +++ b/src/platform/psc/main.cpp @@ -0,0 +1,593 @@ +#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; + + static const EGLint eglAttr[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + + static 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) { + LOG("eglChooseConfig = EGL_FALSE\n"); + return false; + } + + surface = eglCreateWindowSurface(display, config, &fb, 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; + int joyIndex; +} 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 +} + +InputKey codeToInputKey(int code) { + switch (code) { + // keyboard + case KEY_LEFT : return ikLeft; + case KEY_RIGHT : return ikRight; + case KEY_UP : return ikUp; + case KEY_DOWN : return ikDown; + case KEY_SPACE : return ikSpace; + case KEY_TAB : return ikTab; + case KEY_ENTER : return ikEnter; + case KEY_ESC : return ikEscape; + case KEY_LEFTSHIFT : + case KEY_RIGHTSHIFT : return ikShift; + case KEY_LEFTCTRL : + case KEY_RIGHTCTRL : return ikCtrl; + case KEY_LEFTALT : + case KEY_RIGHTALT : return ikAlt; + case KEY_0 : return ik0; + case KEY_1 : return ik1; + case KEY_2 : return ik2; + case KEY_3 : return ik3; + case KEY_4 : return ik4; + case KEY_5 : return ik5; + case KEY_6 : return ik6; + case KEY_7 : return ik7; + case KEY_8 : return ik8; + case KEY_9 : return ik9; + case KEY_A : return ikA; + case KEY_B : return ikB; + case KEY_C : return ikC; + case KEY_D : return ikD; + case KEY_E : return ikE; + case KEY_F : return ikF; + case KEY_G : return ikG; + case KEY_H : return ikH; + case KEY_I : return ikI; + case KEY_J : return ikJ; + case KEY_K : return ikK; + case KEY_L : return ikL; + case KEY_M : return ikM; + case KEY_N : return ikN; + case KEY_O : return ikO; + case KEY_P : return ikP; + case KEY_Q : return ikQ; + case KEY_R : return ikR; + case KEY_S : return ikS; + case KEY_T : return ikT; + case KEY_U : return ikU; + case KEY_V : return ikV; + case KEY_W : return ikW; + case KEY_X : return ikX; + case KEY_Y : return ikY; + case KEY_Z : return ikZ; + case KEY_HOMEPAGE : return ikEscape; + // mouse + case BTN_LEFT : return ikMouseL; + case BTN_RIGHT : return ikMouseR; + case BTN_MIDDLE : return ikMouseM; + // system keys + case KEY_VOLUMEUP : + case BTN_MODE : + Core::quit(); + return ikNone; + } + return ikNone; +} + +JoyKey codeToJoyKey(int code) { + switch (code) { + // gamepad + case BTN_TRIGGER_HAPPY1 : return jkLeft; + case BTN_TRIGGER_HAPPY2 : return jkRight; + case BTN_TRIGGER_HAPPY3 : return jkUp; + case BTN_TRIGGER_HAPPY4 : return jkDown; + case BTN_A : return jkB; + case BTN_B : return jkA; + case BTN_X : return jkY; + case BTN_Y : return jkX; + case BTN_TL : return jkLB; + case BTN_TR : return jkRB; + case BTN_SELECT : return jkSelect; + case BTN_START : return jkStart; + case BTN_THUMBL : return jkL; + case BTN_THUMBR : return jkR; + case BTN_TL2 : return jkLT; + case BTN_TR2 : return jkRT; + } + 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); + + item.joyIndex = -1; + if (udev_device_get_property_value(device, "ID_INPUT_JOYSTICK")) { + + // TODO get index from /dev/input/js[N] + + for (int i = 0; i < 4; i++) { // up to 4 gamepads + bool found = true; + for (int j = 0; j < MAX_INPUT_DEVICES; j++) { + if (inputDevices[j].joyIndex == i) { + found = false; + break; + } + } + + if (found) { + item.joyIndex = i; + break; + } + } + } + + //LOG("input: add %s (%d)\n", node, item.joyIndex); + } +} + +void inputDevRemove(const char *node) { + int index = inputDevIndex(node); + if (index != -1 && inputDevices[index].fd != -1) { + close(inputDevices[index].fd); + //LOG("input: remove %s\n", node); + } +} + +bool inputInit() { + joyL = joyR = vec2(0); + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + inputDevices[i].fd = -1; + inputDevices[i].joyIndex = -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); +} + +#define JOY_DEAD_ZONE_STICK 8192 + +float joyAxisValue(int value) { + if (value > -JOY_DEAD_ZONE_STICK && value < JOY_DEAD_ZONE_STICK) + return 0.0f; + return value / 32767.0f; +} + +float joyTrigger(int value) { + return min(1.0f, value / 255.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)); + + int joyIndex = inputDevices[i].joyIndex; + + input_event *e = events; + while (rb > 0) { + switch (e->type) { + case EV_KEY : { + InputKey key = codeToInputKey(e->code); + if (key != ikNone) { + if (key == ikMouseL || key == ikMouseR || key == ikMouseM) + Input::setPos(key, Input::mouse.pos); + Input::setDown(key, e->value != 0); + } else { + if (joyIndex == -1) + break; + JoyKey key = codeToJoyKey(e->code); + Input::setJoyDown(joyIndex, key, e->value != 0); + } + break; + } + case EV_REL : { + vec2 delta(0); + delta[e->code] = float(e->value); + Input::setPos(ikMouseL, Input::mouse.pos + delta); + break; + } + case EV_ABS : { + if (joyIndex == -1) + break; + + 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; + // Left trigger + case ABS_Z : Input::setJoyPos(joyIndex, jkLT, joyTrigger(e->value)); break; + // Right trigger + case ABS_RZ : Input::setJoyPos(joyIndex, jkRT, joyTrigger(e->value)); break; + // D-PAD + case ABS_HAT0X : + case ABS_THROTTLE : + Input::setJoyDown(joyIndex, jkLeft, e->value < 0); + Input::setJoyDown(joyIndex, jkRight, e->value > 0); + break; + case ABS_HAT0Y : + case ABS_RUDDER : + Input::setJoyDown(joyIndex, jkUp, e->value < 0); + Input::setJoyDown(joyIndex, jkDown, e->value > 0); + break; + } + + Input::setJoyPos(joyIndex, jkL, joyDir(joyL)); + Input::setJoyPos(joyIndex, jkR, joyDir(joyR)); + } + } + //LOG("input: type = %d, code = %d, value = %d\n", int(e->type), int(e->code), int(e->value)); + e++; + rb -= sizeof(events[0]); + } + } + +// monitoring plug and unplug input devices + fd_set fds; + FD_ZERO(&fds); + FD_SET(udevMon_fd, &fds); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + if (select(udevMon_fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(udevMon_fd, &fds)) { + udev_device *device = udev_monitor_receive_device(udevMon); + if (device) { + const char *node = udev_device_get_devnode(device); + if (node) { + const char *action = udev_device_get_action(device); + if (!strcmp(action, "add")) + inputDevAdd(node, device); + if (!strcmp(action, "remove")) + inputDevRemove(node); + } + udev_device_unref(device); + } else + LOG("! input: receive_device\n"); + } +} + +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; + + strcpy(contentDir, argv[0]); + int i = strlen(contentDir); + while (--i >= 0) { + if (contentDir[i] == '/') { + contentDir[i + 1] = 0; + break; + } + i--; + } + + strcpy(cacheDir, contentDir); + strcat(cacheDir, "cache/"); + + struct stat st = {0}; + if (stat(cacheDir, &st) == -1 && mkdir(cacheDir, 0777) == -1) + cacheDir[0] = 0; + + const char *home; + if (!(home = getenv("HOME"))) + home = getpwuid(getuid())->pw_dir; + strcpy(saveDir, home); + strcat(saveDir, "/.openlara/"); + + if (stat(saveDir, &st) == -1 && mkdir(saveDir, 0777) == -1) + saveDir[0] = 0; + + 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; +}