Shipwright/libultraship/libultraship/Lib/Fast3D/gfx_glx.cpp

625 lines
20 KiB
C++

#if defined(__linux__) && defined(X11_SUPPORTED)
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <GL/glx.h>
#include <X11/extensions/Xrandr.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include "gfx_window_manager_api.h"
#include "gfx_screen_config.h"
#define GFX_API_NAME "GLX - OpenGL"
#ifdef VERSION_EU
#define FRAME_INTERVAL_US_NUMERATOR 40000
#define FRAME_INTERVAL_US_DENOMINATOR 1
#else
#define FRAME_INTERVAL_US_NUMERATOR 100000
#define FRAME_INTERVAL_US_DENOMINATOR 3
#endif
const struct {
const char *name;
int scancode;
} keymap_name_to_scancode[] = {
{"ESC", 0x01},
{"AE01", 0x02 },
{"AE02", 0x03 },
{"AE03", 0x04 },
{"AE04", 0x05 },
{"AE05", 0x06 },
{"AE06", 0x07 },
{"AE07", 0x08 },
{"AE08", 0x09 },
{"AE09", 0x0a },
{"AE10", 0x0b },
{"AE11", 0x0c },
{"AE12", 0x0d },
{"BKSP", 0x0e },
{"TAB", 0x0f },
{"AD01", 0x10 },
{"AD02", 0x11 },
{"AD03", 0x12 },
{"AD04", 0x13 },
{"AD05", 0x14 },
{"AD06", 0x15 },
{"AD07", 0x16 },
{"AD08", 0x17 },
{"AD09", 0x18 },
{"AD10", 0x19 },
{"AD11", 0x1a },
{"AD12", 0x1b },
{"RTRN", 0x1c },
{"LCTL", 0x1d },
{"AC01", 0x1e },
{"AC02", 0x1f },
{"AC03", 0x20 },
{"AC04", 0x21 },
{"AC05", 0x22 },
{"AC06", 0x23 },
{"AC07", 0x24 },
{"AC08", 0x25 },
{"AC09", 0x26 },
{"AC10", 0x27 },
{"AC11", 0x28 },
{"TLDE", 0x29 },
{"LFSH", 0x2a },
{"BKSL", 0x2b },
{"AB01", 0x2c },
{"AB02", 0x2d },
{"AB03", 0x2e },
{"AB04", 0x2f },
{"AB05", 0x30 },
{"AB06", 0x31 },
{"AB07", 0x32 },
{"AB08", 0x33 },
{"AB09", 0x34 },
{"AB10", 0x35 },
{"RTSH", 0x36 },
{"KPMU", 0x37 },
{"LALT", 0x38 },
{"SPCE", 0x39 },
{"CAPS", 0x3a },
{"FK01", 0x3b },
{"FK02", 0x3c },
{"FK03", 0x3d },
{"FK04", 0x3e },
{"FK05", 0x3f },
{"FK06", 0x40 },
{"FK07", 0x41 },
{"FK08", 0x42 },
{"FK09", 0x43 },
{"FK10", 0x44 },
{"NMLK", 0x45 },
{"SCLK", 0x46 },
{"KP7", 0x47 },
{"KP8", 0x48 },
{"KP9", 0x49 },
{"KPSU", 0x4a },
{"KP4", 0x4b },
{"KP5", 0x4c },
{"KP6", 0x4d },
{"KPAD", 0x4e },
{"KP1", 0x4f },
{"KP2", 0x50 },
{"KP3", 0x51 },
{"KP0", 0x52 },
{"KPDL", 0x53 },
{"LVL3", 0x54 }, // correct?
{"", 0x55 }, // not mapped?
{"LSGT", 0x56 },
{"FK11", 0x57 },
{"FK12", 0x58 },
{"AB11", 0x59 },
{"KATA", 0 },
{"HIRA", 0 },
{"HENK", 0 },
{"HKTG", 0 },
{"MUHE", 0 },
{"JPCM", 0 },
{"KPEN", 0x11c },
{"RCTL", 0x11d },
{"KPDV", 0x135 },
{"PRSC", 0x54 }, // ?
{"RALT", 0x138 },
{"LNFD", 0 },
{"HOME", 0x147 },
{"UP", 0x148 },
{"PGUP", 0x149 },
{"LEFT", 0x14b },
{"RGHT", 0x14d },
{"END", 0x14f },
{"DOWN", 0x150 },
{"PGDN", 0x151 },
{"INS", 0x152 },
{"DELE", 0x153 },
{"PAUS", 0x21d },
{"LWIN", 0x15b },
{"RWIN", 0x15c },
{"COMP", 0x15d },
};
static struct {
Display *dpy;
Window root;
Window win;
Atom atom_wm_state;
Atom atom_wm_state_fullscreen;
Atom atom_wm_delete_window;
bool is_fullscreen;
bool is_running = true;
void (*on_fullscreen_changed)(bool is_now_fullscreen);
int keymap[256];
bool (*on_key_down)(int scancode);
bool (*on_key_up)(int scancode);
void (*on_all_keys_up)(void);
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
PFNGLXSWAPBUFFERSMSCOMLPROC glXSwapBuffersMscOML;
PFNGLXWAITFORSBCOMLPROC glXWaitForSbcOML;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
bool has_oml_sync_control;
uint64_t ust0;
int64_t last_msc;
uint64_t wanted_ust; // multiplied by FRAME_INTERVAL_US_DENOMINATOR
uint64_t vsync_interval;
uint64_t last_ust;
int64_t target_msc;
bool dropped_frame;
bool has_sgi_video_sync;
uint64_t last_sync_counter;
int64_t this_msc;
int64_t this_ust;
} glx;
static int64_t get_time(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
static int64_t adjust_sync_counter(uint32_t counter) {
uint32_t hi = glx.last_sync_counter >> 32;
uint32_t lo = (uint32_t)glx.last_sync_counter;
if (lo >= 0x80000000U && counter < 0x80000000U) {
// Wrapped
++hi;
}
glx.last_sync_counter = ((uint64_t)hi << 32) | counter;
return glx.last_sync_counter;
}
static int64_t glXWaitVideoSyncSGI_wrapper(void) {
unsigned int counter = 0;
glx.glXWaitVideoSyncSGI(1, 0, &counter);
return adjust_sync_counter(counter);
}
static int64_t glXGetVideoSyncSGI_wrapper(void) {
unsigned int counter = 0;
glx.glXGetVideoSyncSGI(&counter);
return adjust_sync_counter(counter);
}
static void init_keymap(void) {
XkbDescPtr desc = XkbGetMap(glx.dpy, 0, XkbUseCoreKbd);
XkbGetNames(glx.dpy, XkbKeyNamesMask, desc);
for (int i = desc->min_key_code; i <= desc->max_key_code && i < 256; i++) {
char name[XkbKeyNameLength + 1];
memcpy(name, desc->names->keys[i].name, XkbKeyNameLength);
name[XkbKeyNameLength] = '\0';
for (size_t j = 0; j < sizeof(keymap_name_to_scancode) / sizeof(keymap_name_to_scancode[0]); j++) {
if (strcmp(keymap_name_to_scancode[j].name, name) == 0) {
glx.keymap[i] = keymap_name_to_scancode[j].scancode;
break;
}
}
}
XkbFreeNames(desc, XkbKeyNamesMask, True);
XkbFreeKeyboard(desc, 0, True);
}
static void gfx_glx_show_cursor(bool hide) {
// Removes distracting mouse cursor during fullscreen play
if (hide) {
Cursor hideCursor;
Pixmap bitmapNoData;
XColor black;
static char noData[] = { 0,0,0,0,0,0,0,0 };
black.red = black.green = black.blue = 0;
bitmapNoData = XCreateBitmapFromData(glx.dpy, glx.win, noData, 8, 8);
hideCursor = XCreatePixmapCursor(glx.dpy, bitmapNoData, bitmapNoData,
&black, &black, 0, 0);
XDefineCursor(glx.dpy, glx.win, hideCursor);
XSync(glx.dpy, False);
XFreeCursor(glx.dpy, hideCursor);
XFreePixmap(glx.dpy, bitmapNoData);
} else {
XUndefineCursor(glx.dpy, glx.win);
XSync(glx.dpy, False);
}
}
static void gfx_glx_set_fullscreen_state(bool on, bool call_callback) {
if (glx.is_fullscreen == on) {
return;
}
glx.is_fullscreen = on;
XEvent xev;
xev.xany.type = ClientMessage;
xev.xclient.message_type = glx.atom_wm_state;
xev.xclient.format = 32;
xev.xclient.window = glx.win;
xev.xclient.data.l[0] = on;
xev.xclient.data.l[1] = glx.atom_wm_state_fullscreen;
xev.xclient.data.l[2] = 0;
xev.xclient.data.l[3] = 0;
XSendEvent(glx.dpy, glx.root, 0, SubstructureNotifyMask | SubstructureRedirectMask, &xev);
gfx_glx_show_cursor(on);
if (glx.on_fullscreen_changed != NULL && call_callback) {
glx.on_fullscreen_changed(on);
}
}
static bool gfx_glx_check_extension(const char *extensions, const char *extension) {
size_t len = strlen(extension);
const char *pos = extensions;
while ((pos = strstr(pos, extension)) != NULL) {
if ((pos[len] == ' ' || pos[len] == '\0') && (pos == extensions || pos[-1] == ' ')) {
return true;
}
if (pos[len] == '\0') {
break;
}
pos += len + 1;
}
return false;
}
static void gfx_glx_init(const char *game_name, bool start_in_fullscreen, u_int32_t width, uint32_t height) {
// On NVIDIA proprietary driver, make the driver queue up to two frames on glXSwapBuffers,
// which means that glXSwapBuffers should be non-blocking,
// if we are sure to wait at least one vsync interval between calls.
setenv("__GL_MaxFramesAllowed", "2", true);
glx.dpy = XOpenDisplay(NULL);
if (glx.dpy == NULL) {
fprintf(stderr, "Cannot connect to X server\n");
exit(1);
}
int screen = DefaultScreen(glx.dpy);
glx.root = RootWindow(glx.dpy, screen);
GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };
XVisualInfo *vi = glXChooseVisual(glx.dpy, 0, att);
if (vi == NULL) {
fprintf(stderr, "No appropriate GLX visual found\n");
exit(1);
}
Colormap cmap = XCreateColormap(glx.dpy, glx.root, vi->visual, AllocNone);
XSetWindowAttributes swa;
swa.colormap = cmap;
swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | FocusChangeMask;
glx.win = XCreateWindow(glx.dpy, glx.root, 0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa);
glx.atom_wm_state = XInternAtom(glx.dpy, "_NET_WM_STATE", False);
glx.atom_wm_state_fullscreen = XInternAtom(glx.dpy, "_NET_WM_STATE_FULLSCREEN", False);
glx.atom_wm_delete_window = XInternAtom(glx.dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(glx.dpy, glx.win, &glx.atom_wm_delete_window, 1);
XMapWindow(glx.dpy, glx.win);
if (start_in_fullscreen) {
gfx_glx_set_fullscreen_state(true, false);
}
char title[512];
int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME);
XStoreName(glx.dpy, glx.win, title);
GLXContext glc = glXCreateContext(glx.dpy, vi, NULL, GL_TRUE);
glXMakeCurrent(glx.dpy, glx.win, glc);
init_keymap();
const char *extensions = glXQueryExtensionsString(glx.dpy, screen);
if (gfx_glx_check_extension(extensions, "GLX_OML_sync_control")) {
glx.glXGetSyncValuesOML = (PFNGLXGETSYNCVALUESOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML");
glx.glXSwapBuffersMscOML = (PFNGLXSWAPBUFFERSMSCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapBuffersMscOML");
glx.glXWaitForSbcOML = (PFNGLXWAITFORSBCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitForSbcOML");
}
if (gfx_glx_check_extension(extensions, "GLX_EXT_swap_control")) {
glx.glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT");
}
if (gfx_glx_check_extension(extensions, "GLX_SGI_swap_control")) {
glx.glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI");
}
if (gfx_glx_check_extension(extensions, "GLX_SGI_video_sync")) {
glx.glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXGetVideoSyncSGI");
glx.glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitVideoSyncSGI");
}
int64_t ust, msc, sbc;
if (glx.glXGetSyncValuesOML != NULL && glx.glXGetSyncValuesOML(glx.dpy, glx.win, &ust, &msc, &sbc)) {
glx.has_oml_sync_control = true;
glx.ust0 = (uint64_t)ust;
} else {
glx.ust0 = get_time();
if (glx.glXSwapIntervalEXT != NULL) {
glx.glXSwapIntervalEXT(glx.dpy, glx.win, 1);
} else if (glx.glXSwapIntervalSGI != NULL) {
glx.glXSwapIntervalSGI(1);
}
if (glx.glXGetVideoSyncSGI != NULL) {
// Try if it really works
unsigned int count;
if (glx.glXGetVideoSyncSGI(&count) == 0) {
glx.last_sync_counter = count;
glx.has_sgi_video_sync = true;
}
}
}
glx.vsync_interval = 16666;
}
static void gfx_glx_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
glx.on_fullscreen_changed = on_fullscreen_changed;
}
static void gfx_glx_set_fullscreen(bool enable) {
gfx_glx_set_fullscreen_state(enable, true);
}
static void gfx_glx_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
glx.on_key_down = on_key_down;
glx.on_key_up = on_key_up;
glx.on_all_keys_up = on_all_keys_up;
}
static void gfx_glx_main_loop(void (*run_one_game_iter)(void)) {
while (glx.is_running) {
run_one_game_iter();
}
}
static void gfx_glx_get_dimensions(uint32_t *width, uint32_t *height) {
XWindowAttributes attributes;
XGetWindowAttributes(glx.dpy, glx.win, &attributes);
*width = attributes.width;
*height = attributes.height;
}
static void gfx_glx_handle_events(void) {
while (XPending(glx.dpy)) {
XEvent xev;
XNextEvent(glx.dpy, &xev);
if (xev.type == FocusOut) {
if (glx.on_all_keys_up != NULL) {
glx.on_all_keys_up();
}
}
if (xev.type == KeyPress || xev.type == KeyRelease) {
if (xev.xkey.keycode < 256) {
int scancode = glx.keymap[xev.xkey.keycode];
if (scancode != 0) {
if (xev.type == KeyPress) {
if (scancode == 0x44) { // F10
gfx_glx_set_fullscreen_state(!glx.is_fullscreen, true);
}
if (glx.on_key_down != NULL) {
glx.on_key_down(scancode);
}
} else {
if (glx.on_key_up != NULL) {
glx.on_key_up(scancode);
}
}
}
}
}
if (xev.type == ClientMessage && (Atom)xev.xclient.data.l[0] == glx.atom_wm_delete_window) {
glx.is_running = false;
}
}
}
static bool gfx_glx_start_frame(void) {
return true;
}
static void gfx_glx_swap_buffers_begin(void) {
glx.wanted_ust += FRAME_INTERVAL_US_NUMERATOR; // advance 1/30 seconds on JP/US or 1/25 seconds on EU
if (!glx.has_oml_sync_control && !glx.has_sgi_video_sync) {
glFlush();
uint64_t target = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR;
uint64_t now;
while (target > (now = (uint64_t)get_time() - glx.ust0)) {
struct timespec ts = {(time_t)((target - now) / 1000000), (time_t)(((target - now) % 1000000) * 1000)};
if (nanosleep(&ts, NULL) == 0) {
break;
}
}
if (target + 2 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR < now) {
if (target + 32 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR >= now) {
printf("Dropping frame\n");
glx.dropped_frame = true;
return;
} else {
// Reset timer since we are way out of sync
glx.wanted_ust = now * FRAME_INTERVAL_US_DENOMINATOR;
}
}
glXSwapBuffers(glx.dpy, glx.win);
glx.dropped_frame = false;
return;
}
double vsyncs_to_wait = (int64_t)(glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR - glx.last_ust) / (double)glx.vsync_interval;
if (vsyncs_to_wait <= 0) {
printf("Dropping frame\n");
// Drop frame
glx.dropped_frame = true;
return;
}
if (floor(vsyncs_to_wait) != vsyncs_to_wait) {
uint64_t left_ust = glx.last_ust + floor(vsyncs_to_wait) * glx.vsync_interval;
uint64_t right_ust = glx.last_ust + ceil(vsyncs_to_wait) * glx.vsync_interval;
uint64_t adjusted_wanted_ust = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR + (glx.last_ust + FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR > glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000);
int64_t diff_left = adjusted_wanted_ust - left_ust;
int64_t diff_right = right_ust - adjusted_wanted_ust;
if (diff_left < 0) {
diff_left = -diff_left;
}
if (diff_right < 0) {
diff_right = -diff_right;
}
if (diff_left < diff_right) {
vsyncs_to_wait = floor(vsyncs_to_wait);
} else {
vsyncs_to_wait = ceil(vsyncs_to_wait);
}
if (vsyncs_to_wait <= -4) {
printf("vsyncs_to_wait became -4 or less so dropping frame\n");
glx.dropped_frame = true;
return;
} else if (vsyncs_to_wait < 1) {
vsyncs_to_wait = 1;
}
}
glx.dropped_frame = false;
//printf("Vsyncs to wait: %d, diff: %d\n", (int)vsyncs_to_wait, (int)(glx.last_ust + (int64_t)vsyncs_to_wait * glx.vsync_interval - glx.wanted_ust / 3));
if (vsyncs_to_wait > 30) {
// Unreasonable, so change to 2
vsyncs_to_wait = 2;
}
glx.target_msc = glx.last_msc + vsyncs_to_wait;
if (glx.has_oml_sync_control) {
glx.glXSwapBuffersMscOML(glx.dpy, glx.win, glx.target_msc, 0, 0);
} else if (glx.has_sgi_video_sync) {
glFlush(); // Try to submit pending work. Don't use glFinish since that busy loops on NVIDIA proprietary driver.
//uint64_t counter0;
uint64_t counter1, counter2;
//uint64_t before_wait = get_time();
counter1 = glXGetVideoSyncSGI_wrapper();
//counter0 = counter1;
//int waits = 0;
while (counter1 < (uint64_t)glx.target_msc - 1) {
counter1 = glXWaitVideoSyncSGI_wrapper();
//++waits;
}
//uint64_t before = get_time();
glXSwapBuffers(glx.dpy, glx.win);
counter2 = glXGetVideoSyncSGI_wrapper();
while (counter2 < (uint64_t)glx.target_msc) {
counter2 = glXWaitVideoSyncSGI_wrapper();
}
uint64_t after = get_time();
//printf("%.3f %.3f %.3f\t%.3f\t%u %d %.2f %u %d\n", before_wait * 0.000060, before * 0.000060, after * 0.000060, (after - before) * 0.000060, counter0, counter2 - counter0, vsyncs_to_wait, (unsigned int)glx.target_msc, waits);
glx.this_msc = counter2;
glx.this_ust = after;
}
}
static void gfx_glx_swap_buffers_end(void) {
if (glx.dropped_frame || (!glx.has_oml_sync_control && !glx.has_sgi_video_sync)) {
return;
}
int64_t ust, msc, sbc;
if (glx.has_oml_sync_control) {
if (!glx.glXWaitForSbcOML(glx.dpy, glx.win, 0, &ust, &msc, &sbc)) {
// X connection broke or something?
glx.last_ust += (glx.target_msc - glx.last_msc) * glx.vsync_interval;
glx.last_msc = glx.target_msc;
return;
}
} else {
ust = glx.this_ust;
msc = glx.this_msc;
}
uint64_t this_ust = ust - glx.ust0;
uint64_t vsyncs_passed = msc - glx.last_msc;
bool bad_vsync_interval = false;
if (glx.last_ust != 0 && vsyncs_passed != 0) {
uint64_t new_vsync_interval = (this_ust - glx.last_ust) / vsyncs_passed;
if (new_vsync_interval <= 500000) {
// Should be less than 0.5 seconds to be trusted
glx.vsync_interval = new_vsync_interval;
} else {
bad_vsync_interval = true;
}
//printf("glx.vsync_interval: %d\n", (int)glx.vsync_interval);
}
glx.last_ust = this_ust;
glx.last_msc = msc;
if (msc != glx.target_msc) {
printf("Frame too late by %d vsyncs\n", (int)(msc - glx.target_msc));
}
if (msc - glx.target_msc >= 8 || bad_vsync_interval) {
// Frame arrived way too late, so reset timer from here
printf("Reseting timer\n");
glx.wanted_ust = this_ust * FRAME_INTERVAL_US_DENOMINATOR;
}
}
static double gfx_glx_get_time(void) {
return 0.0;
}
static void gfx_glx_set_frame_divisor(int divisor) {
// TODO
}
struct GfxWindowManagerAPI gfx_glx = {
gfx_glx_init,
gfx_glx_set_keyboard_callbacks,
gfx_glx_set_fullscreen_changed_callback,
gfx_glx_set_fullscreen,
gfx_glx_show_cursor,
gfx_glx_main_loop,
gfx_glx_get_dimensions,
gfx_glx_handle_events,
gfx_glx_start_frame,
gfx_glx_swap_buffers_begin,
gfx_glx_swap_buffers_end,
gfx_glx_get_time,
gfx_glx_set_frame_divisor,
};
#endif