gl-x11-glx.c 13.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/******************************************************************************
    Copyright (C) 2014 by Zachary Lund <admin@computerquip.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
17

18 19 20 21 22 23 24 25 26 27 28 29 30 31
/* Version 2 of the GLX backend...
 * Difference from version 1 is that we use XCB to help alleviate
 * pains in a threaded environment that is prone to error.
 * These errors must be readable and handled for the sake of,
 * not only the users' sanity, but my own.
 *
 * With that said, we have more error checking capabilities...
 * and not all of them are used to help simplify current code.
 *
 * TODO: Implement more complete error checking.
 * NOTE: GLX loading functions are placed illogically
 * 	for the sake of convenience.
 */

32
#include <X11/Xlib.h>
33 34 35
#include <X11/Xlib-xcb.h>

#include <xcb/xcb.h>
36 37 38 39 40

#include <stdio.h>

#include "gl-subsystem.h"

B
BtbN 已提交
41
#include <glad/glad_glx.h>
42

Z
Zachary Lund 已提交
43
static const int ctx_attribs[] = {
44
#ifdef _DEBUG
J
jp9000 已提交
45 46
	GLX_CONTEXT_FLAGS_ARB,
	GLX_CONTEXT_DEBUG_BIT_ARB,
47
#endif
J
jp9000 已提交
48 49 50 51 52
	GLX_CONTEXT_PROFILE_MASK_ARB,
	GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
	GLX_CONTEXT_MAJOR_VERSION_ARB,
	3,
	GLX_CONTEXT_MINOR_VERSION_ARB,
53
	3,
54
	None,
55 56
};

J
jp9000 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
static int ctx_pbuffer_attribs[] = {GLX_PBUFFER_WIDTH, 2, GLX_PBUFFER_HEIGHT, 2,
				    None};

static int ctx_visual_attribs[] = {GLX_STENCIL_SIZE,
				   0,
				   GLX_DEPTH_SIZE,
				   0,
				   GLX_BUFFER_SIZE,
				   32,
				   GLX_ALPHA_SIZE,
				   8,
				   GLX_DOUBLEBUFFER,
				   true,
				   GLX_X_RENDERABLE,
				   true,
				   None};
73

74
struct gl_windowinfo {
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
	/* We store this value since we can fetch a lot
	 * of information not only concerning the config
	 * but the visual, and various other settings
	 * for the context.
	 */
	GLXFBConfig config;

	/* Windows in X11 are defined with integers (XID).
	 * xcb_window_t is a define for this... they are
	 * compatible with Xlib as well.
	 */
	xcb_window_t window;

	/* We can't fetch screen without a request so we cache it. */
	int screen;
90
};
91

92
struct gl_platform {
93
	Display *display;
94
	GLXContext context;
95
	GLXPbuffer pbuffer;
96 97
};

98 99 100 101 102 103 104 105 106 107 108
/*
 * Since we cannot take advantage of the asynchronous nature of xcb,
 * all of the helper functions are synchronous but thread-safe.
 *
 * They check for errors and will return 0 on problems
 * with the exception of when 0 is a valid return value... in which case
 * read the specific function comments.
 */

/* Returns -1 on invalid screen. */
static int get_screen_num_from_xcb_screen(xcb_connection_t *xcb_conn,
J
jp9000 已提交
109
					  xcb_screen_t *screen)
110 111 112 113
{
	xcb_screen_iterator_t iter =
		xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
	int screen_num = 0;
114

115 116 117 118 119
	for (; iter.rem; xcb_screen_next(&iter), ++screen_num)
		if (iter.data == screen)
			return screen_num;

	return -1;
120 121
}

122
static xcb_screen_t *get_screen_from_root(xcb_connection_t *xcb_conn,
J
jp9000 已提交
123
					  xcb_window_t root)
124
{
125 126
	xcb_screen_iterator_t iter =
		xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
127

128 129 130
	while (iter.rem) {
		if (iter.data->root == root)
			return iter.data;
131

132
		xcb_screen_next(&iter);
133 134
	}

135
	return 0;
136 137
}

138
static inline int get_screen_num_from_root(xcb_connection_t *xcb_conn,
J
jp9000 已提交
139
					   xcb_window_t root)
140
{
141
	xcb_screen_t *screen = get_screen_from_root(xcb_conn, root);
142

J
jp9000 已提交
143 144
	if (!screen)
		return -1;
145

146 147
	return get_screen_num_from_xcb_screen(xcb_conn, screen);
}
148

J
jp9000 已提交
149 150
static xcb_get_geometry_reply_t *get_window_geometry(xcb_connection_t *xcb_conn,
						     xcb_drawable_t drawable)
