flycast/core/linux-dist/x11.cpp

636 lines
16 KiB
C++
Raw Normal View History

#if defined(SUPPORT_X11)
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#if !defined(GLES)
2015-08-17 21:46:28 +00:00
#include <GL/gl.h>
#include <GL/glx.h>
#endif
#include "types.h"
#include "cfg/cfg.h"
2019-09-07 12:37:39 +00:00
#include "x11.h"
#include "main.h"
2019-02-06 18:57:13 +00:00
#include "rend/gui.h"
#include "input/gamepad.h"
#include "icon.h"
2015-08-24 22:54:23 +00:00
#if FEAT_HAS_NIXPROF
#include "profiler/profiler.h"
#endif
#include "x11_keyboard.h"
2019-10-05 09:50:14 +00:00
#ifdef USE_VULKAN
#include "rend/vulkan/vulkan.h"
static VulkanContext *vulkanContext;
#endif
2015-08-24 22:54:23 +00:00
#if defined(TARGET_PANDORA)
#define DEFAULT_FULLSCREEN true
2015-08-21 00:16:48 +00:00
#define DEFAULT_WINDOW_WIDTH 800
#else
#define DEFAULT_FULLSCREEN false
2015-08-21 00:16:48 +00:00
#define DEFAULT_WINDOW_WIDTH 640
#endif
2015-08-21 00:16:48 +00:00
#define DEFAULT_WINDOW_HEIGHT 480
class MouseInputMapping : public InputMapping
{
public:
MouseInputMapping()
{
name = "X11 Mouse";
set_button(DC_BTN_A, Button1);
set_button(DC_BTN_B, Button3);
set_button(DC_BTN_START, Button2);
dirty = false;
}
};
class X11MouseGamepadDevice : public GamepadDevice
{
public:
X11MouseGamepadDevice(int maple_port) : GamepadDevice(maple_port, "X11")
{
_name = "Mouse";
_unique_id = "x11_mouse";
if (!find_mapping())
input_mapper = new MouseInputMapping();
}
bool gamepad_btn_input(u32 code, bool pressed) override
{
if (gui_is_open())
// Don't register mouse clicks as gamepad presses when gui is open
// This makes the gamepad presses to be handled first and the mouse position to be ignored
// TODO Make this generic
return false;
else
return GamepadDevice::gamepad_btn_input(code, pressed);
}
};
int x11_keyboard_input = 0;
static std::shared_ptr<X11KeyboardDevice> x11_keyboard;
static std::shared_ptr<X11KbGamepadDevice> kb_gamepad;
static std::shared_ptr<X11MouseGamepadDevice> mouse_gamepad;
int x11_width;
int x11_height;
int ndcid = 0;
void* x11_glc = NULL;
bool x11_fullscreen = false;
Atom wmDeleteMessage;
void* x11_vis;
2018-07-16 15:31:52 +00:00
extern bool dump_frame_switch;
2019-09-07 12:37:39 +00:00
void dc_exit();
enum
{
_NET_WM_STATE_REMOVE =0,
_NET_WM_STATE_ADD = 1,
_NET_WM_STATE_TOGGLE =2
};
void x11_window_set_fullscreen(bool fullscreen)
{
XEvent xev;
xev.xclient.type = ClientMessage;
xev.xclient.window = (Window)x11_win;
xev.xclient.message_type = XInternAtom((Display*)x11_disp, "_NET_WM_STATE", False);
xev.xclient.format = 32;
xev.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE
xev.xclient.data.l[1] = XInternAtom((Display*)x11_disp, "_NET_WM_STATE_FULLSCREEN", True);
xev.xclient.data.l[2] = 0; // no second property to toggle
xev.xclient.data.l[3] = 1;
xev.xclient.data.l[4] = 0;
INFO_LOG(RENDERER, "x11: setting fullscreen to %d", fullscreen);
XSendEvent((Display*)x11_disp, DefaultRootWindow((Display*)x11_disp), False, SubstructureNotifyMask, &xev);
}
void event_x11_handle()
{
XEvent event;
while(XPending((Display *)x11_disp))
{
XNextEvent((Display *)x11_disp, &event);
if (event.type == ClientMessage &&
event.xclient.data.l[0] == wmDeleteMessage)
dc_exit();
2018-10-01 18:34:35 +00:00
else if (event.type == ConfigureNotify)
{
x11_width = event.xconfigure.width;
x11_height = event.xconfigure.height;
}
}
}
2018-09-18 07:27:16 +00:00
extern u32 mo_buttons;
extern f32 mo_x_delta;
extern f32 mo_y_delta;
extern f32 mo_wheel_delta;
2018-10-01 18:34:35 +00:00
extern s32 mo_x_abs;
extern s32 mo_y_abs;
2018-09-18 07:27:16 +00:00
static bool capturing_mouse;
static Cursor empty_cursor = None;
static Cursor create_empty_cursor()
2015-08-17 21:46:28 +00:00
{
2018-09-18 07:27:16 +00:00
if (empty_cursor == None)
2015-08-17 21:46:28 +00:00
{
2018-09-18 07:27:16 +00:00
Display *display = (Display*)x11_disp;
char data[] = { 0 };
2015-08-17 21:46:28 +00:00
2018-09-18 07:27:16 +00:00
XColor color;
color.red = color.green = color.blue = 0;
Pixmap pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
data, 1, 1);
if (pixmap)
2015-08-17 21:46:28 +00:00
{
2018-09-18 07:27:16 +00:00
empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, &color, &color, 0, 0);
XFreePixmap(display, pixmap);
}
}
return empty_cursor;
}
static void destroy_empty_cursor()
{
if (empty_cursor != None)
{
XFreeCursor((Display*)x11_disp, empty_cursor);
empty_cursor = None;
}
}
static void x11_capture_mouse()
{
2019-06-20 21:46:20 +00:00
x11_window_set_text("Flycast - mouse capture");
2018-09-18 07:27:16 +00:00
capturing_mouse = true;
Cursor cursor = create_empty_cursor();
Display *display = (Display*)x11_disp;
Window window = (Window)x11_win;
XDefineCursor(display, window, cursor);
XGrabPointer(display, window, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask,
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
}
static void x11_uncapture_mouse()
{
2019-06-20 21:46:20 +00:00
x11_window_set_text("Flycast");
2018-09-18 07:27:16 +00:00
capturing_mouse = false;
Display *display = (Display*)x11_disp;
Window window = (Window)x11_win;
XUndefineCursor(display, window);
XUngrabPointer(display, CurrentTime);
}
void input_x11_handle()
{
//Handle X11
2018-09-26 00:00:50 +00:00
static int prev_x = -1;
static int prev_y = -1;
bool mouse_moved = false;
2018-09-18 07:27:16 +00:00
XEvent e;
2018-09-26 00:00:50 +00:00
Display *display = (Display*)x11_disp;
while (XCheckWindowEvent(display, (Window)x11_win,
2018-09-18 07:27:16 +00:00
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask
| PointerMotionMask | FocusChangeMask,
&e))
{
switch(e.type)
{
case KeyPress:
{
char buf[2];
KeySym keysym_return;
int len = XLookupString(&e.xkey, buf, 1, &keysym_return, NULL);
if (len > 0)
x11_keyboard->keyboard_character(buf[0]);
}
/* no break */
2018-09-18 07:27:16 +00:00
case KeyRelease:
{
2018-09-26 08:08:49 +00:00
if (e.type == KeyRelease && XEventsQueued(display, QueuedAfterReading))
{
XEvent nev;
XPeekEvent(display, &nev);
if (nev.type == KeyPress && nev.xkey.time == e.xkey.time &&
nev.xkey.keycode == e.xkey.keycode)
// Key wasnt actually released: auto repeat
continue;
}
// Dreamcast keyboard emulation
x11_keyboard->keyboard_input(e.xkey.keycode, e.type == KeyPress);
// keyboard-based emulated gamepad
kb_gamepad->gamepad_btn_input(e.xkey.keycode, e.type == KeyPress);
// Start/stop mouse capture with Left Ctrl + Left Alt
if (e.type == KeyPress
&& ((e.xkey.keycode == KEY_LALT && (e.xkey.state & ControlMask))
|| (e.xkey.keycode == KEY_LCTRL && (e.xkey.state & Mod1Mask))))
{
capturing_mouse = !capturing_mouse;
if (capturing_mouse)
x11_capture_mouse();
else
x11_uncapture_mouse();
}
// TODO Move this to bindable keys or in the gui menu
2018-09-18 07:27:16 +00:00
if (x11_keyboard_input)
2018-07-16 15:31:52 +00:00
{
#if 1
2019-02-06 18:57:13 +00:00
if (e.xkey.keycode == KEY_F10)
2018-09-18 07:27:16 +00:00
{
// Dump the next frame into a file
dump_frame_switch = e.type == KeyPress;
}
2019-02-06 18:57:13 +00:00
else
2018-07-16 15:31:52 +00:00
#elif FEAT_HAS_NIXPROF
2019-02-06 18:57:13 +00:00
if (e.type == KeyRelease && e.xkey.keycode == KEY_F10)
2018-09-18 07:27:16 +00:00
{
if (sample_Switch(3000)) {
INFO_LOG(COMMON, "Starting profiling");
2018-09-18 07:27:16 +00:00
} else {
INFO_LOG(COMMON, "Stopping profiling");
2018-09-18 07:27:16 +00:00
}
2015-08-24 22:54:23 +00:00
}
2019-02-06 18:57:13 +00:00
else
2015-08-24 22:54:23 +00:00
#endif
2019-02-06 18:57:13 +00:00
if (e.type == KeyRelease && e.xkey.keycode == KEY_F11)
{
2018-09-18 07:27:16 +00:00
x11_fullscreen = !x11_fullscreen;
x11_window_set_fullscreen(x11_fullscreen);
}
2018-09-18 07:27:16 +00:00
}
}
break;
case FocusOut:
{
if (capturing_mouse)
x11_uncapture_mouse();
capturing_mouse = false;
}
break;
case ButtonPress:
case ButtonRelease:
mouse_gamepad->gamepad_btn_input(e.xbutton.button, e.type == ButtonPress);
2018-09-18 07:27:16 +00:00
{
u32 button_mask = 0;
switch (e.xbutton.button)
{
case Button1: // Left button
2018-09-18 07:27:16 +00:00
button_mask = 1 << 2;
break;
case Button2: // Middle button
2018-10-01 18:34:35 +00:00
button_mask = 1 << 3;
break;
case Button3: // Right button
2018-09-18 07:27:16 +00:00
button_mask = 1 << 1;
break;
case Button4: // Mouse wheel up
mo_wheel_delta -= 16;
break;
case Button5: // Mouse wheel down
mo_wheel_delta += 16;
break;
default:
break;
}
2018-09-18 07:27:16 +00:00
if (button_mask)
{
if (e.type == ButtonPress)
mo_buttons &= ~button_mask;
else
mo_buttons |= button_mask;
}
}
/* no break */
2018-09-18 07:27:16 +00:00
case MotionNotify:
2018-10-01 18:34:35 +00:00
// For Light gun
mo_x_abs = (e.xmotion.x - (x11_width - x11_height * 640 / 480) / 2) * 480 / x11_height;
mo_y_abs = e.xmotion.y * 480 / x11_height;
// For mouse
mouse_moved = true;
if (prev_x != -1)
mo_x_delta += (f32)(e.xmotion.x - prev_x) * settings.input.MouseSensitivity / 100.f;
if (prev_y != -1)
mo_y_delta += (f32)(e.xmotion.y - prev_y) * settings.input.MouseSensitivity / 100.f;
prev_x = e.xmotion.x;
prev_y = e.xmotion.y;
2018-09-18 07:27:16 +00:00
break;
2015-08-17 21:46:28 +00:00
}
}
2018-09-26 00:00:50 +00:00
if (capturing_mouse && mouse_moved)
{
prev_x = x11_width / 2;
prev_y = x11_height / 2;
XWarpPointer(display, None, (Window)x11_win, 0, 0, 0, 0,
prev_x, prev_y);
XSync(display, true);
}
}
void input_x11_init()
{
x11_keyboard = std::make_shared<X11KeyboardDevice>(0);
kb_gamepad = std::make_shared<X11KbGamepadDevice>(0);
GamepadDevice::Register(kb_gamepad);
mouse_gamepad = std::make_shared<X11MouseGamepadDevice>(0);
GamepadDevice::Register(mouse_gamepad);
2018-08-12 13:17:56 +00:00
x11_keyboard_input = (cfgLoadInt("input", "enable_x11_keyboard", 1) >= 1);
if (!x11_keyboard_input)
INFO_LOG(INPUT, "X11 Keyboard input disabled by config.");
}
static int x11_error_handler(Display *, XErrorEvent *)
{
return 0;
}
void x11_window_create()
{
2015-08-17 21:46:28 +00:00
if (cfgLoadInt("pvr", "nox11", 0) == 0)
{
XInitThreads();
// X11 variables
Window x11Window = 0;
Display* x11Display = 0;
long x11Screen = 0;
XVisualInfo* x11Visual = 0;
Colormap x11Colormap = 0;
/*
Step 0 - Create a NativeWindowType that we can use it for OpenGL ES output
*/
Window sRootWindow;
XSetWindowAttributes sWA;
unsigned int ui32Mask;
int i32Depth;
// Initializes the display and screen
x11Display = XOpenDisplay(NULL);
2015-08-17 21:46:28 +00:00
if (!x11Display && !(x11Display = XOpenDisplay(":0")))
{
ERROR_LOG(RENDERER, "Error: Unable to open X display");
2015-08-17 21:46:28 +00:00
return;
}
x11Screen = XDefaultScreen(x11Display);
2019-02-06 18:57:13 +00:00
float xdpi = (float)DisplayWidth(x11Display, x11Screen) / DisplayWidthMM(x11Display, x11Screen) * 25.4;
float ydpi = (float)DisplayHeight(x11Display, x11Screen) / DisplayHeightMM(x11Display, x11Screen) * 25.4;
screen_dpi = max(xdpi, ydpi);
2015-08-17 21:46:28 +00:00
// Gets the window parameters
sRootWindow = RootWindow(x11Display, x11Screen);
int depth = CopyFromParent;
2019-10-05 09:50:14 +00:00
#if !defined(GLES)
// Get a matching FB config
static int visual_attribs[] =
{
GLX_X_RENDERABLE , True,
GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT,
GLX_RENDER_TYPE , GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR,
GLX_RED_SIZE , 8,
GLX_GREEN_SIZE , 8,
GLX_BLUE_SIZE , 8,
GLX_ALPHA_SIZE , 8,
GLX_DEPTH_SIZE , 24,
GLX_STENCIL_SIZE , 8,
GLX_DOUBLEBUFFER , True,
//GLX_SAMPLE_BUFFERS , 1,
//GLX_SAMPLES , 4,
None
};
int glx_major, glx_minor;
// FBConfigs were added in GLX version 1.3.
if (!glXQueryVersion(x11Display, &glx_major, &glx_minor) ||
((glx_major == 1) && (glx_minor < 3)) || (glx_major < 1))
{
ERROR_LOG(RENDERER, "Invalid GLX version");
exit(1);
}
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
int fbcount;
GLXFBConfig* fbc = glXChooseFBConfig(x11Display, x11Screen, visual_attribs, &fbcount);
if (!fbc)
{
ERROR_LOG(RENDERER, "Failed to retrieve a framebuffer config");
exit(1);
}
INFO_LOG(RENDERER, "Found %d matching FB configs.", fbcount);
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
GLXFBConfig bestFbc = fbc[0];
XFree(fbc);
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
// Get a visual
XVisualInfo *vi = glXGetVisualFromFBConfig(x11Display, bestFbc);
INFO_LOG(RENDERER, "Chosen visual ID = 0x%lx", vi->visualid);
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
depth = vi->depth;
x11Visual = vi;
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
x11Colormap = XCreateColormap(x11Display, RootWindow(x11Display, x11Screen), vi->visual, AllocNone);
#else
i32Depth = DefaultDepth(x11Display, x11Screen);
x11Visual = new XVisualInfo;
XMatchVisualInfo(x11Display, x11Screen, i32Depth, TrueColor, x11Visual);
if (!x11Visual)
{
ERROR_LOG(RENDERER, "Error: Unable to acquire visual");
return;
}
x11Colormap = XCreateColormap(x11Display, sRootWindow, x11Visual->visual, AllocNone);
#endif
2015-08-17 21:46:28 +00:00
sWA.colormap = x11Colormap;
// Add to these for handling other events
sWA.event_mask = StructureNotifyMask | ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask;
2019-02-06 18:57:13 +00:00
sWA.event_mask |= PointerMotionMask | FocusChangeMask;
2015-08-17 21:46:28 +00:00
ui32Mask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap;
x11_width = cfgLoadInt("x11", "width", DEFAULT_WINDOW_WIDTH);
x11_height = cfgLoadInt("x11", "height", DEFAULT_WINDOW_HEIGHT);
x11_fullscreen = cfgLoadBool("x11", "fullscreen", DEFAULT_FULLSCREEN);
2015-08-17 21:46:28 +00:00
if (x11_width < 0 || x11_height < 0)
2015-08-17 21:46:28 +00:00
{
x11_width = XDisplayWidth(x11Display, x11Screen);
x11_height = XDisplayHeight(x11Display, x11Screen);
2015-08-17 21:46:28 +00:00
}
// Creates the X11 window
x11Window = XCreateWindow(x11Display, RootWindow(x11Display, x11Screen), (ndcid%3)*640, (ndcid/3)*480, x11_width, x11_height,
2015-08-17 21:46:28 +00:00
0, depth, InputOutput, x11Visual->visual, ui32Mask, &sWA);
XSetWindowBackground(x11Display, x11Window, 0);
Atom net_wm_icon = XInternAtom(x11Display, "_NET_WM_ICON", False);
Atom cardinal = XInternAtom(x11Display, "CARDINAL", False);
XChangeProperty(x11Display, x11Window, net_wm_icon, cardinal, 32, PropModeReplace,
(const unsigned char*)reicast_icon, sizeof(reicast_icon) / sizeof(*reicast_icon));
// Capture the close window event
wmDeleteMessage = XInternAtom(x11Display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(x11Display, x11Window, &wmDeleteMessage, 1);
if(x11_fullscreen)
{
2015-08-17 21:46:28 +00:00
// fullscreen
Atom wmState = XInternAtom(x11Display, "_NET_WM_STATE", False);
Atom wmFullscreen = XInternAtom(x11Display, "_NET_WM_STATE_FULLSCREEN", False);
XChangeProperty(x11Display, x11Window, wmState, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wmFullscreen, 1);
XMapRaised(x11Display, x11Window);
}
else
{
2015-08-17 21:46:28 +00:00
XMapWindow(x11Display, x11Window);
}
2015-08-17 21:46:28 +00:00
2019-10-05 09:50:14 +00:00
if (settings.pvr.IsOpenGL())
{
#if !defined(GLES)
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
verify(glXCreateContextAttribsARB != 0);
int context_attribs[] =
{
2018-10-04 08:29:23 +00:00
GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
GLX_CONTEXT_MINOR_VERSION_ARB, 3,
#ifndef RELEASE
GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB,
2018-10-04 08:29:23 +00:00
#endif
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
None
};
int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&x11_error_handler);
x11_glc = glXCreateContextAttribsARB(x11Display, bestFbc, 0, True, context_attribs);
if (!x11_glc)
{
INFO_LOG(RENDERER, "Open GL 4.3 not supported");
// Try GL 3.0
2018-10-04 08:29:23 +00:00
context_attribs[1] = 3;
context_attribs[3] = 0;
2018-10-04 08:29:23 +00:00
x11_glc = glXCreateContextAttribsARB(x11Display, bestFbc, 0, True, context_attribs);
if (!x11_glc)
{
die("Open GL 3.0 not supported\n");
2018-10-04 08:29:23 +00:00
}
}
XSetErrorHandler(old_handler);
2018-10-04 08:29:23 +00:00
XSync(x11Display, False);
2019-10-05 09:50:14 +00:00
#endif
}
#ifdef USE_VULKAN
else
{
vulkanContext = new VulkanContext();
const char * extensions[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME };
vulkanContext->InitInstance(extensions, ARRAY_SIZE(extensions));
vulkanContext->SetWindowSize(x11_width, x11_height);
VkXlibSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0, x11Display, x11Window };
VkSurfaceKHR surface;
vkCreateXlibSurfaceKHR(vulkanContext->GetInstance(), &createInfo, nullptr, &surface);
vulkanContext->SetSurface(surface);
vulkanContext->InitDevice();
}
#endif
2015-08-17 21:46:28 +00:00
XFlush(x11Display);
//(EGLNativeDisplayType)x11Display;
x11_disp = (void*)x11Display;
x11_win = (void*)x11Window;
x11_vis = (void*)x11Visual->visual;
2019-06-20 21:46:20 +00:00
x11_window_set_text("Flycast");
2015-08-17 21:46:28 +00:00
}
else
{
INFO_LOG(RENDERER, "Not creating X11 window ..");
2015-08-17 21:46:28 +00:00
}
}
void x11_window_set_text(const char* text)
{
2015-08-17 21:46:28 +00:00
if (x11_win)
{
XStoreName((Display*)x11_disp, (Window)x11_win, text);
XSetIconName((Display*)x11_disp, (Window)x11_win, text);
XClassHint hint = { (char *)"WM_CLASS", (char *)text };
XSetClassHint((Display*)x11_disp, (Window)x11_win, &hint);
2015-08-17 21:46:28 +00:00
}
}
void x11_window_destroy()
{
destroy_empty_cursor();
2019-10-05 09:50:14 +00:00
#ifdef USE_VULKAN
if (vulkanContext != nullptr)
delete vulkanContext;
#endif
2015-08-17 21:46:28 +00:00
// close XWindow
if (x11_win)
{
if (!x11_fullscreen)
{
cfgSaveInt("x11", "width", x11_width);
cfgSaveInt("x11", "height", x11_height);
}
cfgSaveBool("x11", "fullscreen", x11_fullscreen);
XDestroyWindow((Display*)x11_disp, (Window)x11_win);
x11_win = NULL;
2015-08-17 21:46:28 +00:00
}
if (x11_disp)
{
2018-08-20 11:17:01 +00:00
#if !defined(GLES)
if (x11_glc)
{
glXMakeCurrent((Display*)x11_disp, None, NULL);
glXDestroyContext((Display*)x11_disp, (GLXContext)x11_glc);
x11_glc = NULL;
}
2018-08-20 11:17:01 +00:00
#endif
XCloseDisplay((Display*)x11_disp);
x11_disp = NULL;
2015-08-17 21:46:28 +00:00
}
}
2015-08-17 21:46:28 +00:00
#endif