diff --git a/src/core.h b/src/core.h index 3e5e0787..0aca5509 100644 --- a/src/core.h +++ b/src/core.h @@ -27,6 +27,12 @@ //#define _GAPI_VULKAN extern void osToggleVR(bool enable); +#elif __SDL2__ + #define _GAPI_GL 1 +#ifdef SDL2GLES + #define _GAPI_GLES 1 + #define DYNGEOM_NO_VBO +#endif #elif __RPI__ #define _OS_RPI 1 #define _GAPI_GL 1 diff --git a/src/gapi_gl.h b/src/gapi_gl.h index a978c8e2..0b086215 100644 --- a/src/gapi_gl.h +++ b/src/gapi_gl.h @@ -16,6 +16,51 @@ #include #include #include + +#elif defined(__SDL2__) + #include +#if !defined(_GAPI_GLES) + #include +#else + #include + + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 + + #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 GL_TEXTURE_3D 0 + #define GL_TEXTURE_WRAP_R 0 + #define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES + #define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES + + #define glTexImage3D(...) 0 + + #define glGenVertexArrays(...) + #define glDeleteVertexArrays(...) + #define glBindVertexArray(...) + + #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES + #define glGetProgramBinary(...) + #define glProgramBinary(...) +#endif + #elif defined(_OS_RPI) || defined(_OS_CLOVER) #include #include @@ -245,11 +290,10 @@ PFNGLPROGRAMBINARYPROC glProgramBinary; #endif -#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) && !defined(_OS_IOS) && !defined(_OS_ANDROID) +#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) && !defined(_OS_IOS) && !defined(_OS_ANDROID) && !defined(__SDL2__) PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #endif - #ifdef PROFILE //#define USE_CV_MARKERS @@ -1074,7 +1118,11 @@ namespace GAPI { #else #ifdef _GAPI_GLES int GLES_VERSION = 1; + #if defined(__SDL2__) + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &GLES_VERSION); + #else glGetIntegerv(GL_MAJOR_VERSION, &GLES_VERSION); + #endif GLES3 = GLES_VERSION > 2; #endif #endif @@ -1272,7 +1320,9 @@ namespace GAPI { #ifdef _OS_ANDROID glInvalidateFramebuffer(GL_FRAMEBUFFER, count, discard); #else - glDiscardFramebufferEXT(GL_FRAMEBUFFER, count, discard); + #if !defined(__SDL2__) + glDiscardFramebufferEXT(GL_FRAMEBUFFER, count, discard); + #endif #endif } } diff --git a/src/platform/sdl2/Makefile b/src/platform/sdl2/Makefile new file mode 100644 index 00000000..3b5040af --- /dev/null +++ b/src/platform/sdl2/Makefile @@ -0,0 +1,31 @@ +SRCS=main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c +CC=g++ +OBJS=OpenLara.o +BIN=OpenLara + +CFLAGS+=-D__SDL2__ +LDFLAGS+=-lGLESv2 -lEGL -lSDL2 -lpthread -lrt -lm + +INCLUDES+=-I./ + +openlara : $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ + +%.o: %.c + @rm -f $@ + $(CC) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations + +%.o: %.cpp + @rm -f $@ + $(CXX) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations + +%.bin: $(OBJS) + $(CC) -o $@ -Wl,--whole-archive $(OBJS) $(LDFLAGS) -Wl,--no-whole-archive -rdynamic + +%.a: $(OBJS) + $(AR) r $@ $^ + +clean: + for i in $(OBJS); do (if test -e "$$i"; then ( rm $$i ); fi ); done + @rm -f $(BIN) $(LIB) + diff --git a/src/platform/sdl2/build.sh b/src/platform/sdl2/build.sh new file mode 100755 index 00000000..d8f622fd --- /dev/null +++ b/src/platform/sdl2/build.sh @@ -0,0 +1,4 @@ +set -e +g++ -DSDL2_GLES -std=c++11 `sdl2-config --cflags` -O3 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG -D__SDL2__ main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c -I../../ -o OpenLara `sdl2-config --libs` -lGLESv2 -lEGL -lm -lrt -lpthread -lasound -ludev +#g++ -std=c++11 `sdl2-config --cflags` -O3 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG -D__SDL2__ main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c -I../../ -o OpenLara `sdl2-config --libs` -lGL -lm -lrt -lpthread -lasound -ludev +#strip ../../../bin/OpenLara --strip-all --remove-section=.comment --remove-section=.note diff --git a/src/platform/sdl2/main.cpp b/src/platform/sdl2/main.cpp new file mode 100644 index 00000000..29cb386d --- /dev/null +++ b/src/platform/sdl2/main.cpp @@ -0,0 +1,500 @@ +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include + +// SDL2 include stuff +#include +#define GL_GLEXT_PROTOTYPES 1 +#include +// + +#include "game.h" + +#define WND_TITLE "OpenLara" + +// timing +unsigned int startTime; + +int osGetTime() { + 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 err = snd_pcm_writei(sndOut, sndData, SND_FRAMES); + if (err < 0) { + LOG("! sound: write %s\n", snd_strerror(err));; + if (err != -EPIPE) + break; + + err = snd_pcm_recover(sndOut, err, 0); + if (err < 0) { + LOG("! sound: failed to recover\n"); + break; + } + snd_pcm_prepare(sndOut); + } + } + return NULL; +} + +bool sndInit() { + unsigned int freq = 44100; + + int err; + if ((err = snd_pcm_open(&sndOut, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + LOG("! sound: open %s\n", snd_strerror(err));\ + sndOut = NULL; + 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; +} + +/*bool eglInit(EGL_DISPMANX_WINDOW_T &window, EGLDisplay &display, EGLSurface &surface, EGLContext &context) { + static const EGLint eglAttr[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_SAMPLES, 0, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + static const EGLint ctxAttr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) + return false; + + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) + return false; + + EGLConfig config; + EGLint configCount; + + if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE) + return false; + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr); + if (context == EGL_NO_CONTEXT) + return false; + + surface = eglCreateWindowSurface(display, config, &window, NULL); + if (surface == EGL_NO_SURFACE) + return false; + + if (eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE) + return false; + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) + return false; + + return true; +}*/ + +// Input +#define MAX_INPUT_DEVICES 16 +int 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; + } + return ikNone; +} + +JoyKey codeToJoyKey(int code) { + switch (code) { + // gamepad + case BTN_A : return jkA; + case BTN_B : return jkB; + case BTN_X : return jkX; + case BTN_Y : return jkY; + 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) { + int index = inputDevIndex(node); + if (index != -1) { + inputDevices[index] = open(node, O_RDONLY | O_NONBLOCK); + ioctl(inputDevices[index], EVIOCGRAB, 1); + //LOG("input: add %s\n", node); + } +} + +void inputDevRemove(const char *node) { + int index = inputDevIndex(node); + if (index != -1 && inputDevices[index] != -1) { + close(inputDevices[index]); + //LOG("input: remove %s\n", node); + } +} + +bool inputInit() { + joyL = joyR = vec2(0); + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + inputDevices[i] = -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); + } + udev_enumerate_unref(e); + + return true; +} + +void inputFree() { + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + if (inputDevices[i] != -1) + close(inputDevices[i]); + 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] == -1) continue; + int rb = read(inputDevices[i], events, sizeof(events)); + + int joyIndex = 0; // TODO: joy index + + 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 { + 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 : { + 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); + if (!strcmp(action, "remove")) + inputDevRemove(node); + } + udev_device_unref(device); + } else + LOG("! input: receive_device\n"); + } +} + +int main(int argc, char **argv) { + + SDL_Init(SDL_INIT_VIDEO); + + //SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + //SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + SDL_Window *window = SDL_CreateWindow(WND_TITLE, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); + + Core::width = 640; + Core::height = 480; + + SDL_GLContext context = SDL_GL_CreateContext(window); + SDL_GL_SetSwapInterval(0); + + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); + + + cacheDir[0] = saveDir[0] = contentDir[0] = 0; + + const char *home; + if (!(home = getenv("HOME"))) + home = getpwuid(getuid())->pw_dir; + strcat(cacheDir, home); + strcat(cacheDir, "/.openlara/"); + + struct stat st = {0}; + if (stat(cacheDir, &st) == -1 && mkdir(cacheDir, 0777) == -1) + cacheDir[0] = 0; + strcpy(saveDir, cacheDir); + + timeval t; + gettimeofday(&t, NULL); + startTime = t.tv_sec; + + sndInit(); + + char *lvlName = argc > 1 ? argv[1] : NULL; + + Game::init(lvlName); + + inputInit(); // initialize and grab input devices + + while (!Core::isQuit) { + inputUpdate(); + + if (Game::update()) { + Game::render(); + Core::waitVBlank(); + SDL_GL_SwapWindow(window); + } + }; + + inputFree(); + + sndFree(); + Game::deinit(); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +}