// Copyright (C) 2022 RozK // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // Adapted from https://www.khronos.org/opengl/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX) #include "../display.hpp" #include "display_glx.hpp" #include "../events/events_x11.hpp" #include #include static bool rk_error_occured = false; #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 typedef GLXContext (*rk_CreateContextAttribsFunc)(Display *, GLXFBConfig, GLXContext, Bool, int const *); static int const rk_context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 2, GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_ES_PROFILE_BIT_EXT, None }; static void rk_x11_printf( char const * messsage) { printf("[X11] %s\n", messsage); } static void rk_glx_printf( char const * messsage) { printf("[GLX] %s\n", messsage); } bool rk_extension_supported( char const * extensions, char const * extension) { char const * where = strchr(extension, ' '); if (where || *extension == '\0') { return false; } for (char const * start = extensions;;) { where = strstr(start, extension); if (!where) { break; } char const * const terminator = where + strlen(extension); if ((where == start || *(where - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) { return true; } start = terminator; } return false; } rk_func_ptr rk_resolve_extension( char const * extensions, char const * extension, char const * fn) { if (!extensions || !extension || !fn) { return nullptr; } if (!rk_extension_supported(extensions, extension)) { printf("[GLX] Extension %s not supported\n", extension); return nullptr; } rk_func_ptr const ptr = glXGetProcAddressARB(reinterpret_cast(fn)); if (!ptr) { printf("[GLX] Function %s::%s not found\n", extension, fn); } return ptr; } static int rk_error_handler( Display * display, XErrorEvent * event) { rk_error_occured = true; return 0; } rk_display_t rk_create_display( rk_char const * name, rk_uint width, rk_uint height) { if (!name || !width || !height) { return nullptr; }; rk_display_glx * const display = new rk_display_glx; display->display = XOpenDisplay(nullptr); display->window = 0; display->colormap = 0; display->context = nullptr; if (!display->display) { rk_x11_printf("Failed to open X display."); rk_destroy_display(reinterpret_cast(display)); return nullptr; } int glx_major, glx_minor; if (!glXQueryVersion(display->display, &glx_major, &glx_minor) || (glx_major == 1 && glx_minor < 3) || glx_major < 1) { rk_glx_printf("Invalid GLX version."); rk_destroy_display(reinterpret_cast(display)); return nullptr; } char const * const glx_exts = glXQueryExtensionsString(display->display, DefaultScreen(display->display)); // rk_glx_printf(glx_exts); rk_CreateContextAttribsFunc const glXCreateContextAttribs = reinterpret_cast( rk_resolve_extension(glx_exts, "GLX_ARB_create_context", "glXCreateContextAttribsARB")); if (!glXCreateContextAttribs) { rk_destroy_display(reinterpret_cast(display)); return nullptr; } int fb_count = 0; GLXFBConfig * fb_configs; bool const srgb_ext = rk_extension_supported(glx_exts, "GLX_EXT_framebuffer_sRGB"); if (srgb_ext) { rk_glx_printf("GLX_EXT_framebuffer_sRGB extension supported."); int const visual_attribs[] = { GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, True, GLX_DOUBLEBUFFER, True, None }; fb_configs = glXChooseFBConfig(display->display, DefaultScreen(display->display), visual_attribs, &fb_count); } else { int const visual_attribs[] = { GLX_DOUBLEBUFFER, True, None }; fb_configs = glXChooseFBConfig(display->display, DefaultScreen(display->display), visual_attribs, &fb_count); } printf("[GLX] Found %d framebuffer configs.\n", fb_count); if (!fb_configs || !fb_count) { rk_glx_printf("Failed to retrieve framebuffer configs."); rk_destroy_display(reinterpret_cast(display)); return nullptr; } GLXFBConfig fb_config; int rs = 0; int gs = 0; int bs = 0; int as = 0; int ds = 0; int ss = 0; for (int fb_index = 0; fb_index < fb_count; ++fb_index) { GLXFBConfig _config = fb_configs[fb_index]; int _rs = 0; int _gs = 0; int _bs = 0; int _as = 0; int _ds = 0; int _ss = 0; glXGetFBConfigAttrib(display->display, _config, GLX_RED_SIZE, &_rs); glXGetFBConfigAttrib(display->display, _config, GLX_GREEN_SIZE, &_gs); glXGetFBConfigAttrib(display->display, _config, GLX_BLUE_SIZE, &_bs); glXGetFBConfigAttrib(display->display, _config, GLX_ALPHA_SIZE, &_as); glXGetFBConfigAttrib(display->display, _config, GLX_DEPTH_SIZE, &_ds); glXGetFBConfigAttrib(display->display, _config, GLX_STENCIL_SIZE, &_ss); if ((_rs >= rs && _gs >= gs && _bs >= bs && _as >= as && _ds >= ds && _ss >= ss) && (_rs > rs || _gs > gs || _bs > bs || _as > as || _ds > ds || _ss > ss)) { XVisualInfo * const vi = glXGetVisualFromFBConfig(display->display, _config); if (vi) { XFree(vi); rs = _rs; gs = _gs; bs = _bs; as = _as; ds = _ds; ss = _ss; fb_config = _config; } } } printf("[RK] Select framebuffer config R%dG%dB%dA%d D%dS%d.\n", rs, gs, bs, as, ds, ss); if (srgb_ext) { int srgb = 0; glXGetFBConfigAttrib(display->display, fb_config, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb); if (srgb) { rk_glx_printf("sRGB framebuffer selected."); } } XVisualInfo * const vi = glXGetVisualFromFBConfig(display->display, fb_config); Window root = RootWindow(display->display, vi->screen); display->colormap = XCreateColormap(display->display, root, vi->visual, AllocNone); XSetWindowAttributes win_attributes; win_attributes.colormap = display->colormap; win_attributes.event_mask = RK_EVENTS_MASK; display->window = XCreateWindow(display->display, root, 0, 0, width, height, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &win_attributes); XFree(vi); XFree(fb_configs); if (!display->window) { rk_x11_printf("Failed to create window."); rk_destroy_display(reinterpret_cast(display)); return nullptr; } XStoreName(display->display, display->window, name); XMapWindow(display->display, display->window); rk_error_occured = false; int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&rk_error_handler); display->context = glXCreateContextAttribs(display->display, fb_config, 0, True, rk_context_attribs); XSync(display->display, False); XSetErrorHandler(oldHandler); if (rk_error_occured || !display->context) { rk_glx_printf("Failed to create context."); rk_destroy_display(reinterpret_cast(display)); return nullptr; } if (!glXIsDirect(display->display, display->context)) { rk_glx_printf("Warning: Rendering context is indirect."); } glXMakeCurrent(display->display, display->window, display->context); if (srgb_ext) { glEnable(GL_FRAMEBUFFER_SRGB_EXT); if (glIsEnabled(GL_FRAMEBUFFER_SRGB_EXT)) { rk_glx_printf("sRGB framebuffer enabled."); } } return reinterpret_cast(display); } void rk_destroy_display( rk_display_t _display) { rk_display_glx * const display = reinterpret_cast(_display); if (display) { if (display->display) { glXMakeCurrent(display->display, 0, nullptr); if (display->context) { glXDestroyContext(display->display, display->context); } if (display->window) { XDestroyWindow(display->display, display->window); } if (display->colormap) { XFreeColormap(display->display, display->colormap); } XCloseDisplay(display->display); } delete display; } } void rk_swap_buffers( rk_display_t _display) { rk_display_glx const * const display = reinterpret_cast(_display); if (display && display->display && display->window) { glXSwapBuffers(display->display, display->window); } }