151 152 153 154 155 156 157 158 159 160 161 162 163
{
	xcb_get_geometry_cookie_t cookie;
	xcb_generic_error_t *error;
	xcb_get_geometry_reply_t *reply;

	cookie = xcb_get_geometry(xcb_conn, drawable);
	reply = xcb_get_geometry_reply(xcb_conn, cookie, &error);

	if (error) {
		blog(LOG_ERROR, "Failed to fetch parent window geometry!");
		free(error);
		free(reply);
		return 0;
164 165
	}

166 167 168
	free(error);
	return reply;
}
169

170 171 172
static bool gl_context_create(struct gl_platform *plat)
{
	Display *display = plat->display;
173 174
	int frame_buf_config_count = 0;
	GLXFBConfig *config = NULL;
175
	GLXContext context;
176
	bool success = false;
177

178 179
	if (!GLAD_GLX_ARB_create_context) {
		blog(LOG_ERROR, "ARB_GLX_create_context not supported!");
180
		return false;
181
	}
182

183
	config = glXChooseFBConfig(display, DefaultScreen(display),
J
jp9000 已提交
184
				   ctx_visual_attribs, &frame_buf_config_count);
185 186
	if (!config) {
		blog(LOG_ERROR, "Failed to create OpenGL frame buffer config");
187
		return false;
188
	}
189

J
jp9000 已提交
190 191
	context = glXCreateContextAttribsARB(display, config[0], NULL, true,
					     ctx_attribs);
192 193
	if (!context) {
		blog(LOG_ERROR, "Failed to create OpenGL context.");
194
		goto error;
195
	}
196

197 198
	plat->context = context;
	plat->display = display;
199

J
jp9000 已提交
200 201
	plat->pbuffer =
		glXCreatePbuffer(display, config[0], ctx_pbuffer_attribs);
202 203 204 205 206 207 208 209 210 211 212
	if (!plat->pbuffer) {
		blog(LOG_ERROR, "Failed to create OpenGL pbuffer");
		goto error;
	}

	success = true;

error:
	XFree(config);
	XSync(display, false);
	return success;
213
}
214

215 216 217
static void gl_context_destroy(struct gl_platform *plat)
{
	Display *display = plat->display;
218

219
	glXMakeContextCurrent(display, None, None, NULL);
220 221 222
	glXDestroyContext(display, plat->context);
	bfree(plat);
}
223

J
jp9000 已提交
224 225
extern struct gl_windowinfo *
gl_windowinfo_create(const struct gs_init_data *info)
226 227 228 229
{
	UNUSED_PARAMETER(info);
	return bmalloc(sizeof(struct gl_windowinfo));
}
230

231 232 233 234
extern void gl_windowinfo_destroy(struct gl_windowinfo *info)
{
	bfree(info);
}
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
static Display *open_windowless_display(void)
{
	Display *display = XOpenDisplay(NULL);
	xcb_connection_t *xcb_conn;
	xcb_screen_iterator_t screen_iterator;
	xcb_screen_t *screen;
	int screen_num;

	if (!display) {
		blog(LOG_ERROR, "Unable to open new X connection!");
		return NULL;
	}

	xcb_conn = XGetXCBConnection(display);
	if (!xcb_conn) {
		blog(LOG_ERROR, "Unable to get XCB connection to main display");
		goto error;
	}

	screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
	screen = screen_iterator.data;
	if (!screen) {
		blog(LOG_ERROR, "Unable to get screen root");
		goto error;
	}

	screen_num = get_screen_num_from_root(xcb_conn, screen->root);
	if (screen_num == -1) {
		blog(LOG_ERROR, "Unable to get screen number from root");
		goto error;
	}

	if (!gladLoadGLX(display, screen_num)) {
		blog(LOG_ERROR, "Unable to load GLX entry functions.");
		goto error;
	}

	return display;

error:
	if (display)
		XCloseDisplay(display);
	return NULL;
}

281 282
static int x_error_handler(Display *display, XErrorEvent *error)
{
283 284 285 286 287 288 289
	char str1[512];
	char str2[512];
	char str3[512];
	XGetErrorText(display, error->error_code, str1, sizeof(str1));
	XGetErrorText(display, error->request_code, str2, sizeof(str2));
	XGetErrorText(display, error->minor_code, str3, sizeof(str3));

J
jp9000 已提交
290 291 292 293
	blog(LOG_ERROR,
	     "X Error: %s, Major opcode: %s, "
	     "Minor opcode: %s, Serial: %lu",
	     str1, str2, str3, error->serial);
294 295 296
	return 0;
}

297
extern struct gl_platform *gl_platform_create(gs_device_t *device,
J
jp9000 已提交
298
					      uint32_t adapter)
