#include #include #include #include /* for XGetXCBConnection, link with libX11-xcb */ #include #include #include #include #include #include #include xcb_window_t getWindow(xcb_connection_t*, xcb_colormap_t, int, xcb_screen_t*, uint16_t, uint16_t); xcb_colormap_t getColorMap(xcb_connection_t*, xcb_screen_t*, int); GLXContext getGLXContext(Display*, GLXFBConfig); /* Attribs filter the list of FBConfigs returned by glXChooseFBConfig(). Visual attribs further described in glXGetFBConfigAttrib(3) */ 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 }; /* Macro definition to parse X server events * The ~0x80 is needed to get the lower 7 bits */ #define RECEIVE_EVENT(ev) (ev->response_type & ~0x80) void draw(uint16_t height, uint16_t width) { // Draw a Red 1x1 Square centered at origin glBegin(GL_QUADS); // Each set of 4 vertices form a quad glColor3f(1.0f, 0.0f, 0.0f); // Red glVertex2f(-0.5f, -0.5f); // x, y glVertex2f( 0.5f, -0.5f); glVertex2f( 0.5f, 0.5f); glVertex2f(-0.5f, 0.5f); glEnd(); glFlush(); // Render now } static struct timespec genSleep(time_t sec, long nanosec) { struct timespec t; t.tv_sec = sec; t.tv_nsec = nanosec; return t; } int message_loop(Display *display, xcb_connection_t *xcb_display, xcb_window_t window, xcb_screen_t *screen, GLXDrawable drawable) { int running = 1; glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); GLint x_offset = 0; /* Used to handle the event loop */ struct timespec req = genSleep(0, 20000000); struct timespec rem = genSleep(0, 0); xcb_expose_event_t *expose; uint16_t window_height = screen->height_in_pixels; uint16_t window_width = screen->width_in_pixels; int exposed = 0; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set background color to black and opaque glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer (background) while (running) { /* Poll for events */ xcb_generic_event_t *event = xcb_poll_for_event(xcb_display); if (event != NULL) { switch (RECEIVE_EVENT(event)) { case XCB_KEY_PRESS: /* Quit on key press */ // running = 0; break; case XCB_EXPOSE: expose = (xcb_expose_event_t *)event; window_height = expose->height; window_width = expose->width; printf("Got expose event\n"); exposed = 1; break; default: break; } free(event); } if (exposed) { draw(window_width, window_height); /* This is where the magic happens */ /* This call will NOT block.*/ /* It will be sync'd with vertical refresh */ glXSwapBuffers(display, drawable); nanosleep(&req, &rem); } } return 0; } int setup_message_loop(Display* display, xcb_connection_t *xcb_display, int default_screen, xcb_screen_t *screen) { int visualID = 0; uint16_t width = screen->width_in_pixels; uint16_t height = screen->height_in_pixels; /* Query framebuffer configurations that match visual_attribs */ GLXFBConfig *fb_configs = 0; int num_fb_configs = 0; fb_configs = glXChooseFBConfig(display, default_screen, visual_attribs, &num_fb_configs); if (!fb_configs || num_fb_configs == 0) { fprintf(stderr, "glXGetFBConfigs failed\n"); return -1; } printf("Found %d matching FB configs\n", num_fb_configs); /* Select first framebuffer config and query visualID */ GLXFBConfig fb_config = fb_configs[0]; /* This will write the visualID */ glXGetFBConfigAttrib(display, fb_config, GLX_VISUAL_ID , &visualID); /* Create XIDs for colormap and window */ /* This is how we can use the Xlib GLX functions */ /* Since these XIDs are transferrable between xcb and xlib */ xcb_colormap_t colormap = getColorMap(xcb_display, screen, visualID); xcb_window_t window = getWindow(xcb_display, colormap, visualID, screen, width, height); /* NOTE: window must be mapped before glXMakeContextCurrent */ xcb_map_window(xcb_display, window); /* Create GLX Window */ GLXContext context = getGLXContext(display, fb_config); /* This is going to be our backbuffer */ GLXDrawable drawable = 0; GLXWindow glxwindow = glXCreateWindow( display, fb_config, window, 0); if (!window) { xcb_destroy_window(xcb_display, window); glXDestroyContext(display, context); fprintf(stderr, "glXDestroyContext failed\n"); return -1; } drawable = glxwindow; /* make OpenGL context current */ /* This will allow us to write to it with the GLX functions */ /* https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXMakeContextCurrent.xml */ if (!glXMakeContextCurrent(display, drawable, drawable, context)) { xcb_destroy_window(xcb_display, window); glXDestroyContext(display, context); fprintf(stderr, "glXMakeContextCurrent failed\n"); return -1; } /* run message loop */ int retval = message_loop(display, xcb_display, window, screen, drawable); /* Cleanup */ glXDestroyWindow(display, glxwindow); xcb_destroy_window(xcb_display, window); glXDestroyContext(display, context); return retval; } GLXContext getGLXContext(Display *display, GLXFBConfig fb_config) { /* Create GLX Window */ GLXContext context; /* Create OpenGL context */ /* Display* dpy * GLXFBConfig config * int render_type * GLXContext share_list * Bool direct (indicates we want direct rendering) */ context = glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); if (!context) { fprintf(stderr, "glXCreateNewContext failed\n"); exit(1); } return context; } xcb_colormap_t getColorMap(xcb_connection_t *xcb_display, xcb_screen_t *screen, int visualID) { /* Create XID's for colormap and window */ xcb_colormap_t colormap = xcb_generate_id(xcb_display); /* Create colormap */ xcb_create_colormap( xcb_display, XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, visualID ); return colormap; } xcb_window_t getWindow(xcb_connection_t *xcb_display, xcb_colormap_t colormap, int visualID, xcb_screen_t *screen, uint16_t width, uint16_t height) { xcb_window_t window = xcb_generate_id(xcb_display); /* Create window */ uint32_t eventmask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS; uint32_t valuelist[] = { eventmask, colormap, 0 }; uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; xcb_create_window( xcb_display, XCB_COPY_FROM_PARENT, window, screen->root, 0, 0, /* x y */ width, height, /* width height */ 0, /* border width */ XCB_WINDOW_CLASS_INPUT_OUTPUT, visualID, valuemask, valuelist ); return window; } Display* getDisplay() { Display *display; if (display == NULL) { fprintf(stderr, "Could not open the display! :(\n"); exit(1); } /* Open Xlib Display */ return XOpenDisplay(0); } xcb_connection_t* getXCBDisplay(Display *x_display) { /* Get the XCB connection from the display */ /* See https://xcb.freedesktop.org/MixingCalls/ */ xcb_connection_t *xcb_display = XGetXCBConnection(x_display); if (!xcb_display) { XCloseDisplay(x_display); fprintf(stderr, "Can't get xcb connection from display\n"); exit(1); } return xcb_display; } xcb_screen_t* getScreen(xcb_connection_t *xcb_display, int default_screen) { /* Find XCB screen */ xcb_screen_t *screen = 0; /* Create an iterator of all available screens */ xcb_screen_iterator_t screen_iter; screen_iter = xcb_setup_roots_iterator(xcb_get_setup(xcb_display)); int screen_num = default_screen; printf("%d\n", screen_num); /* Handle multiple screens */ /* We want screen number 0 */ while (screen_iter.rem && screen_num > 0) { printf("%d\n", screen_num); screen_num--; xcb_screen_next(&screen_iter); } return screen_iter.data; } int main(void) { Display *display = getDisplay(); /* Get the XCB connection from the display */ xcb_connection_t *xcb_display = getXCBDisplay(display); /* Acquire event queue ownership */ /* See https://xcb.freedesktop.org/MixingCalls/ for why this is needed */ XSetEventQueueOwner(display, XCBOwnsEventQueue); int default_screen = DefaultScreen(display); xcb_screen_t *screen = getScreen(xcb_display, default_screen); /* Initialize window and OpenGL context, run main loop and deinitialize */ int retval = setup_message_loop(display, xcb_display, default_screen, screen); /* Cleanup */ XCloseDisplay(display); return retval; }