/* SPDX-License-Identifier: ISC */ /* * window.c * * Copyright (C) 2022 David Oberhollenzer */ #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; }