[sdl] Initial implementation of a basic SDL interface

pull/741/head
Hleb Valoshka 2020-04-08 14:04:14 +03:00
parent 9863f8bea9
commit 54da875bf8
7 changed files with 589 additions and 4 deletions

View File

@ -57,6 +57,7 @@ install:
- vcpkg install libepoxy:x86-windows
- vcpkg install eigen3:x86-windows
- vcpkg install freetype:x86-windows
- vcpkg install sdl2:x86-windows
- git pull
- vcpkg install cspice:x86-windows
- vcpkg integrate install
@ -70,7 +71,7 @@ build_script:
cd build
cmake -DCMAKE_PREFIX_PATH=%Qt5_DIR% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON -DENABLE_TTF=ON -DENABLE_TESTS=ON -DENABLE_GLEW=OFF ..
cmake -DCMAKE_PREFIX_PATH=%Qt5_DIR% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON -DENABLE_TTF=ON -DENABLE_TESTS=ON -DENABLE_SDL=OFF ..
cmake --build . --config Release -- /maxcpucount:4 /nologo

View File

@ -35,7 +35,7 @@ before_script:
- cd build
script:
- cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON -DENABLE_TTF=ON -DENABLE_TESTS=ON -DENABLE_GLEW=OFF ..
- cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON -DENABLE_TTF=ON -DENABLE_TESTS=ON -DENABLE_SDL=ON ..
- make -j $(nproc || echo 4)
- CTEST_OUTPUT_ON_FAILURE=1 ctest
@ -52,6 +52,7 @@ addons:
- qtbase5-dev-tools
- libqt5opengl5-dev
- libfreetype6-dev
- libsdl2-dev
homebrew:
packages:
- eigen
@ -64,4 +65,5 @@ addons:
- qt5
- freetype
- libepoxy
- sdl2
update: true

View File

@ -14,6 +14,11 @@ if(POLICY CMP0072)
endif()
endif()
# Remove leading and trailing whitespace from libraries linked
if(POLICY CMP0004)
cmake_policy(SET CMP0004 OLD)
endif()
project(celestia VERSION 1.7.0 LANGUAGES C CXX)
set(DISPLAY_NAME "Celestia")
#
@ -25,6 +30,7 @@ option(ENABLE_NLS "Enable interface translation? (Default: on)" ON)
option(ENABLE_GLUT "Build simple Glut frontend? (Default: on)" OFF)
option(ENABLE_GTK "Build GTK2 frontend (Unix only)? (Default: off)" OFF)
option(ENABLE_QT "Build Qt frontend? (Default: on)" ON)
option(ENABLE_SDL "Build SDL frontend? (Default: off)" OFF)
option(ENABLE_WIN "Build Windows native frontend? (Default: on)" ON)
option(ENABLE_THEORA "Support video capture to OGG Theora? (Default: on)" ON)
option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF)

View File

