shadermeh/window.c

311 lines
7.3 KiB
C

/* SPDX-License-Identifier: ISC */
/*
* window.c
*
* Copyright (C) 2022 David Oberhollenzer <goliath@infraroot.at>
*/
#include "shadermeh.h"
#ifndef GLX_ARB_create_context
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
#define GLX_CONTEXT_FLAGS_ARB 0x2094
#endif
#ifndef GLX_ARB_create_context_profile
#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#endif
typedef GLXContext (*CREATECONTEXTATTRIBSPROC)(Display *, GLXFBConfig,
GLXContext, Bool,
const int *);
typedef void (*SWAPINTERVALEXTPROC)(Display *, GLXDrawable, int);
static int glversions[][2] = { {4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0},
{3,3}, {3,2}, {3,1}, {3,0} };
static struct {
Display *dpy;
Atom atom_wm_delete;
XContext xctx;
size_t vis_wnd_count;
size_t refcount;
} x11_globals;
static int xlib_swallow_errors(Display *display, XErrorEvent *event)
{
(void)display; (void)event;
return 0;
}
static int xlib_grab_ref(void)
{
if (x11_globals.refcount == 0) {
x11_globals.dpy = XOpenDisplay(0);
if (!x11_globals.dpy)
return -1;
XSetErrorHandler(xlib_swallow_errors);
x11_globals.atom_wm_delete = XInternAtom(x11_globals.dpy,
"WM_DELETE_WINDOW",
True);
x11_globals.xctx = XUniqueContext();
}
x11_globals.refcount += 1;
return 0;
}
static void xlib_drop_ref(void)
{
x11_globals.refcount -= 1;
if (x11_globals.refcount == 0) {
XCloseDisplay(x11_globals.dpy);
memset(&x11_globals, 0, sizeof(x11_globals));
}
}
/****************************************************************************/
window *window_create(unsigned int width, unsigned int height,
const char *caption)
{
CREATECONTEXTATTRIBSPROC CreateContextAttribs;
XSetWindowAttributes swa;
int fbcount, attr[20];
GLXFBConfig fbc, *fbl;
unsigned int i = 0;
XSizeHints hints;
XVisualInfo *vi;
window *this;
if (xlib_grab_ref())
return NULL;
this = calloc(1, sizeof(*this));
if (!this) {
xlib_drop_ref();
return NULL;
}
/* get a valid GLX framebuffer configuration; create an X11 visual */
attr[ 0] = GLX_BUFFER_SIZE; attr[ 1] = 32;
attr[ 2] = GLX_RED_SIZE; attr[ 3] = 8;
attr[ 4] = GLX_GREEN_SIZE; attr[ 5] = 8;
attr[ 6] = GLX_BLUE_SIZE; attr[ 7] = 8;
attr[ 8] = GLX_ALPHA_SIZE; attr[ 9] = 8;
attr[10] = GLX_DOUBLEBUFFER; attr[11] = True;
attr[12] = None;
fbl = glXChooseFBConfig(x11_globals.dpy, DefaultScreen(x11_globals.dpy),
attr, &fbcount);
if (!fbl || !fbcount) {
window_destroy(this);
return NULL;
}
fbc = fbl[0];
vi = glXGetVisualFromFBConfig(x11_globals.dpy, fbl[0]);
XFree(fbl);
if (!vi) {
window_destroy(this);
return NULL;
}
/* Get a colormap ID from the visual. Needed for legacy reasons. */
swa.colormap = XCreateColormap(x11_globals.dpy,
RootWindow(x11_globals.dpy, vi->screen),
vi->visual, AllocNone);
if (!swa.colormap) {
XFree(vi);
window_destroy(this);
return NULL;
}
swa.border_pixel = 0;
/* create a window, and get rid of the X11 visual */
this->wnd = XCreateWindow(x11_globals.dpy,
RootWindow(x11_globals.dpy, vi->screen), 0, 0,
width, height, 0, vi->depth, InputOutput,
vi->visual, CWBorderPixel | CWColormap, &swa);
XFree(vi);
if (!this->wnd) {
window_destroy(this);
return NULL;
}
/* try to create a GLX context, the new modern way */
CreateContextAttribs = (CREATECONTEXTATTRIBSPROC)
glXGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB");
if (CreateContextAttribs) {
attr[0] = GLX_CONTEXT_MAJOR_VERSION_ARB;
attr[1] = 0;
attr[2] = GLX_CONTEXT_MINOR_VERSION_ARB;
attr[3] = 0;
attr[4] = GLX_CONTEXT_PROFILE_MASK_ARB;
attr[5] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
attr[6] = GLX_CONTEXT_FLAGS_ARB;
attr[7] = GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
attr[8] = None;
for (i = 0; !this->gl && i < sizeof(glversions) /
sizeof(glversions[0]); ++i) {
attr[1] = glversions[i][0];
attr[3] = glversions[i][1];
this->gl = CreateContextAttribs(x11_globals.dpy, fbc, 0,
True, attr);
}
}
/* fallback: create a GLX context, the legacy way */
if (!this->gl) {
this->gl = glXCreateNewContext(x11_globals.dpy, fbc,
GLX_RGBA_TYPE, 0, GL_TRUE);
}
if (!this->gl) {
window_destroy(this);
return NULL;
}
/* make the window non-resizable */
hints.flags = PSize | PMinSize | PMaxSize;
hints.min_width = hints.max_width = hints.base_width = width;
hints.min_height = hints.max_height = hints.base_height = height;
XSetWMNormalHints(x11_globals.dpy, this->wnd, &hints);
/* X11 should tell us about pretty much everything */
XSelectInput(x11_globals.dpy, this->wnd, StructureNotifyMask |
KeyPressMask | KeyReleaseMask |
PointerMotionMask | PropertyChangeMask |
ButtonPressMask | ButtonReleaseMask);
XSetWMProtocols(x11_globals.dpy, this->wnd,
&x11_globals.atom_wm_delete, 1);
/* store the object pointer with the window,
so we can get it back later */
XSaveContext(x11_globals.dpy, this->wnd,
x11_globals.xctx, (XPointer)this);
/* set the caption */
XStoreName(x11_globals.dpy, this->wnd, caption);
this->visible = false;
/* flush any pending requests to the X Server */
XFlush(x11_globals.dpy);
return this;
}
void window_make_current(window *this)
{
if (this) {
glXMakeContextCurrent(x11_globals.dpy, this->wnd,
this->wnd, this->gl);
} else {
glXMakeContextCurrent(x11_globals.dpy, 0, 0, 0);
}
}
void window_swap_buffers(window *this)
{
glXSwapBuffers(x11_globals.dpy, this->wnd);
}
void window_set_vsync(window *this, int enable)
{
SWAPINTERVALEXTPROC SwapIntervalEXT;
SwapIntervalEXT = (SWAPINTERVALEXTPROC)
glXGetProcAddress((const GLubyte *)"glXSwapIntervalEXT");
if (SwapIntervalEXT)
SwapIntervalEXT(x11_globals.dpy, this->wnd, enable ? 1 : 0);
}
void window_destroy(window *this)
{
if (this->gl) {
glXMakeContextCurrent(x11_globals.dpy, 0, 0, 0);
glXDestroyContext(x11_globals.dpy, this->gl);
}
if (this->wnd) {
XDeleteContext(x11_globals.dpy, this->wnd, x11_globals.xctx);
XDestroyWindow(x11_globals.dpy, this->wnd);
}
if (this->visible)
x11_globals.vis_wnd_count -= 1;
xlib_drop_ref();
free(this);
}
void window_show(window *this)
{
if (this->visible)
return;
XMapWindow(x11_globals.dpy, this->wnd);
XFlush(x11_globals.dpy);
this->visible = true;
x11_globals.vis_wnd_count += 1;
}
void window_hide(window *this)
{
if (!this->visible)
return;
XUnmapWindow(x11_globals.dpy, this->wnd);
XFlush(x11_globals.dpy);
this->visible = false;
x11_globals.vis_wnd_count -= 1;
}
bool window_handle_events(void)
{
window *wnd;
XEvent e;
/* if there are no events, return to the drawing loop ASAP */
if (!XPending(x11_globals.dpy))
goto out;
/* get a window event, and get the coresponding object pointer */
XNextEvent(x11_globals.dpy, &e);
XFindContext(x11_globals.dpy, e.xany.window,
x11_globals.xctx, (XPointer *)&wnd);
if (!wnd)
goto out;
/* process event */
switch (e.type) {
case ClientMessage:
if (e.xclient.data.l[0] == (long)x11_globals.atom_wm_delete) {
XUnmapWindow(x11_globals.dpy, e.xany.window);
wnd->visible = false;
x11_globals.vis_wnd_count -= 1;
}
break;
}
out:
return x11_globals.vis_wnd_count > 0;
}