libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
glctx.c
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license in the file COPYING
10 * or http://www.opensource.org/licenses/CDDL-1.0.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file COPYING.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2020 Saso Kiselkov. All rights reserved.
24 */
25
26#include <string.h>
27#include <stdlib.h>
28
29#if LIN
30#include <X11/X.h>
31#include <X11/Xlib.h>
32#include <GL/gl.h>
33#include <GL/glx.h>
34#elif IBM
35#include <windows.h>
36#include <wingdi.h>
37#else /* APL */
38#include <OpenGL/OpenGL.h>
39#include <OpenGL/CGLTypes.h>
40#include <OpenGL/CGLCurrent.h>
41#endif /* APL */
42
43#include "acfutils/assert.h"
44#include "acfutils/dr.h"
45#include "acfutils/glctx.h"
47#include "acfutils/log.h"
48#include "acfutils/safe_alloc.h"
49#include "acfutils/thread.h"
50
51#if defined(__WGLEW_H__) || defined(__GLEW_H__)
52#error "MUST NOT include GLEW headers from glctx.c"
53#endif
54
55struct glctx_s {
56#if LIN
57 Display *dpy;
58 GLXContext glc;
59#elif IBM
60 char win_cls_name[32];
61 HWND win;
62 bool_t release_dc;
63 HDC dc;
64 HGLRC hgl;
65#else /* APL */
66 CGLContextObj cgl;
67#endif /* APL */
68 bool_t created;
69};
70
71#if LIN
72
73glctx_t *
74glctx_create_invisible(void *win_ptr, glctx_t *share_ctx, int major_ver,
75 int minor_ver, bool_t fwd_compat, bool_t debug)
76{
77 typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*,
78 GLXFBConfig, GLXContext, Bool, const int*);
79 typedef GLXFBConfig *(*glXChooseFBConfigARBProc)(Display *, int,
80 const int *, int *);
81
82 glXCreateContextAttribsARBProc glXCreateContextAttribsARB = NULL;
83 glXChooseFBConfigARBProc glXChooseFBConfigARB = NULL;
84
85 static int visual_attribs[] = { None };
86 int context_attribs[] = {
87 GLX_CONTEXT_MAJOR_VERSION_ARB, major_ver,
88 GLX_CONTEXT_MINOR_VERSION_ARB, minor_ver,
89 GLX_CONTEXT_FLAGS_ARB,
90 (fwd_compat ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB : 0) |
91 (debug ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
92 None
93 };
94 int fbcount = 0;
95 GLXFBConfig *fbc = NULL;
96 glctx_t *ctx = safe_calloc(1, sizeof (*ctx));
97
98 ASSERT(share_ctx == NULL || share_ctx->glc != NULL);
99
100 ctx->created = B_TRUE;
101
102 /* open display */
103 ctx->dpy = XOpenDisplay(win_ptr);
104 if (ctx->dpy == NULL) {
105 logMsg("Failed to open display");
106 goto errout;
107 }
108 /* Get the function pointers */
109 glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
110 glXGetProcAddressARB((GLubyte *)"glXCreateContextAttribsARB");
111 glXChooseFBConfigARB = (glXChooseFBConfigARBProc)
112 glXGetProcAddress((GLubyte *)"glXChooseFBConfig");
113 if (glXCreateContextAttribsARB == NULL ||
114 glXChooseFBConfigARB == NULL) {
115 logMsg("Missing support for GLX_ARB_create_context");
116 goto errout;
117 }
118 /*
119 * get framebuffer configs, any is usable (might want to add proper
120 * attribs)
121 */
122 fbc = glXChooseFBConfigARB(ctx->dpy, DefaultScreen(ctx->dpy),
123 visual_attribs, &fbcount);
124 if (fbc == NULL) {
125 logMsg("Failed to get FBConfig");
126 goto errout;
127 }
128 /* Create a context using glXCreateContextAttribsARB */
129 ctx->glc = glXCreateContextAttribsARB(ctx->dpy, fbc[0],
130 share_ctx != NULL ? share_ctx->glc : NULL, True, context_attribs);
131 if (ctx->glc == NULL) {
132 logMsg("Failed to create opengl context");
133 goto errout;
134 }
135 XFree(fbc);
136 fbc = NULL;
137
138 return (ctx);
139errout:
140 if (fbc != NULL)
141 XFree(fbc);
142 glctx_destroy(ctx);
143
144 return (NULL);
145}
146
147void *
149{
150 return (getenv("DISPLAY"));
151}
152
153#elif IBM
154
155#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
156#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
157#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
158#define WGL_CONTEXT_FLAGS_ARB 0x2094
159#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
160
161#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001
162#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
163
164/*
165 * Creates a private window with a private device context that we
166 * utilize when we don't need sharelists. This is more stable under
167 * Vulkan, which may not set up the main window device context
168 * pixel format to be OpenGL-compatible.
169 */
170static bool_t
171glctx_create_priv_window(glctx_t *ctx)
172{
173 const PIXELFORMATDESCRIPTOR pfd = {
174 .nSize = sizeof(pfd),
175 .nVersion = 1,
176 .iPixelType = PFD_TYPE_RGBA,
177 .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
178 PFD_DOUBLEBUFFER,
179 .cColorBits = 32,
180 .cAlphaBits = 8,
181 .iLayerType = PFD_MAIN_PLANE,
182 .cDepthBits = 24,
183 .cStencilBits = 8,
184 };
185 WNDCLASSA wc = {
186 .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
187 .lpfnWndProc = DefWindowProcA,
188 .hInstance = GetModuleHandle(NULL)
189 };
190 int pixel_format;
191
192 ASSERT(ctx != NULL);
193
194 /*
195 * Using the context struct pointer should be unique enough
196 * for the class name. Allocating two different structs with
197 * the same address should be impossible (and if it happens,
198 * we're dealing with MUCH bigger problems).
199 */
200 snprintf(ctx->win_cls_name, sizeof (ctx->win_cls_name),
201 "glctx-%p", ctx);
202 wc.lpszClassName = ctx->win_cls_name;
203 if (!RegisterClassA(&wc)) {
204 win_perror(GetLastError(), "Failed to register window class");
205 memset(ctx->win_cls_name, 0, sizeof (ctx->win_cls_name));
206 return (B_FALSE);
207 }
208 ctx->win = CreateWindowA(ctx->win_cls_name, ctx->win_cls_name,
209 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
210 0, 0, 32, 32, NULL, NULL, GetModuleHandle(NULL), NULL);
211 if (ctx->win == NULL) {
212 win_perror(GetLastError(), "Failed to create window");
213 return (B_FALSE);
214 }
215 ctx->dc = GetDC(ctx->win);
216 if (ctx->dc == NULL) {
217 win_perror(GetLastError(),
218 "Failed to get window device context");
219 return (B_FALSE);
220 }
221 ctx->release_dc = B_TRUE;
222 pixel_format = ChoosePixelFormat(ctx->dc, &pfd);
223 if (pixel_format == 0) {
224 logMsg("Couldn't find a suitable pixel format");
225 return (B_FALSE);
226 }
227 if (!SetPixelFormat(ctx->dc, pixel_format, &pfd)) {
228 win_perror(GetLastError(), "Couldn't set pixel format");
229 return (B_FALSE);
230 }
231
232 return (B_TRUE);
233}
234
235glctx_t *
236glctx_create_invisible(void *win_ptr, glctx_t *share_ctx, int major_ver,
237 int minor_ver, bool_t fwd_compat, bool_t debug)
238{
239 typedef HGLRC (*wglCreateContextAttribsProc)(HDC, HGLRC, const int *);
240 wglCreateContextAttribsProc wglCreateContextAttribsARB;
241 const int attrs[] = {
242 WGL_CONTEXT_MAJOR_VERSION_ARB, major_ver,
243 WGL_CONTEXT_MINOR_VERSION_ARB, minor_ver,
244 WGL_CONTEXT_FLAGS_ARB,
245 (fwd_compat ? WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB : 0) |
246 (debug ? WGL_CONTEXT_DEBUG_BIT_ARB : 0),
247 0 /* list terminator */
248 };
249 glctx_t *ctx;
250
251 ASSERT(share_ctx == NULL || share_ctx->hgl != NULL);
252 LACF_UNUSED(win_ptr);
253
254 /* Get the required extensions */
255 wglCreateContextAttribsARB = (wglCreateContextAttribsProc)
256 wglGetProcAddress("wglCreateContextAttribsARB");
257 if (wglCreateContextAttribsARB == NULL) {
258 logMsg("Missing support for WGL_ARB_create_context");
259 return (NULL);
260 }
261 ctx = safe_calloc(1, sizeof (*ctx));
262 ctx->created = B_TRUE;
263 /*
264 * We used to attempt to reuse X-Plane's window's device context
265 * here. But since Vulkan, that is just too unreliable, so we
266 * just always use a private window and call it a day.
267 */
268 if (!glctx_create_priv_window(ctx))
269 goto errout;
270 ASSERT(ctx->dc != NULL);
271 ctx->hgl = wglCreateContextAttribsARB(ctx->dc,
272 share_ctx != NULL ? share_ctx->hgl : NULL, attrs);
273 if (ctx->hgl == NULL) {
274 win_perror(GetLastError(),
275 "Failed to create invisible OpenGL context");
276 goto errout;
277 }
278
279 return (ctx);
280errout:
281 glctx_destroy(ctx);
282 return (NULL);
283}
284
285void *
287{
288 dr_t win_dr;
289 unsigned int win_i[2];
290 HWND xp_window;
291
292 fdr_find(&win_dr, "sim/operation/windows/system_window_64");
293 VERIFY3U(dr_getvi(&win_dr, (int *)win_i, 0, 2), ==, 2);
294
295 xp_window = (HWND)((uintptr_t)win_i[1] << 32 | (uintptr_t)win_i[0]);
296 VERIFY(xp_window != INVALID_HANDLE_VALUE);
297
298 return (xp_window);
299}
300
301#else /* APL */
302
303static CGLOpenGLProfile
304get_gl_profile(int major_ver)
305{
306 if (major_ver <= 2)
307 return (kCGLOGLPVersion_Legacy);
308 if (major_ver == 3)
309 return (kCGLOGLPVersion_GL3_Core);
310 return (kCGLOGLPVersion_GL4_Core);
311}
312
313glctx_t *
314glctx_create_invisible(void *win_ptr, glctx_t *share_ctx, int major_ver,
315 int minor_ver, bool_t fwd_compat, bool_t debug)
316{
317 CGLOpenGLProfile profile = get_gl_profile(major_ver);
318 const CGLPixelFormatAttribute attrs[] = {
319 /* always request hardware acceleration */
320 kCGLPFAAccelerated,
321 kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)profile,
322 0 /* list terminator */
323 };
324 CGLPixelFormatObj pix;
325 CGLError error;
326 glctx_t *ctx = safe_calloc(1, sizeof (*ctx));
327
328 LACF_UNUSED(win_ptr);
329 LACF_UNUSED(minor_ver);
330 LACF_UNUSED(fwd_compat);
331 LACF_UNUSED(debug);
332 ASSERT(share_ctx == NULL || share_ctx->cgl != NULL);
333
334 ctx->created = B_TRUE;
335 if (share_ctx != NULL) {
336 pix = CGLGetPixelFormat(share_ctx->cgl);
337 error = CGLCreateContext(pix, share_ctx->cgl, &ctx->cgl);
338 } else {
339 GLint num;
340 error = CGLChoosePixelFormat(attrs, &pix, &num);
341 if (error != kCGLNoError) {
342 logMsg("CGLChoosePixelFormat failed with error %d",
343 error);
344 goto errout;
345 }
346 error = CGLCreateContext(pix, NULL, &ctx->cgl);
347 CGLDestroyPixelFormat(pix);
348 }
349 if (error != kCGLNoError) {
350 logMsg("CGLCreateContext failed with error %d", error);
351 goto errout;
352 }
353
354 return (ctx);
355errout:
356 glctx_destroy(ctx);
357 return (NULL);
358}
359
360void *
362{
363 /* unnecessary on MacOS */
364 return (NULL);
365}
366
367#endif /* APL */
368
369glctx_t *
371{
372 glctx_t *ctx = safe_calloc(1, sizeof (*ctx));
373
374#if LIN
375 /* open display */
376 ctx->dpy = XOpenDisplay(0);
377 if (ctx->dpy == NULL) {
378 logMsg("Failed to open display");
379 goto errout;
380 }
381 ctx->glc = glXGetCurrentContext();
382 if (ctx->glc == NULL) {
383 glctx_destroy(ctx);
384 return (NULL);
385 }
386
387 return (ctx);
388errout:
389 glctx_destroy(ctx);
390 return (NULL);
391#elif IBM
392 ctx->hgl = wglGetCurrentContext();
393 if (ctx->hgl == 0) {
394 glctx_destroy(ctx);
395 return (NULL);
396 }
397 ctx->dc = wglGetCurrentDC();
398 if (ctx->dc == NULL) {
399 logMsg("Current context had no DC?!");
400 glctx_destroy(ctx);
401 return (NULL);
402 }
403 return (ctx);
404#else /* APL */
405 ctx->cgl = CGLGetCurrentContext();
406 if (ctx->cgl == NULL) {
407 glctx_destroy(ctx);
408 return (NULL);
409 }
410 return (ctx);
411#endif /* APL */
412}
413
414API_EXPORT bool_t
415glctx_is_current(glctx_t *ctx)
416{
417 ASSERT(ctx != NULL);
418#if LIN
419 return (ctx->glc == glXGetCurrentContext());
420#elif IBM
421 return (ctx->hgl == wglGetCurrentContext());
422#else /* APL */
423 return (ctx->cgl == CGLGetCurrentContext());
424#endif /* APL */
425}
426
427void *
428glctx_get_handle(const glctx_t *ctx)
429{
430#if LIN
431 return (ctx->glc);
432#elif IBM
433 return (ctx->hgl);
434#else /* APL */
435 return (ctx->cgl);
436#endif /* APL */
437}
438
439bool_t
441{
442#if LIN
443 typedef Bool (*glXMakeContextCurrentARBProc)(Display*,
444 GLXDrawable, GLXDrawable, GLXContext);
445 static glXMakeContextCurrentARBProc glXMakeContextCurrentARB = NULL;
446
447 if (glXMakeContextCurrentARB == NULL) {
448 glXMakeContextCurrentARB = (glXMakeContextCurrentARBProc)
449 glXGetProcAddressARB((GLubyte *)"glXMakeContextCurrent");
450 /*
451 * We should never have gotten here without a working
452 * GLX_ARB_create_context.
453 */
454 VERIFY(glXMakeContextCurrentARB != NULL);
455 }
456 if (ctx != NULL) {
457 GLXDrawable tgt = (glutils_in_zink_mode() ? None :
458 DefaultRootWindow(ctx->dpy));
459 ASSERT(ctx->dpy != NULL);
460 ASSERT(ctx->glc != NULL);
461 if (!glXMakeContextCurrentARB(ctx->dpy, tgt, tgt, ctx->glc)) {
462 logMsg("Failed to make context current");
463 return (B_FALSE);
464 }
465 } else {
466 glXMakeContextCurrentARB(NULL, None, None, NULL);
467 }
468#elif IBM
469 if (ctx != NULL) {
470 ASSERT(ctx->dc != NULL);
471 ASSERT(ctx->hgl != NULL);
472 if (!wglMakeCurrent(ctx->dc, ctx->hgl)) {
473 win_perror(GetLastError(),
474 "Failed to make context current");
475 return (B_FALSE);
476 }
477 } else {
478 wglMakeCurrent(NULL, NULL);
479 }
480#else /* APL */
481 if (ctx != NULL) {
482 CGLError error;
483
484 ASSERT(ctx->cgl != NULL);
485 error = CGLSetCurrentContext(ctx->cgl);
486 if (error != kCGLNoError) {
487 logMsg("CGLSetCurrentContext failed with error %d",
488 error);
489 return (B_FALSE);
490 }
491 } else {
492 CGLSetCurrentContext(NULL);
493 }
494#endif /* APL */
495 return (B_TRUE);
496}
497
498void *
500{
501 ASSERT(ctx != NULL);
502#if LIN
503 ASSERT(ctx->dpy != NULL);
504 return (ctx->dpy);
505#elif IBM
506 ASSERT(ctx->win != NULL);
507 return (ctx->win);
508#else /* APL */
509 return (NULL);
510#endif /* APL */
511}
512
513API_EXPORT void
514glctx_destroy(glctx_t *ctx)
515{
516 if (ctx == NULL)
517 return;
518#if LIN
519 if (ctx->created) {
520 if (ctx->glc != NULL)
521 glXDestroyContext(ctx->dpy, ctx->glc);
522 }
523 if (ctx->dpy != NULL)
524 XCloseDisplay(ctx->dpy);
525#elif IBM
526 if (ctx->created) {
527 glctx_t *cur_ctx;
528
529 /*
530 * Due to a bug in ReShade, deleting a context instead installs
531 * it as the "current" context, which means this immediately
532 * invalidates the current context. To work around this bug,
533 * we grab our current context before deleting the other one
534 * and then we reinstall it back as the current context.
535 */
536 cur_ctx = glctx_get_current();
537 if (!wglDeleteContext(ctx->hgl))
538 win_perror(GetLastError(), "wglDeleteContext failed");
539 if (cur_ctx != NULL) {
540 glctx_make_current(cur_ctx);
541 /*
542 * This won't recurse again, because the output of
543 * glctx_get_current doesn't return a context with
544 * the `created' flag set.
545 */
546 glctx_destroy(cur_ctx);
547 }
548 ASSERT(ctx->win != NULL);
549 if (ctx->dc != NULL && ctx->release_dc) {
550 ASSERT(ctx->win != NULL);
551 ReleaseDC(ctx->win, ctx->dc);
552 }
553 if (ctx->win != NULL)
554 DestroyWindow(ctx->win);
555 if (ctx->win_cls_name[0] != '\0') {
556 UnregisterClassA(ctx->win_cls_name,
557 GetModuleHandle(NULL));
558 }
559 }
560#elif APL
561 if (ctx->created) {
562 CGLDestroyContext(ctx->cgl);
563 }
564#endif /* APL */
565 memset(ctx, 0, sizeof (*ctx));
566 free(ctx);
567}
#define VERIFY(x)
Definition assert.h:78
#define ASSERT(x)
Definition assert.h:208
#define VERIFY3U(x, op, y)
Definition assert.h:136
#define fdr_find(dr,...)
Definition dr.h:318
#define dr_getvi(__dr, __i, __off, __num)
Definition dr.h:426
API_EXPORT void * glctx_get_window_system_handle(glctx_t *ctx)
Definition glctx.c:499
API_EXPORT void * glctx_get_handle(const glctx_t *ctx)
Definition glctx.c:428
API_EXPORT glctx_t * glctx_get_current(void)
Definition glctx.c:370
API_EXPORT void glctx_destroy(glctx_t *ctx)
Definition glctx.c:514
API_EXPORT void * glctx_get_xplane_win_ptr(void)
Definition glctx.c:361
API_EXPORT bool_t glctx_is_current(glctx_t *ctx)
Definition glctx.c:415
API_EXPORT glctx_t * glctx_create_invisible(void *win_ptr, glctx_t *share_ctx, int major_ver, int minor_ver, bool_t fwd_compat, bool_t debug)
Definition glctx.c:314
API_EXPORT bool_t glctx_make_current(glctx_t *ctx)
Definition glctx.c:440
bool_t glutils_in_zink_mode(void)
Definition glutils.c:1446
#define logMsg(...)
Definition log.h:112
static void * safe_calloc(size_t nmemb, size_t size)
Definition safe_alloc.h:71