metaforce/visigen/MainXlib.cpp

308 lines
9.7 KiB
C++

#include "VISIRenderer.hpp"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <GL/glx.h>
#include "athena/Global.hpp"
#include "logvisor/logvisor.hpp"
#include "../version.h"
#include <thread>
#include <condition_variable>
#include <cstdint>
#include <limits.h>
#include <signal.h>
#define MWM_HINTS_FUNCTIONS (1L << 0)
#define MWM_HINTS_DECORATIONS (1L << 1)
#define MWM_DECOR_BORDER (1L << 1)
#define MWM_DECOR_RESIZEH (1L << 2)
#define MWM_DECOR_TITLE (1L << 3)
#define MWM_DECOR_MENU (1L << 4)
#define MWM_DECOR_MINIMIZE (1L << 5)
#define MWM_DECOR_MAXIMIZE (1L << 6)
#define MWM_FUNC_RESIZE (1L << 1)
#define MWM_FUNC_MOVE (1L << 2)
#define MWM_FUNC_MINIMIZE (1L << 3)
#define MWM_FUNC_MAXIMIZE (1L << 4)
#define MWM_FUNC_CLOSE (1L << 5)
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr;
static const int ContextAttribList[7][7] = {
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 5, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 4, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 2, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 0, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
{GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_FLAGS_ARB,
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
};
static bool s_glxError;
static int ctxErrorHandler(Display* /*dpy*/, XErrorEvent* /*ev*/) {
s_glxError = true;
return 0;
}
static logvisor::Module Log("visigen-xlib");
static logvisor::Module AthenaLog("Athena");
static void AthenaExc(athena::error::Level level, const char* /*file*/, const char*, int /*line*/,
fmt::string_view fmt, fmt::format_args args) {
AthenaLog.vreport(logvisor::Level(level), fmt, args);
}
static Display* xDisp;
static Window windowId;
static void UpdatePercent(float percent) {
XLockDisplay(xDisp);
std::string title = fmt::format(FMT_STRING("VISIGen [{:g}%]"), double(percent * 100.f));
XChangeProperty(xDisp, windowId, XA_WM_NAME, XA_STRING, 8, PropModeReplace,
reinterpret_cast<const unsigned char*>(title.c_str()), int(title.size()));
XUnlockDisplay(xDisp);
}
/* Empty handler for SIGINT */
static void _sigint(int) {}
int main(int argc, char** argv) {
if (argc > 1 && !strcmp(argv[1], "--dlpackage")) {
fmt::print(FMT_STRING("{}\n"), METAFORCE_DLPACKAGE);
return 100;
}
/* Program is portable to all locales */
setlocale(LC_ALL, "");
logvisor::RegisterStandardExceptions();
logvisor::RegisterConsoleLogger();
atSetExceptionHandler(AthenaExc);
VISIRenderer renderer(argc, argv);
if (!XInitThreads()) {
Log.report(logvisor::Error, FMT_STRING("X doesn't support multithreading"));
return 1;
}
/* Open Xlib Display */
xDisp = XOpenDisplay(nullptr);
if (!xDisp) {
Log.report(logvisor::Error, FMT_STRING("Can't open X display"));
return 1;
}
/* Default screen */
int xDefaultScreen = DefaultScreen(xDisp);
Screen* screen = ScreenOfDisplay(xDisp, xDefaultScreen);
/* Query framebuffer configurations */
GLXFBConfig* fbConfigs = nullptr;
int numFBConfigs = 0;
fbConfigs = glXGetFBConfigs(xDisp, xDefaultScreen, &numFBConfigs);
if (!fbConfigs || numFBConfigs == 0) {
Log.report(logvisor::Error, FMT_STRING("glXGetFBConfigs failed"));
return 1;
}
VisualID selVisualId = 0;
GLXFBConfig selFBConfig = nullptr;
for (int i = 0; i < numFBConfigs; ++i) {
GLXFBConfig config = fbConfigs[i];
int visualId, depthSize, colorSize, doubleBuffer;
glXGetFBConfigAttrib(xDisp, config, GLX_VISUAL_ID, &visualId);
glXGetFBConfigAttrib(xDisp, config, GLX_DEPTH_SIZE, &depthSize);
glXGetFBConfigAttrib(xDisp, config, GLX_BUFFER_SIZE, &colorSize);
glXGetFBConfigAttrib(xDisp, config, GLX_DOUBLEBUFFER, &doubleBuffer);
/* Single-buffer only */
if (doubleBuffer)
continue;
if (colorSize >= 32 && depthSize >= 24 && visualId != 0) {
selFBConfig = config;
selVisualId = VisualID(visualId);
break;
}
}
XFree(fbConfigs);
if (!selFBConfig) {
Log.report(logvisor::Error, FMT_STRING("unable to find suitable pixel format"));
return 1;
}
XVisualInfo visTemplate = {};
visTemplate.screen = xDefaultScreen;
int numVisuals;
XVisualInfo* visualList = XGetVisualInfo(xDisp, VisualScreenMask, &visTemplate, &numVisuals);
Visual* selectedVisual = nullptr;
for (int i = 0; i < numVisuals; ++i) {
if (visualList[i].visualid == selVisualId) {
selectedVisual = visualList[i].visual;
break;
}
}
XFree(visualList);
/* Create colormap */
Colormap colormapId = XCreateColormap(xDisp, screen->root, selectedVisual, AllocNone);
/* Create window */
XSetWindowAttributes swa;
swa.colormap = colormapId;
swa.border_pixmap = 0;
swa.event_mask = 0;
int instIdx = -1;
if (argc > 3)
instIdx = atoi(argv[3]);
int x = 0;
int y = 0;
if (instIdx != -1) {
x = (instIdx & 1) != 0;
y = (instIdx & 2) != 0;
}
windowId = XCreateWindow(xDisp, screen->root, x, y, 768, 512, 10, CopyFromParent, CopyFromParent, selectedVisual,
CWBorderPixel | CWEventMask | CWColormap, &swa);
if (!glXCreateContextAttribsARB) {
glXCreateContextAttribsARB = reinterpret_cast<glXCreateContextAttribsARBProc>(
glXGetProcAddressARB(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")));
if (!glXCreateContextAttribsARB) {
Log.report(logvisor::Error, FMT_STRING("unable to resolve glXCreateContextAttribsARB"));
return 1;
}
}
s_glxError = false;
XErrorHandler oldHandler = XSetErrorHandler(ctxErrorHandler);
GLXContext glxCtx = nullptr;
for (uint32_t attribIdx = 0; attribIdx < std::extent<decltype(ContextAttribList)>::value; ++attribIdx) {
glxCtx = glXCreateContextAttribsARB(xDisp, selFBConfig, nullptr, True, ContextAttribList[attribIdx]);
if (glxCtx)
break;
}
XSetErrorHandler(oldHandler);
if (!glxCtx) {
Log.report(logvisor::Fatal, FMT_STRING("unable to make new GLX context"));
return 1;
}
GLXWindow glxWindow = glXCreateWindow(xDisp, selFBConfig, windowId, nullptr);
if (!glxWindow) {
Log.report(logvisor::Fatal, FMT_STRING("unable to make new GLX window"));
return 1;
}
XMapWindow(xDisp, windowId);
struct {
unsigned long flags = 0;
unsigned long functions = 0;
unsigned long decorations = 0;
long inputMode = 0;
unsigned long status = 0;
} wmHints;
Atom motifWmHints = XInternAtom(xDisp, "_MOTIF_WM_HINTS", True);
if (motifWmHints) {
wmHints.flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS;
wmHints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU;
wmHints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE;
XChangeProperty(xDisp, windowId, motifWmHints, motifWmHints, 32, PropModeReplace,
reinterpret_cast<unsigned char*>(&wmHints), 5);
}
/* SIGINT will be used to cancel main thread when client thread ends
* (also enables graceful quitting via ctrl-c) */
pthread_t mainThread = pthread_self();
struct sigaction s;
s.sa_handler = _sigint;
sigemptyset(&s.sa_mask);
s.sa_flags = 0;
sigaction(SIGINT, &s, nullptr);
sigaction(SIGUSR2, &s, nullptr);
sigset_t waitmask, origmask;
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGINT);
sigaddset(&waitmask, SIGUSR2);
pthread_sigmask(SIG_BLOCK, &waitmask, &origmask);
int x11Fd = ConnectionNumber(xDisp);
/* Spawn client thread */
std::mutex initmt;
std::condition_variable initcv;
std::unique_lock<std::mutex> outerLk(initmt);
std::thread clientThread([&]() {
std::unique_lock<std::mutex> innerLk(initmt);
innerLk.unlock();
initcv.notify_one();
XLockDisplay(xDisp);
if (!glXMakeContextCurrent(xDisp, glxWindow, glxWindow, glxCtx))
Log.report(logvisor::Fatal, FMT_STRING("unable to make GLX context current"));
XUnlockDisplay(xDisp);
UpdatePercent(0);
renderer.Run(UpdatePercent);
XLockDisplay(xDisp);
XClientMessageEvent exitEvent = {};
exitEvent.type = ClientMessage;
exitEvent.window = windowId;
exitEvent.format = 32;
XSendEvent(xDisp, windowId, 0, 0, reinterpret_cast<XEvent*>(&exitEvent));
XFlush(xDisp);
XUnlockDisplay(xDisp);
pthread_kill(mainThread, SIGUSR2);
});
initcv.wait(outerLk);
/* Begin application event loop */
bool clientRunning = true;
while (clientRunning) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(x11Fd, &fds);
if (pselect(x11Fd + 1, &fds, nullptr, nullptr, nullptr, &origmask) < 0) {
/* SIGINT/SIGUSR2 handled here */
if (errno == EINTR || errno == SIGUSR2)
break;
}
if (FD_ISSET(x11Fd, &fds)) {
XLockDisplay(xDisp);
while (XPending(xDisp)) {
XEvent event;
XNextEvent(xDisp, &event);
if (XFilterEvent(&event, None))
continue;
if (event.type == ClientMessage)
clientRunning = false;
}
XUnlockDisplay(xDisp);
}
}
renderer.Terminate();
if (clientThread.joinable())
clientThread.join();
glXDestroyWindow(xDisp, glxWindow);
XDestroyWindow(xDisp, windowId);
XCloseDisplay(xDisp);
return renderer.ReturnVal();
}