299 300 301 302
{
	/* There's some trickery here... we're mixing libX11, xcb, and GLX
	   For an explanation see here: http://xcb.freedesktop.org/MixingCalls/
	   Essentially, GLX requires Xlib. Everything else we use xcb. */
J
jp9000 已提交
303 304
	struct gl_platform *plat = bmalloc(sizeof(struct gl_platform));
	Display *display = open_windowless_display();
305

306 307
	if (!display) {
		goto fail_display_open;
308
	}
309

310
	XSetEventQueueOwner(display, XCBOwnsEventQueue);
311
	XSetErrorHandler(x_error_handler);
312

313
	/* We assume later that cur_swap is already set. */
314
	device->plat = plat;
315

316
	plat->display = display;
317

318 319 320 321
	if (!gl_context_create(plat)) {
		blog(LOG_ERROR, "Failed to create context!");
		goto fail_context_create;
	}
322

323
	if (!glXMakeContextCurrent(plat->display, plat->pbuffer, plat->pbuffer,
J
jp9000 已提交
324
				   plat->context)) {
325 326 327
		blog(LOG_ERROR, "Failed to make context current.");
		goto fail_make_current;
	}
328

329 330 331 332
	if (!gladLoadGL()) {
		blog(LOG_ERROR, "Failed to load OpenGL entry functions.");
		goto fail_load_gl;
	}
333

334 335 336 337 338 339 340 341
	goto success;

fail_make_current:
	gl_context_destroy(plat);
fail_context_create:
fail_load_gl:
	XCloseDisplay(display);
fail_display_open:
342
	bfree(plat);
343 344
	plat = NULL;
success:
345
	UNUSED_PARAMETER(adapter);
346
	return plat;
347 348
}

349
extern void gl_platform_destroy(struct gl_platform *plat)
350
{
351
	if (!plat) /* In what case would platform be invalid here? */
352 353
		return;

354
	gl_context_destroy(plat);
355 356
}

357
extern bool gl_platform_init_swapchain(struct gs_swap_chain *swap)
358
{
359 360 361 362 363 364 365 366 367 368 369 370
	Display *display = swap->device->plat->display;
	xcb_connection_t *xcb_conn = XGetXCBConnection(display);
	xcb_window_t wid = xcb_generate_id(xcb_conn);
	xcb_window_t parent = swap->info.window.id;
	xcb_get_geometry_reply_t *geometry =
		get_window_geometry(xcb_conn, parent);
	bool status = false;

	int screen_num;
	int visual;
	GLXFBConfig *fb_config;

J
jp9000 已提交
371 372
	if (!geometry)
		goto fail_geometry_request;
373 374 375 376

	screen_num = get_screen_num_from_root(xcb_conn, geometry->root);
	if (screen_num == -1) {
		goto fail_screen;
377 378
	}

379 380 381 382
	/* ...fetch the best match... */
	{
		int num_configs;
		fb_config = glXChooseFBConfig(display, screen_num,
J
jp9000 已提交
383
					      ctx_visual_attribs, &num_configs);
384 385 386 387 388 389

		if (!fb_config || !num_configs) {
			blog(LOG_ERROR, "Failed to find FBConfig!");
			goto fail_fb_config;
		}
	}
390

391 392
	/* ...then fetch matching visual info for xcb. */
	{
J
jp9000 已提交
393 394
		int error = glXGetFBConfigAttrib(display, fb_config[0],
						 GLX_VISUAL_ID, &visual);
395

396 397 398 399 400
		if (error) {
			blog(LOG_ERROR, "Bad call to GetFBConfigAttrib!");
			goto fail_visual_id;
		}
	}
401

402 403
	xcb_colormap_t colormap = xcb_generate_id(xcb_conn);
	uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP;
J
jp9000 已提交
404 405 406 407 408 409 410 411
	uint32_t mask_values[] = {0, colormap, 0};

	xcb_create_colormap(xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, parent,
			    visual);

	xcb_create_window(xcb_conn, 24 /* Hardcoded? */, wid, parent, 0, 0,
			  geometry->width, geometry->height, 0, 0, visual, mask,
			  mask_values);
412

413 414
	swap->wi->config = fb_config[0];
	swap->wi->window = wid;
415

416
	xcb_map_window(xcb_conn, wid);
417

418 419 420
	XFree(fb_config);
	status = true;
	goto success;
421

422 423 424 425 426 427 428 429
fail_visual_id:
	XFree(fb_config);
fail_fb_config:
fail_screen:
fail_geometry_request:
success:
	free(geometry);
	return status;
430 431
}

432
extern void gl_platform_cleanup_swapchain(struct gs_swap_chain *swap)
433
{
434 435
	UNUSED_PARAMETER(swap);
	/* Really nothing to clean up? */
436 437
}

