From 15ba98250c28922c8dc0469e16b467ac15ea9cca Mon Sep 17 00:00:00 2001 From: hissingshark Date: Mon, 25 Jan 2021 21:23:50 +0000 Subject: [PATCH] Provide non-X11 support, using fullscreen EGL video and SDL2 controls --- CMakeLists.txt | 12 +- cmake/FindEGL.cmake | 25 ++ src/window_manager_sdl2.cpp | 59 ++++ src/window_manager_sdl2.h | 21 ++ src/window_sdl2.cpp | 557 ++++++++++++++++++++++++++++++++++++ src/window_sdl2.h | 93 ++++++ 6 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 cmake/FindEGL.cmake create mode 100644 src/window_manager_sdl2.cpp create mode 100644 src/window_manager_sdl2.h create mode 100644 src/window_sdl2.cpp create mode 100644 src/window_sdl2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a8bbfa..fc45042 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,13 @@ project(eglut LANGUAGES CXX) include(BuildSettings.cmake) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") + set(GAMEWINDOW_SOURCES include/game_window.h include/game_window_manager.h src/game_window_manager.cpp src/game_window_error_handler.cpp src/joystick_manager.cpp) set(GAMEWINDOW_SOURCES_LINUX_GAMEPAD src/joystick_manager_linux_gamepad.cpp src/joystick_manager_linux_gamepad.h src/window_with_linux_gamepad.cpp src/window_with_linux_gamepad.h) set(GAMEWINDOW_SOURCES_EGLUT src/window_eglut.h src/window_eglut.cpp src/window_manager_eglut.cpp src/window_manager_eglut.h) set(GAMEWINDOW_SOURCES_GLFW src/window_glfw.h src/window_glfw.cpp src/window_manager_glfw.cpp src/window_manager_glfw.h src/joystick_manager_glfw.cpp src/joystick_manager_glfw.h) +set(GAMEWINDOW_SOURCES_SDL2 src/window_sdl2.h src/window_sdl2.cpp src/window_manager_sdl2.cpp src/window_manager_sdl2.h) add_library(gamewindow ${GAMEWINDOW_SOURCES}) target_include_directories(gamewindow PUBLIC include/) @@ -18,4 +21,11 @@ if (GAMEWINDOW_SYSTEM STREQUAL "EGLUT") elseif (GAMEWINDOW_SYSTEM STREQUAL "GLFW") target_sources(gamewindow PRIVATE ${GAMEWINDOW_SOURCES_GLFW}) target_link_libraries(gamewindow PUBLIC glfw3) -endif() \ No newline at end of file +elseif (GAMEWINDOW_SYSTEM STREQUAL "SDL2") + find_package(EGL REQUIRED) + message(STATUS "Found EGL") + find_package(SDL2 REQUIRED) + message(STATUS "Found SDL2: -I" ${SDL2_INCLUDE_DIRS} " " ${SDL2_LIBRARIES}) + target_sources(gamewindow PRIVATE ${GAMEWINDOW_SOURCES_SDL2}) + target_link_libraries(gamewindow ${EGL_LIBRARIES} ${SDL2_LIBRARIES}) +endif() diff --git a/cmake/FindEGL.cmake b/cmake/FindEGL.cmake new file mode 100644 index 0000000..d88d33c --- /dev/null +++ b/cmake/FindEGL.cmake @@ -0,0 +1,25 @@ +# Finds the `egl` library. +# This file is released under the Public Domain. +# Once done this will define +# EGL_FOUND - Set to true if egl has been found +# EGL_INCLUDE_DIRS - The egl include directories +# EGL_LIBRARIES - The libraries needed to use egl + +find_package(PkgConfig) +pkg_check_modules(PC_EGL QUIET egl) + +find_path(EGL_INCLUDE_DIR + NAMES EGL/egl.h + HINTS ${PC_EGL_INCLUDEDIR} ${PC_EGL_INCLUDE_DIRS}) +find_library(EGL_LIBRARY + NAMES EGL libEGL + HINTS ${PC_EGL_LIBDIR} ${PC_EGL_LIBRARY_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(EGL DEFAULT_MSG + EGL_LIBRARY EGL_INCLUDE_DIR) + +mark_as_advanced(EGL_INCLUDE_DIR EGL_LIBRARY) + +set(EGL_LIBRARIES ${EGL_LIBRARY}) +set(EGL_INCLUDE_DIRS ${EGL_INCLUDE_DIR}) \ No newline at end of file diff --git a/src/window_manager_sdl2.cpp b/src/window_manager_sdl2.cpp new file mode 100644 index 0000000..713e756 --- /dev/null +++ b/src/window_manager_sdl2.cpp @@ -0,0 +1,59 @@ +#include "window_manager_sdl2.h" +#include "window_sdl2.h" +#include +#include + +GameWindowManager::ProcAddrFunc dlsymGetProcAddress(const char* sym) { + if (!sym) + return NULL; + void *eglFunc; + + // try official EGL method first + eglFunc = (void*)eglGetProcAddress(sym); + if (eglFunc) + return (GameWindowManager::ProcAddrFunc)eglFunc; + + // manual fallback "If the EGL version is not 1.5 or greater, only queries of EGL and client API extension functions will succeed." + void *libEGL; + libEGL = dlopen("libEGL.so", RTLD_LAZY | RTLD_GLOBAL); + if (!libEGL) { + printf("Error: Unable to open libEGL.so for symbol processing"); + return NULL; + } + + eglFunc = dlsym(libEGL, sym); + dlclose(libEGL); + + return (GameWindowManager::ProcAddrFunc)eglFunc; +} + +SDL2WindowManager::SDL2WindowManager() { + /* + nothing instantiated yet + just a handle to: + 1. access the GL libraries via dlsymGetProcAddress() + 2. the future window + */ +} + +GameWindowManager::ProcAddrFunc SDL2WindowManager::getProcAddrFunc() { + return (GameWindowManager::ProcAddrFunc) dlsymGetProcAddress; +} + +std::shared_ptr SDL2WindowManager::createWindow(const std::string& title, int width, int height, + GraphicsApi api) { + return std::shared_ptr(new SDL2GameWindow(title, width, height, api)); +} + +void SDL2WindowManager::addGamepadMappingFile(const std::string &path) { + printf("Loaded %d more controller mappings from %s\n", SDL_GameControllerAddMappingsFromFile(path.c_str()), path.c_str()); +} + +void SDL2WindowManager::addGamePadMapping(const std::string &content) { + // NOOP +} + +// Define this window manager as the used one +std::shared_ptr GameWindowManager::createManager() { + return std::shared_ptr(new SDL2WindowManager()); +} diff --git a/src/window_manager_sdl2.h b/src/window_manager_sdl2.h new file mode 100644 index 0000000..d4c2604 --- /dev/null +++ b/src/window_manager_sdl2.h @@ -0,0 +1,21 @@ +#pragma once + +#include "game_window_manager.h" + + +GameWindowManager::ProcAddrFunc dlsymGetProcAddress(const char*); + + +class SDL2WindowManager : public GameWindowManager { + +public: + SDL2WindowManager(); + + ProcAddrFunc getProcAddrFunc() override; + + std::shared_ptr createWindow(const std::string& title, int width, int height, GraphicsApi api) override; + + void addGamepadMappingFile(const std::string& path) override; + + void addGamePadMapping(const std::string &content) override; +}; diff --git a/src/window_sdl2.cpp b/src/window_sdl2.cpp new file mode 100644 index 0000000..87d7402 --- /dev/null +++ b/src/window_sdl2.cpp @@ -0,0 +1,557 @@ +#include "window_sdl2.h" +#include "game_window_manager.h" +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +SDL2GameWindow::SDL2GameWindow(const std::string& title, int width, int height, GraphicsApi api) : + GameWindow(title, width, height, api) { + + currentGameWindow = this; + + initFrameBuffer(); + initEGL(); + initSDL(); + initCursor(); +} + +SDL2GameWindow::~SDL2GameWindow() { + munmap (fb.mmap, fb.mmap_size); + ::close(fb.fd); + delete cursor.img; + + eglDestroySurface(egl.display, egl.surface); + eglDestroyContext(egl.display, egl.context); + eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + SDL_Quit(); +} + +void SDL2GameWindow::abortMsg(const char *msg) +{ + fflush(stdout); + fprintf(stderr, "Fatal Error: %s\n", msg); + exit(1); +} + +void SDL2GameWindow::initFrameBuffer() { + // map framebuffer for drawing operations and to get dimensions + fb.fd = open("/dev/fb0", O_RDWR); + if (fb.fd < 0) + abortMsg("Failed to open fb0 for cursor drawing operations!"); + + struct fb_var_screeninfo vinfo; + + ioctl (fb.fd, FBIOGET_VSCREENINFO, &vinfo); + + fb.w = vinfo.xres; + fb.h = vinfo.yres; + int fb_bpp = vinfo.bits_per_pixel; + int fb_bytes = fb_bpp / 8; + + fb.mmap_size = fb.w * fb.h * fb_bytes * 2; // double buffered so x2 size + + fb.mmap = (bgra_pixel*)mmap (0, fb.mmap_size, + PROT_READ | PROT_WRITE, MAP_SHARED, fb.fd, (off_t)0); + + // assume backbuffer is the 2nd half of the memory map - will sync it up after EGL initialised + fb.frontbuff = fb.mmap; + fb.backbuff = &fb.mmap[fb.h * fb.w]; +} + +void SDL2GameWindow::initEGL() { + // init EGL diplay, window, surface and context + EGLConfig config; + EGLint num_config; + EGLint const config_attribute_list[] = { + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_DEPTH_SIZE, 1, + EGL_STENCIL_SIZE, 8, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + EGLint const context_attribute_list[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + egl.display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (eglGetError() != EGL_SUCCESS) + abortMsg("EGL error when getting display!"); + + if (! eglInitialize(egl.display, NULL, NULL)) + abortMsg("Failed to initialize EGL Display!"); + + eglChooseConfig(egl.display, config_attribute_list, &config, 1, &num_config); + if (eglGetError() != EGL_SUCCESS) + abortMsg("EGL error when choosing config!"); + + if (! eglBindAPI(EGL_OPENGL_ES_API)) + abortMsg("Failed to bind EGL API!\n"); + + egl.context = eglCreateContext(egl.display, config, EGL_NO_CONTEXT, context_attribute_list); + if (! egl.context) + abortMsg("Failed to create EGL Context!"); + + egl.surface = eglCreateWindowSurface(egl.display, config, NULL, NULL); + if (egl.surface == EGL_NO_SURFACE) + abortMsg("Failed to create EGL Window Surface!"); + + if (! eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) + abortMsg("Failed to make EGL Context current!"); + + // find the backbuffer address in framebuffer in case it is out of sync + // first ensure both surfaces are black + clearColour(0.0); + clearColour(0.0); + // then clear to mid-grey and test where it landed after swapping + clearColour(0.5); + if(fb.backbuff->r == 128) // back is actually front! + std::swap(fb.frontbuff, fb.backbuff); // synchronise +} + +void SDL2GameWindow::clearColour(float shade) { + glClearColor(shade, shade, shade, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); + eglSwapBuffers(egl.display, egl.surface); +} + +void SDL2GameWindow::initSDL() { + // video is mandatory to get events, even though we aren't using it, so we wont be creating a window + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS|SDL_INIT_GAMECONTROLLER) != 0) { + abortMsg("Unable to initialize SDL for video|events|gamecontroller"); + } + + if(!SDL_GameControllerEventState(SDL_QUERY)) + SDL_GameControllerEventState(SDL_ENABLE); + + gamepad.count = 0; +} + +void SDL2GameWindow::initCursor() { + const char mcw = 16; // raw mouse cursor image width + // rgba mouse cursor image data + const char mci[16][16][4] = { + {{6,61,51,255}, {6,61,51,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{2,32,26,255}, {165,253,240,255}, {165,253,240,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{2,32,26,255}, {165,253,240,255}, {38,200,174,255}, {165,253,240,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {2,32,26,255}, {165,253,240,255}, {38,200,174,255}, {165,253,240,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {38,200,174,255}, {165,253,240,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {38,200,174,255}, {165,253,240,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {38,200,174,255}, {47,236,204,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {6,61,51,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {38,200,174,255}, {47,236,204,255}, {6,61,51,255}, {0,0,0,0}, {6,61,51,255}, {13,99,84,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {24,139,120,255}, {47,236,204,255}, {2,32,26,255}, {24,139,120,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {47,236,204,255}, {13,99,84,255}, {24,139,120,255}, {24,139,120,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {6,61,51,255}, {13,99,84,255}, {6,61,51,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {6,61,51,255}, {6,61,51,255}, {2,32,26,255}, {104,77,24,255}, {71,51,13,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {6,61,51,255}, {2,32,26,255}, {2,32,26,255}, {0,0,0,0}, {35,24,3,255}, {138,103,34,255}, {71,51,13,255}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {2,32,26,255}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {35,24,3,255}, {104,77,24,255}, {6,61,51,255}, {6,61,51,255}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {13,99,84,255}, {6,61,51,255}}, + {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {2,32,26,255}, {2,32,26,255}, {2,32,26,255}}}; + + // calculate scale factor to make mouse cursor image 1/8th of screen height + const char mcs = fb.h / 8 / mcw; + cursor.size = mcw * mcs; // store width for drawing operations later + + // allocate storage for scaled cursor image + cursor.img = new bgra_pixel*[mcw*mcs]; // allocate rows + // allocate columns + for(int row=0; row<(mcw*mcs); row++) { + cursor.img[row] = new bgra_pixel[mcw * mcs]; + } + + // scale image into storage, remapping from rgba to bgra in the process + for(int u=0; u cursor.size) + clip_w = cursor.size; + + int clip_h = fb.h - my; + if(clip_h > cursor.size) + clip_h = cursor.size; + + for(int u=0; utype == SDL_CONTROLLERDEVICEADDED) { + // The game will only be informed of the first connection and last disconnection. + // All inputs seen as controller 0 so the behaviour of multiple connected gamepads will be undefined. + gamepad.count++; + SDL_GameController *controller = NULL; + controller = SDL_GameControllerOpen(cdeviceevent->which); + if(!controller) + printf("SDL2GameWindow: Couldn't open controller! - %s\n", SDL_GetError()); + else + printf("SDL2GameWindow: Controller %d opened: %s!\n", cdeviceevent->which, SDL_GameControllerName(controller)); + if (gamepad.count > 1) + return; + } + else if (cdeviceevent->type == SDL_CONTROLLERDEVICEREMOVED) { + if (gamepad.count < 1) { + printf("SDL2GameWindow: Error - controller removed when none were known to be connected"); + return; + } + else + gamepad.count--; + printf("SDL2GameWindow: Controller %d removed!\n", cdeviceevent->which); + if (gamepad.count > 0) + return; + } + else + return; + + printf("SDL2GameWindow: There are now %d connected joysticks\n", SDL_NumJoysticks()); + currentGameWindow->onGamepadState(0, (cdeviceevent->type == SDL_CONTROLLERDEVICEADDED)); +} + +void SDL2GameWindow::handleControllerAxisEvent(SDL_ControllerAxisEvent *caxisevent) { + cursor.inuse = false; // gampepad has taken priority over mouse so hide the cursor + GamepadAxisId axis; + switch (caxisevent->axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + axis = GamepadAxisId::LEFT_X; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + axis = GamepadAxisId::LEFT_Y; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + axis = GamepadAxisId::RIGHT_X; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + axis = GamepadAxisId::RIGHT_Y; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + axis = GamepadAxisId::LEFT_TRIGGER; + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + axis = GamepadAxisId::RIGHT_TRIGGER; + break; + default : + return; + } + + double deflection = (double)caxisevent->value / 32768; // normalised -1 to 1 range + currentGameWindow->onGamepadAxis(0, axis, deflection); +} + +void SDL2GameWindow::handleControllerButtonEvent(SDL_ControllerButtonEvent *cbuttonevent) { + cursor.inuse = false; // gampepad has taken priority over mouse so hide the cursor + + GamepadButtonId btn; + + switch (cbuttonevent->button) { + case SDL_CONTROLLER_BUTTON_A: + btn = GamepadButtonId::A; + break; + case SDL_CONTROLLER_BUTTON_B: + btn = GamepadButtonId::B; + break; + case SDL_CONTROLLER_BUTTON_X: + btn = GamepadButtonId::X; + break; + case SDL_CONTROLLER_BUTTON_Y: + btn = GamepadButtonId::Y; + break; + case SDL_CONTROLLER_BUTTON_BACK: + btn = GamepadButtonId::BACK; + break; + case SDL_CONTROLLER_BUTTON_START: + btn = GamepadButtonId::START; + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + btn = GamepadButtonId::GUIDE; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + btn = GamepadButtonId::LEFT_STICK; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + btn = GamepadButtonId::RIGHT_STICK; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + btn = GamepadButtonId::LB; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + btn = GamepadButtonId::RB; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + btn = GamepadButtonId::DPAD_UP; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + btn = GamepadButtonId::DPAD_DOWN; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + btn = GamepadButtonId::DPAD_LEFT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + btn = GamepadButtonId::DPAD_RIGHT; + break; + default : + return; + } + + currentGameWindow->onGamepadButton(0, btn, (cbuttonevent->state == SDL_PRESSED)); +} + +void SDL2GameWindow::handleMouseWheelEvent(SDL_MouseWheelEvent *wheelevent) { + if (wheelevent->x < 0) + currentGameWindow->onMouseScroll(cursor.x, cursor.y, -1.0, 0.0); + else if (wheelevent->x > 0) + currentGameWindow->onMouseScroll(cursor.x, cursor.y, 1.0, 0.0); + else if (wheelevent->y < 0) + currentGameWindow->onMouseScroll(0.0, 0.0, 0.0, -1); + else if (wheelevent->y > 0) + currentGameWindow->onMouseScroll(0.0, 0.0, 0.0, 1); +} + +void SDL2GameWindow::handleMouseMotionEvent(SDL_MouseMotionEvent *motionevent) { + if (cursor.hidden) { + currentGameWindow->onMouseRelativePosition(motionevent->xrel, motionevent->yrel); + } + else { + cursor.inuse = true; // mouse has taken priority as input, so draw the cursor as required + // provide own AbsolutePosition as SDL won't do this without a window + int tx = cursor.x + motionevent->xrel; + cursor.x = tx < 0 ? 0 : (tx >= fb.w ? fb.w-1 : tx); + int ty = cursor.y + motionevent->yrel; + cursor.y = ty < 0 ? 0 : (ty >= fb.h ? fb.h-1 : ty); + currentGameWindow->onMousePosition(cursor.x, cursor.y); + } +} + +void SDL2GameWindow::handleMouseClickEvent(SDL_MouseButtonEvent *clickevent) { + currentGameWindow->onMouseButton(cursor.x, cursor.y, clickevent->button, (clickevent->state == SDL_PRESSED ? MouseButtonAction::PRESS : MouseButtonAction::RELEASE)); +} + +void SDL2GameWindow::handleKeyboardEvent(SDL_KeyboardEvent *keyevent) { + KeyCode key = getKeyMinecraft(keyevent->keysym.scancode); + + KeyAction action; + if (keyevent->repeat) { + action = KeyAction::REPEAT; + } + else { + switch (keyevent->state) { + case SDL_PRESSED: + action = KeyAction::PRESS; + break; + case SDL_RELEASED: + action = KeyAction::RELEASE; + break; + default: + return; + } + } + + currentGameWindow->onKeyboard(key, action); +} + +void SDL2GameWindow::setCursorDisabled(bool disabled) { + if (!disabled) { + // warp mouse to center for initial display + cursor.x = fb.w / 2; + cursor.y = fb.h / 2; + } + + cursor.hidden = disabled; +} + +void SDL2GameWindow::setFullscreen(bool fullscreen) { + // NOOP - always fullscreen +} + +void SDL2GameWindow::setClipboardText(std::string const &text) { + // NOOP - nowhere to cut/paste to/from without a desktop and other applications +} + +void SDL2GameWindow::swapBuffers() { + if (!cursor.hidden && cursor.inuse) { + glFinish(); + drawCursor(); + } + std::swap(fb.frontbuff, fb.backbuff); + eglSwapBuffers(egl.display, egl.surface); +} + +void SDL2GameWindow::setSwapInterval(int interval) { + eglSwapInterval(egl.display, interval); +} + +// TODO fix QWERTY and numpad mapping. +KeyCode SDL2GameWindow::getKeyMinecraft(int keyCode) { + if (keyCode >= SDLK_F1 && keyCode <= SDLK_F12) + return (KeyCode) (keyCode - SDLK_F1 + (int) KeyCode::FN1); + + switch (keyCode) { + case SDLK_BACKSPACE: + return KeyCode::BACKSPACE; + case SDLK_TAB: + return KeyCode::TAB; + case SDLK_RETURN: + return KeyCode::ENTER; + case SDLK_LSHIFT: + return KeyCode::LEFT_SHIFT; + case SDLK_RSHIFT: + return KeyCode::RIGHT_SHIFT; + case SDLK_LCTRL: + return KeyCode::LEFT_CTRL; + case SDLK_RCTRL: + return KeyCode::RIGHT_CTRL; + case SDLK_PAUSE: + return KeyCode::PAUSE; + case SDLK_CAPSLOCK: + return KeyCode::CAPS_LOCK; + case SDLK_ESCAPE: + return KeyCode::ESCAPE; + case SDLK_PAGEUP: + return KeyCode::PAGE_UP; + case SDLK_PAGEDOWN: + return KeyCode::PAGE_DOWN; + case SDLK_END: + return KeyCode::END; + case SDLK_HOME: + return KeyCode::HOME; + case SDLK_LEFT: + return KeyCode::LEFT; + case SDLK_UP: + return KeyCode::UP; + case SDLK_RIGHT: + return KeyCode::RIGHT; + case SDLK_DOWN: + return KeyCode::DOWN; + case SDLK_INSERT: + return KeyCode::INSERT; + case SDLK_DELETE: + return KeyCode::DELETE; + case SDLK_NUMLOCKCLEAR: + return KeyCode::NUM_LOCK; + case SDLK_SCROLLLOCK: + return KeyCode::SCROLL_LOCK; + case SDLK_SEMICOLON: + return KeyCode::SEMICOLON; + case SDLK_EQUALS: + return KeyCode::EQUAL; + case SDLK_COMMA: + return KeyCode::COMMA; + case SDLK_MINUS: + return KeyCode::MINUS; + case SDLK_PERIOD: + return KeyCode::PERIOD; + case SDLK_SLASH: + return KeyCode::SLASH; + case SDLK_BACKQUOTE: + return KeyCode::GRAVE; + case SDLK_LEFTBRACKET: + return KeyCode::LEFT_BRACKET; + case SDLK_BACKSLASH: + return KeyCode::BACKSLASH; + case SDLK_RIGHTBRACKET: + return KeyCode::RIGHT_BRACKET; + case SDLK_QUOTE: + return KeyCode::APOSTROPHE; + case SDLK_APPLICATION: + return KeyCode::LEFT_SUPER; + case SDLK_LALT: + return KeyCode::LEFT_ALT; + case SDLK_RALT: + return KeyCode::RIGHT_ALT; + } + + if (keyCode < 256) + return (KeyCode) keyCode; + return KeyCode::UNKNOWN; +} diff --git a/src/window_sdl2.h b/src/window_sdl2.h new file mode 100644 index 0000000..8af9d0c --- /dev/null +++ b/src/window_sdl2.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include + + +struct bgra_pixel { + // channels in framebuffer order + char b; + char g; + char r; + char a; +}; + +class SDL2GameWindow : public GameWindow { + +private: + + struct framebuffer_state { + int fd; + bgra_pixel *mmap; + int mmap_size; + int w; + int h; + bgra_pixel *frontbuff; + bgra_pixel *backbuff; + } fb; + + struct egl_state { + EGLDisplay display; + EGLContext context; + EGLSurface surface; + } egl; + + struct cursor_state { + bgra_pixel **img; + int size; + bool hidden; + bool inuse; + int x; + int y; + } cursor; + + struct gamepad_state { + int count; + } gamepad; + + SDL2GameWindow *currentGameWindow; + + void abortMsg(const char *msg); + void initFrameBuffer(); + void initEGL(); + void clearColour(float shade); + void initSDL(); + void initCursor(); + void drawCursor(); + void handleControllerDeviceEvent(SDL_ControllerDeviceEvent *cdeviceevent); + void handleControllerAxisEvent(SDL_ControllerAxisEvent *caxisevent); + void handleControllerButtonEvent(SDL_ControllerButtonEvent *cbuttonevent); + void handleMouseMotionEvent(SDL_MouseMotionEvent *motionevent); + void handleMouseWheelEvent(SDL_MouseWheelEvent *wheelevent); + void handleMouseClickEvent(SDL_MouseButtonEvent *clickevent); + void handleKeyboardEvent(SDL_KeyboardEvent *event); + KeyCode getKeyMinecraft(int keyCode); + +public: + + SDL2GameWindow(const std::string& title, int width, int height, GraphicsApi api); + + ~SDL2GameWindow() override; + + void setIcon(std::string const& iconPath) override; + + void show() override; + + void close() override; + + void pollEvents() override; + + void setCursorDisabled(bool disabled) override; + + void setFullscreen(bool fullscreen) override; + + void getWindowSize(int& width, int& height) const override; + + void setClipboardText(std::string const& text) override; + + void swapBuffers() override; + + void setSwapInterval(int interval) override; + +};