@ -117,11 +117,14 @@ make
sudo make install
```
[*] `INTERFACE` must be replaced with one of "`QT`", "`GTK`", or "`GLUT`".
[*] `INTERFACE` must be replaced with one of "`QT`", "`GTK`", "`SDL`" or
"`GLUT`".
Three interfaces are available for Celestia on Unix-like systems:
Four interfaces are available for Celestia on Unix-like systems:
- GLUT: minimal interface, barebone Celestia core with no toolbar or menu...
Disabled by default.
- SDL: minimal interface, barebone Celestia core with no toolbar or menu...
Disabled by default.
- GTK: A full interface with minimal dependencies, adds a menu, a configuration
dialog some other utilities. Legacy interface, may lack some new
features. Disabled by default.
@ -228,6 +231,7 @@ Install optional packages:
```
pacman -S mingw-w64-x86_64-fmt mingw-w64-x86_64-eigen3 mingw-w64-x86_64-luajit
pacman -S mingw-w64-x86_64-sld2
```
Clone the source and go to the source directory.
@ -325,6 +329,7 @@ List of supported parameters (passed as `-DPARAMETER=VALUE`):
| ENABLE_GLUT | bool | OFF | Build simple Glut frontend
| ENABLE_GTK | bool | \*\*OFF | Build legacy GTK2 frontend
| ENABLE_QT | bool | ON | Build Qt frontend
| ENABLE_SDL | bool | OFF | Build SQL frontend
| ENABLE_WIN | bool | \*\*\*ON | Build Windows native frontend
| ENABLE_THEORA | bool | \*\*ON | Support video capture to OGG Theora
| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files
@ -364,4 +369,5 @@ Here's the table which provides executable file names accordingly to interface:
| Qt5 | celestia-qt
| GTK | celestia-gtk
| GLUT | celestia-glut
| SDL | celestia-sdl
| WIN | celestia-win

View File

@ -59,4 +59,5 @@ cotire(celestia)
add_subdirectory(glut)
add_subdirectory(gtk)
add_subdirectory(qt)
add_subdirectory(sdl)
add_subdirectory(win32)

View File

@ -0,0 +1,11 @@
if(NOT ENABLE_SDL)
message(STATUS "SDL frontend is disabled.")
return()
endif()
find_package(SDL2 CONFIG REQUIRED)
set(SDL_SOURCES sdlmain.cpp)
add_executable(celestia-sdl ${SDL_SOURCES})
target_include_directories(celestia-sdl PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(celestia-sdl ${CELESTIA_LIBS} ${SDL2_LIBRARIES})
install(TARGETS celestia-sdl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -0,0 +1,558 @@
//#define GL_ES
#include <cctype>
#include <cstring>
#include <iostream>
#include <memory>
#include <fmt/printf.h>
#include <celengine/glsupport.h>
#include <celutil/gettext.h>
#include <SDL.h>
#ifdef GL_ES
#include <SDL_opengles2.h>
#else
#include <SDL_opengl.h>
#endif
#include <unistd.h>
#include <celestia/celestiacore.h>
namespace celestia
{
class SDL_Alerter : public CelestiaCore::Alerter
{
SDL_Window* window { nullptr };
public:
void fatalError(const std::string& msg)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"Fatal Error",
msg.c_str(),
window);
}
};
class SDL_Application
{
public:
SDL_Application() = delete;
SDL_Application(const std::string name, int w, int h) :
m_appName { name },
m_windowWidth { w },
m_windowHeight { h }
{
}
~SDL_Application();
static std::shared_ptr<SDL_Application> init(const std::string, int, int);
bool createOpenGLWindow();
bool initCelestiaCore();
void run();
const char* getError() const;
private:
void display();
// handlers
void handleTextInputEvent(const SDL_TextInputEvent &event);
void handleKeyPressEvent(const SDL_KeyboardEvent &event);
void handleKeyReleaseEvent(const SDL_KeyboardEvent &event);
void handleMousePressEvent(const SDL_MouseButtonEvent &event);
void handleMouseReleaseEvent(const SDL_MouseButtonEvent &event);
void handleMouseWheelEvent(const SDL_MouseWheelEvent &event);
void handleMouseMotionEvent(const SDL_MouseMotionEvent &event);
void handleWindowEvent(const SDL_WindowEvent &event);
// aux functions
void toggleFullscreen();
// state variables
std::string m_appName;
int m_windowWidth;
int m_windowHeight;
int m_lastX { 0 };
int m_lastY { 0 };
bool m_cursorVisible { true };
bool m_fullscreen { false };
CelestiaCore *m_appCore { nullptr };
SDL_Window *m_mainWindow { nullptr };
SDL_GLContext m_glContext { nullptr };
};
std::shared_ptr<SDL_Application>
SDL_Application::init(const std::string name, int w, int h)
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
return nullptr;
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
#ifdef GL_ES
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);
#endif
return std::make_shared<SDL_Application>(std::move(name), w, h);
}
SDL_Application::~SDL_Application()
{
delete m_appCore;
if (m_glContext != nullptr)
SDL_GL_DeleteContext(m_glContext);
if (m_mainWindow != nullptr)
SDL_DestroyWindow(m_mainWindow);
SDL_Quit();
}
bool
SDL_Application::createOpenGLWindow()
{
m_mainWindow = SDL_CreateWindow(m_appName.c_str(),
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
m_windowWidth,
m_windowHeight,
SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);
if (m_mainWindow == nullptr)
return false;
m_glContext = SDL_GL_CreateContext(m_mainWindow);
if (m_glContext == nullptr)
return false;
// First try to enable adaptive sync and then vsync
if (SDL_GL_SetSwapInterval(-1) == -1)
SDL_GL_SetSwapInterval(1);
return true;
}
const char*
SDL_Application::getError() const
{
return SDL_GetError();
}
void
SDL_Application::display()
{
m_appCore->draw();
SDL_GL_SwapWindow(m_mainWindow);
}
bool
SDL_Application::initCelestiaCore()
{
m_appCore = new CelestiaCore();
m_appCore->setAlerter(new SDL_Alerter());
bool ret = m_appCore->initSimulation();
return ret;
}
void
SDL_Application::run()
{
m_appCore->initRenderer();
m_appCore->getRenderer()->setRenderFlags(Renderer::DefaultRenderFlags);
m_appCore->start();
std::string tzName;
int dstBias;
if (GetTZInfo(tzName, dstBias))
{
m_appCore->setTimeZoneName(tzName);
m_appCore->setTimeZoneBias(dstBias);
}
m_appCore->resize(m_windowWidth, m_windowHeight);
SDL_StartTextInput();
bool quit = false;
while (!quit)
{
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
switch (event.type)
{
case SDL_QUIT:
quit = true;
break;
case SDL_TEXTINPUT:
handleTextInputEvent(event.text);
break;
case SDL_KEYDOWN:
handleKeyPressEvent(event.key);
break;
case SDL_KEYUP:
handleKeyReleaseEvent(event.key);
break;
case SDL_MOUSEBUTTONDOWN:
handleMousePressEvent(event.button);
break;
case SDL_MOUSEBUTTONUP:
handleMouseReleaseEvent(event.button);
break;
case SDL_MOUSEWHEEL:
handleMouseWheelEvent(event.wheel);
break;
case SDL_MOUSEMOTION:
handleMouseMotionEvent(event.motion);
break;
case SDL_WINDOWEVENT:
handleWindowEvent(event.window);
break;
default:
break;
}
}
m_appCore->tick();
display();
}
}
static int
toCelestiaKey(SDL_Keycode key)
{
switch (key)
{
case SDLK_DOWN: return CelestiaCore::Key_Down;
case SDLK_UP: return CelestiaCore::Key_Up;
case SDLK_LEFT: return CelestiaCore::Key_Left;
case SDLK_RIGHT: return CelestiaCore::Key_Right;
case SDLK_END: return CelestiaCore::Key_End;
case SDLK_HOME: return CelestiaCore::Key_Home;
case SDLK_PAGEDOWN: return CelestiaCore::Key_PageDown;
case SDLK_PAGEUP: return CelestiaCore::Key_PageUp;
case SDLK_RETURN:
case SDLK_ESCAPE:
case SDLK_BACKSPACE:
case SDLK_DELETE:
case SDLK_TAB:
case SDLK_SPACE:
return key;
case SDLK_F1: return CelestiaCore::Key_F1;
case SDLK_F2: return CelestiaCore::Key_F2;
case SDLK_F3: return CelestiaCore::Key_F3;
case SDLK_F4: return CelestiaCore::Key_F4;
case SDLK_F5: return CelestiaCore::Key_F5;
case SDLK_F6: return CelestiaCore::Key_F6;
case SDLK_F7: return CelestiaCore::Key_F7;
case SDLK_F8: return CelestiaCore::Key_F8;
case SDLK_F9: return CelestiaCore::Key_F9;
case SDLK_F10: return CelestiaCore::Key_F10;
case SDLK_F11: return CelestiaCore::Key_F11;
case SDLK_F12: return CelestiaCore::Key_F12;
case SDLK_KP_0: return CelestiaCore::Key_NumPad0;
case SDLK_KP_1: return CelestiaCore::Key_NumPad1;
case SDLK_KP_2: return CelestiaCore::Key_NumPad2;
case SDLK_KP_3: return CelestiaCore::Key_NumPad3;
case SDLK_KP_4: return CelestiaCore::Key_NumPad4;
case SDLK_KP_5: return CelestiaCore::Key_NumPad5;
case SDLK_KP_6: return CelestiaCore::Key_NumPad6;
case SDLK_KP_7: return CelestiaCore::Key_NumPad7;
case SDLK_KP_8: return CelestiaCore::Key_NumPad8;
case SDLK_KP_9: return CelestiaCore::Key_NumPad9;
case SDLK_KP_DECIMAL:
return CelestiaCore::Key_NumPadDecimal;
default:
if (key >= 32 && key <= 127)
return key;
return -1;
}
}
void
SDL_Application::handleKeyPressEvent(const SDL_KeyboardEvent &event)
{
switch (event.keysym.sym)
{
case SDLK_TAB:
case SDLK_BACKSPACE:
case SDLK_DELETE:
case SDLK_ESCAPE:
m_appCore->charEntered(event.keysym.sym, 0);
case SDLK_RETURN:
return;
}
int key = toCelestiaKey(event.keysym.sym);
if (key == -1)
return;
int mod = 0;
if ((event.keysym.mod & KMOD_CTRL) != 0)
{
int k = tolower(key);
if (k >= 'a' && k <= 'z')
{
key = k + 1 - 'a';
m_appCore->charEntered(key, mod);
return;
}
mod |= CelestiaCore::ControlKey;
}
if ((event.keysym.mod & KMOD_SHIFT) != 0)
mod |= CelestiaCore::ShiftKey;
m_appCore->keyDown(key, mod);
}
void
SDL_Application::handleKeyReleaseEvent(const SDL_KeyboardEvent &event)
{
if (event.keysym.sym == SDLK_RETURN)
{
if ((event.keysym.mod & KMOD_ALT) != 0)
toggleFullscreen();
else
m_appCore->charEntered(SDLK_RETURN, 0);
}
else
{
int key = toCelestiaKey(event.keysym.sym);
if (key == -1)
return;
int mod = 0;
if ((event.keysym.mod & KMOD_CTRL) != 0)
{
mod |= CelestiaCore::ControlKey;
if (key >= '0' && key <= '9')
m_appCore->charEntered(key, mod);
}
if ((event.keysym.mod & KMOD_SHIFT) != 0)
mod |= CelestiaCore::ShiftKey;
m_appCore->keyUp(key, 0);
}
}
void
SDL_Application::handleTextInputEvent(const SDL_TextInputEvent &event)
{
m_appCore->charEntered(event.text, 0);
}
static int
toCelestiaButton(int button)
{
switch (button)
{
case SDL_BUTTON_LEFT:
return CelestiaCore::LeftButton;
case SDL_BUTTON_MIDDLE:
return CelestiaCore::MiddleButton;
case SDL_BUTTON_RIGHT:
return CelestiaCore::RightButton;
default:
return -1;
}
}
void
SDL_Application::handleMousePressEvent(const SDL_MouseButtonEvent &event)
{
int button = toCelestiaButton(event.button);
if (button == -1)
return;
m_lastX = event.x;
m_lastY = event.y;
m_appCore->mouseButtonDown(event.x, event.y, button);
}
void
SDL_Application::handleMouseReleaseEvent(const SDL_MouseButtonEvent &event)
{
int button = toCelestiaButton(event.button);;
if (button == -1)
return;
if (button & (CelestiaCore::LeftButton | CelestiaCore::RightButton))
{
if (!m_cursorVisible)
{
SDL_ShowCursor(SDL_ENABLE);
m_cursorVisible = true;
SDL_WarpMouseInWindow(m_mainWindow, m_lastX, m_lastY);
}
}
m_lastX = event.x;
m_lastY = event.y;
m_appCore->mouseButtonUp(event.x, event.y, button);
}
void
SDL_Application::handleMouseWheelEvent(const SDL_MouseWheelEvent &event)
{
if (event.y > 0) // scroll up
m_appCore->mouseWheel(-1.0f, 0);
else if (event.y < 0) // scroll down
m_appCore->mouseWheel(1.0f, 0);
}
void
SDL_Application::handleMouseMotionEvent(const SDL_MouseMotionEvent &event)
{
if (event.state & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK))
{
int buttons = 0;
if (event.state & SDL_BUTTON_LMASK)
buttons |= CelestiaCore::LeftButton;
if (event.state & SDL_BUTTON_RMASK)
buttons |= CelestiaCore::RightButton;
int x = event.x - m_lastX;
int y = event.y - m_lastY;
if (m_cursorVisible)
{
SDL_ShowCursor(SDL_DISABLE);
m_cursorVisible = false;
m_lastX = event.x;
m_lastY = event.y;
}
m_appCore->mouseMove(x, y, buttons);
SDL_WarpMouseInWindow(m_mainWindow, m_lastX, m_lastY);
}
}
void
SDL_Application::handleWindowEvent(const SDL_WindowEvent &event)
{
switch (event.event)
{
case SDL_WINDOWEVENT_RESIZED:
m_windowWidth = event.data1;
m_windowHeight = event.data2;
m_appCore->resize(m_windowWidth, m_windowHeight);
break;
default:
break;
}
}
void
SDL_Application::toggleFullscreen()
{
int ret = 0;
if (m_fullscreen)
{
ret = SDL_SetWindowFullscreen(m_mainWindow, 0);
if (ret == 0)
{
m_fullscreen = false;
SDL_SetWindowSize(m_mainWindow, m_windowWidth, m_windowHeight);
m_appCore->resize(m_windowWidth, m_windowHeight);
}
}
else
{
SDL_DisplayMode dm;
if (SDL_GetDesktopDisplayMode(0, &dm) == 0)
{
SDL_SetWindowSize(m_mainWindow, dm.w, dm.h);
m_appCore->resize(dm.w, dm.h);
}
// First try to activate real fullscreen mode
ret = SDL_SetWindowFullscreen(m_mainWindow, SDL_WINDOW_FULLSCREEN);
// Then try to emulate fulscreen resizing to the desktop size
if (ret == 0)
ret = SDL_SetWindowFullscreen(m_mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
if (ret == 0)
m_fullscreen = true;
}
}
}
void
FatalError(const std::string &message)
{
int ret = SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"Fatal Error",
message.c_str(),
nullptr);
if (ret < 0)
std::cerr << message << std::endl;
}
void DumpGLInfo()
{
const char* s;
s = reinterpret_cast<const char*>(glGetString(GL_VERSION));
if (s != nullptr)
std::cout << s << '\n';
s = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
if (s != nullptr)
std::cout << s << '\n';
s = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
if (s != nullptr)
std::cout << s << '\n';
s = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
if (s != nullptr)
std::cout << s << '\n';
}
using namespace celestia;
int main(int argc, char **argv)
{
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "C");
bindtextdomain(PACKAGE, LOCALEDIR);
bind_textdomain_codeset(PACKAGE, "UTF-8");
textdomain(PACKAGE);
if (chdir(CONFIG_DATA_DIR) == -1)
{
FatalError(fmt::sprintf("Cannot chdir to '%s', probably due to improper installation",
CONFIG_DATA_DIR));
return 1;
}
auto app = SDL_Application::init("Celestia", 640, 480);
if (app == nullptr)
{
FatalError(fmt::sprintf("Could not initialize SDL! Error: %s",
app->getError()));
return 2;
}
if (!app->initCelestiaCore())
{
FatalError("Could not initialize Celestia!");
return 3;
}
if (!app->createOpenGLWindow())
{
FatalError(fmt::sprintf("Could not create a OpenGL window! Error: %s",
app->getError()));
return 4;
}
gl::init();
#ifndef GL_ES
if (!gl::checkVersion(gl::GL_2_1))
{
FatalError("Celestia requires OpenGL 2.1!");
return 5;
}
#endif
DumpGLInfo();
app->run();
return 0;
}