438
extern void device_enter_context(gs_device_t *device)
439 440
{
	GLXContext context = device->plat->context;
441
	Display *display = device->plat->display;
442

443 444 445 446 447 448 449 450 451 452
	if (device->cur_swap) {
		XID window = device->cur_swap->wi->window;
		if (!glXMakeContextCurrent(display, window, window, context)) {
			blog(LOG_ERROR, "Failed to make context current.");
		}
	} else {
		GLXPbuffer pbuf = device->plat->pbuffer;
		if (!glXMakeContextCurrent(display, pbuf, pbuf, context)) {
			blog(LOG_ERROR, "Failed to make context current.");
		}
453 454
	}
}
455

456
extern void device_leave_context(gs_device_t *device)
457
{
458
	Display *display = device->plat->display;
459

460
	if (!glXMakeContextCurrent(display, None, None, NULL)) {
461 462 463
		blog(LOG_ERROR, "Failed to reset current context.");
	}
}
464

465 466 467 468 469
void *device_get_device_obj(gs_device_t *device)
{
	return device->plat->context;
}

J
jp9000 已提交
470 471
extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width,
			     uint32_t *height)
472
{
J
jp9000 已提交
473 474
	xcb_connection_t *xcb_conn =
		XGetXCBConnection(swap->device->plat->display);
475 476
	xcb_window_t window = swap->wi->window;

J
jp9000 已提交
477 478
	xcb_get_geometry_reply_t *geometry =
		get_window_geometry(xcb_conn, window);
479
	if (geometry) {
J
jp9000 已提交
480
		*width = geometry->width;
481 482
		*height = geometry->height;
	}
483

484
	free(geometry);
485 486
}

487 488 489 490 491 492 493 494 495
extern void gl_clear_context(gs_device_t *device)
{
	Display *display = device->plat->display;

	if (!glXMakeContextCurrent(display, None, None, NULL)) {
		blog(LOG_ERROR, "Failed to reset current context.");
	}
}

496 497 498 499 500
extern void gl_update(gs_device_t *device)
{
	Display *display = device->plat->display;
	xcb_window_t window = device->cur_swap->wi->window;

J
jp9000 已提交
501 502
	uint32_t values[] = {device->cur_swap->info.cx,
			     device->cur_swap->info.cy};
503

J
jp9000 已提交
504 505 506
	xcb_configure_window(XGetXCBConnection(display), window,
			     XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
			     values);
507 508 509
}

extern void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap)
510
{
511 512 513
	if (device->cur_swap == swap)
		return;

514
	Display *dpy = device->plat->display;
515 516
	GLXContext ctx = device->plat->context;

517
	device->cur_swap = swap;
518

519 520 521 522 523 524 525 526 527 528
	if (swap) {
		XID window = swap->wi->window;
		if (!glXMakeContextCurrent(dpy, window, window, ctx)) {
			blog(LOG_ERROR, "Failed to make context current.");
		}
	} else {
		GLXPbuffer pbuf = device->plat->pbuffer;
		if (!glXMakeContextCurrent(dpy, pbuf, pbuf, ctx)) {
			blog(LOG_ERROR, "Failed to make context current.");
		}
529
	}
530 531
}

532 533 534 535 536 537 538
enum swap_type {
	SWAP_TYPE_NORMAL,
	SWAP_TYPE_EXT,
	SWAP_TYPE_MESA,
	SWAP_TYPE_SGI,
};

539
extern void device_present(gs_device_t *device)
540
{
541 542 543
	static bool initialized = false;
	static enum swap_type swap_type = SWAP_TYPE_NORMAL;

544 545 546
	Display *display = device->plat->display;
	XID window = device->cur_swap->wi->window;

547 548 549 550 551 552 553 554 555 556 557
	if (!initialized) {
		if (GLAD_GLX_EXT_swap_control)
			swap_type = SWAP_TYPE_EXT;
		else if (GLAD_GLX_MESA_swap_control)
			swap_type = SWAP_TYPE_MESA;
		else if (GLAD_GLX_SGI_swap_control)
			swap_type = SWAP_TYPE_SGI;

		initialized = true;
	}

558 559
	xcb_connection_t *xcb_conn = XGetXCBConnection(display);
	xcb_generic_event_t *xcb_event;
J
jp9000 已提交
560
	while ((xcb_event = xcb_poll_for_event(xcb_conn))) {
561 562 563
		/* TODO: Handle XCB events. */
		free(xcb_event);
	}
564

565
	switch (swap_type) {
J
jp9000 已提交
566 567 568 569 570 571 572 573 574
	case SWAP_TYPE_EXT:
		glXSwapIntervalEXT(display, window, 0);
		break;
	case SWAP_TYPE_MESA:
		glXSwapIntervalMESA(0);
		break;
	case SWAP_TYPE_SGI:
		glXSwapIntervalSGI(0);
		break;
575 576 577
	case SWAP_TYPE_NORMAL:;
	}

578 579
	glXSwapBuffers(display, window);
}