1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-09 22:56:53 +02:00

Add SDL2 platform (#166)

* Add SDL2 platform

* add desktopGL support to the SDL2 platform, and add SDL2 context shutdown calls
This commit is contained in:
vanfanel2
2019-02-20 22:28:25 +01:00
committed by Timur Gagiev
parent 1833cb1cc9
commit a8ec6cc511
5 changed files with 594 additions and 3 deletions

View File

@@ -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

View File

@@ -16,6 +16,51 @@
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#elif defined(__SDL2__)
#include <SDL2/SDL.h>
#if !defined(_GAPI_GLES)
#include <SDL2/SDL_opengl.h>
#else
#include <SDL2/SDL_opengles2.h>
#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 <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
@@ -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
}
}

View File

@@ -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)

4
src/platform/sdl2/build.sh Executable file
View File

@@ -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

500
src/platform/sdl2/main.cpp Normal file
View File

@@ -0,0 +1,500 @@
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <linux/input.h>
#include <unistd.h>
#include <pwd.h>
#include <pthread.h>
//#include <EGL/egl.h>
#include <fcntl.h>
#include <linux/input.h>
#include <libudev.h>
#include <alsa/asoundlib.h>
// SDL2 include stuff
#include <SDL2/SDL.h>
#define GL_GLEXT_PROTOTYPES 1
#include <SDL2/SDL_opengles2.h>
//
#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(&params);
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;
}