1 /** \file opengl_renderer_unix.c
2 *
3 * \author David Hogan <david.q.hogan@gmail.com>
4 */
5
6 /*
7 * This file is part of VICE, the Versatile Commodore Emulator.
8 * See README for copyright notice.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 * 02111-1307 USA.
24 *
25 */
26
27 #include "vice.h"
28
29 #include "archdep.h"
30
31 #include <string.h>
32
33 #include "opengl_renderer.h"
34
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37
38 #include "log.h"
39 #include "render_queue.h"
40
41 #define CANVAS_LOCK() pthread_mutex_lock(&context->canvas_lock)
42 #define CANVAS_UNLOCK() pthread_mutex_unlock(&context->canvas_lock)
43 #define RENDER_LOCK() pthread_mutex_lock(&context->render_lock)
44 #define RENDER_UNLOCK() pthread_mutex_unlock(&context->render_lock)
45
46 typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
47
48 static bool isExtensionSupported(const char *extList, const char *extension);
49
50 /**/
51
vice_opengl_renderer_make_current(vice_opengl_renderer_context_t * context)52 void vice_opengl_renderer_make_current(vice_opengl_renderer_context_t *context)
53 {
54 glXMakeCurrent(context->x_display, context->x_overlay_window, context->gl_context);
55 }
56
vice_opengl_renderer_set_viewport(vice_opengl_renderer_context_t * context)57 void vice_opengl_renderer_set_viewport(vice_opengl_renderer_context_t *context)
58 {
59 glViewport(0, 0, context->gl_backing_layer_width, context->gl_backing_layer_height);
60 }
61
vice_opengl_renderer_present_backbuffer(vice_opengl_renderer_context_t * context)62 void vice_opengl_renderer_present_backbuffer(vice_opengl_renderer_context_t *context)
63 {
64 glXSwapBuffers(context->x_display, context->x_overlay_window);
65 }
66
vice_opengl_renderer_clear_current(vice_opengl_renderer_context_t * context)67 void vice_opengl_renderer_clear_current(vice_opengl_renderer_context_t *context)
68 {
69 glXMakeCurrent(context->x_display, None, NULL);
70 }
71
catch_x_error(Display * display,XErrorEvent * event)72 static int catch_x_error(Display *display, XErrorEvent *event)
73 {
74 return 0;
75 }
76
77 /**/
78
vice_opengl_renderer_create_child_view(GtkWidget * widget,vice_opengl_renderer_context_t * context)79 void vice_opengl_renderer_create_child_view(GtkWidget *widget, vice_opengl_renderer_context_t *context)
80 {
81 /*
82 * OpenGL context initialisation adapted from
83 * https://www.khronos.org/opengl/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
84 */
85
86 /* use the same connection to the x server that GDK is using */
87 context->x_display = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(widget));
88
89 /* we need to create a child window compatible with the type of OpenGL stuff we want */
90 static int visual_attribs[] =
91 {
92 GLX_X_RENDERABLE , True,
93 GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT,
94 GLX_RENDER_TYPE , GLX_RGBA_BIT,
95 GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR,
96 GLX_RED_SIZE , 8,
97 GLX_GREEN_SIZE , 8,
98 GLX_BLUE_SIZE , 8,
99 GLX_ALPHA_SIZE , 8,
100 GLX_DOUBLEBUFFER , True,
101 None
102 };
103
104 int glx_major;
105 int glx_minor;
106
107 if (!glXQueryVersion(context->x_display, &glx_major, &glx_minor)) {
108 log_error(LOG_DEFAULT, "Failed to get GLX version\n");
109 archdep_vice_exit(1);
110 }
111
112 log_message(LOG_DEFAULT, "GLX version: %d.%d", glx_major, glx_minor);
113
114 /* FBConfigs were added in GLX version 1.3. */
115 if (glx_major < 1 || (glx_major == 1 && glx_minor < 3)) {
116 log_error(LOG_DEFAULT, "At least GLX 1.3 is required\n");
117 archdep_vice_exit(1);
118 }
119
120 log_message(LOG_DEFAULT, "Getting matching framebuffer configs" );
121 int fbcount;
122 PFNGLXCHOOSEFBCONFIGPROC vice_glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)glXGetProcAddressARB((const GLubyte *)"glXChooseFBConfig");
123 GLXFBConfig *framebuffer_configs = vice_glXChooseFBConfig(context->x_display, DefaultScreen(context->x_display), visual_attribs, &fbcount);
124 if (!framebuffer_configs) {
125 log_error(LOG_DEFAULT, "Failed to retrieve a framebuffer config\n");
126 archdep_vice_exit(1);
127 }
128 log_message(LOG_DEFAULT, "Found %d matching FB configs.", fbcount);
129
130 /* Just pick the first one I guess. */
131 GLXFBConfig framebuffer_config = framebuffer_configs[0];
132 XFree(framebuffer_configs);
133
134 /* Get a visual */
135 PFNGLXGETVISUALFROMFBCONFIGPROC vice_glXGetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC)glXGetProcAddressARB((const GLubyte *)"glXGetVisualFromFBConfig");
136 XVisualInfo *x_visual = vice_glXGetVisualFromFBConfig(context->x_display, framebuffer_config);
137
138 XSetWindowAttributes x_set_window_attributes;
139
140 x_set_window_attributes.colormap =
141 XCreateColormap(
142 context->x_display,
143 GDK_WINDOW_XID(gtk_widget_get_window(widget)),
144 x_visual->visual,
145 AllocNone);
146 x_set_window_attributes.background_pixmap = None ;
147 x_set_window_attributes.border_pixel = 0;
148 x_set_window_attributes.event_mask = 0;
149
150 context->x_overlay_window =
151 XCreateWindow(
152 context->x_display,
153 GDK_WINDOW_XID(gtk_widget_get_window(widget)),
154 context->native_view_x, context->native_view_y, context->gl_backing_layer_width, context->gl_backing_layer_height,
155 0,
156 x_visual->depth,
157 InputOutput,
158 x_visual->visual,
159 CWColormap | CWBackPixmap | CWBorderPixel | CWEventMask,
160 &x_set_window_attributes);
161
162 XMapWindow(context->x_display, context->x_overlay_window);
163
164 /* Done with the visual info data */
165 XFree(x_visual);
166
167 /* Get the screen's GLX extension list */
168 const char *glx_extensions = glXQueryExtensionsString(context->x_display, DefaultScreen(context->x_display));
169
170 PFNGLXCREATECONTEXTATTRIBSARBPROC vice_glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
171
172 /*
173 * Check for the GLX_ARB_create_context extension string and the function.
174 * If either is not present, use GLX 1.3 context creation method.
175 */
176
177 if (!isExtensionSupported(glx_extensions, "GLX_ARB_create_context") || !vice_glXCreateContextAttribsARB) {
178 /* Legact context -- TODO, actually support using this */
179 PFNGLXCREATENEWCONTEXTPROC vice_glXCreateNewContext = (PFNGLXCREATENEWCONTEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateNewContext");
180 context->gl_context = vice_glXCreateNewContext(context->x_display, framebuffer_config, GLX_RGBA_TYPE, NULL, True);
181 }
182 else
183 {
184 XErrorHandler oldHandler;
185 int context_attribs[] =
186 {
187 GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
188 GLX_CONTEXT_MINOR_VERSION_ARB, 2,
189 None
190 };
191
192 oldHandler = XSetErrorHandler(catch_x_error);
193
194 context->gl_context = vice_glXCreateContextAttribsARB(context->x_display, framebuffer_config, NULL, True, context_attribs);
195
196 /* Sync to ensure any errors generated are processed. */
197 XSync(context->x_display, False);
198 XSetErrorHandler(oldHandler);
199
200 if (!context->gl_context) {
201 log_error(LOG_DEFAULT, "Failed to obtain an OpenGL 3.2 context, requesting a legacy context\n" );
202
203 /*
204 * Couldn't create GL 3.2 context. Fall back to old-style 2.x context.
205 * When a context version below 3.0 is requested, implementations will
206 * return the newest context version compatible with OpenGL versions less
207 * than version 3.0.
208 */
209
210 context_attribs[1] = 1;
211 context_attribs[3] = 0;
212
213 context->gl_context = vice_glXCreateContextAttribsARB(context->x_display, framebuffer_config, NULL, True, context_attribs);
214 }
215 }
216
217 /* Sync to ensure any errors generated are processed. */
218 XSync(context->x_display, False);
219
220 /* make sure OpenGL extension pointers are loaded for the general renderer code */
221 vice_opengl_renderer_make_current(context);
222 glewInit();
223
224 int major = -1;
225 int minor = -1;
226 sscanf((const char *)glGetString(GL_VERSION), "%d.%d", &major, &minor);
227
228 /* Anything less than OpenGL 3.2 will use the legacy renderer */
229 context->gl_context_is_legacy = major < 3 || (major == 3 && minor < 2);
230
231 log_message(LOG_DEFAULT, "Obtained OpenGL %d.%d context\n\t Vendor: %s\n\tRenderer: %s\n\t Version: %s\n\t Legacy: %s",
232 major,
233 minor,
234 glGetString(GL_VENDOR),
235 glGetString(GL_RENDERER),
236 glGetString(GL_VERSION),
237 context->gl_context_is_legacy ? "yes" : "no");
238
239 /* Not sure if an indirect context will work but lets leave some useful output for bug reports */
240 if (!glXIsDirect(context->x_display, context->gl_context)) {
241 log_message(LOG_DEFAULT, "Indirect GLX rendering context obtained - please let us know if this works!");
242 } else {
243 log_message(LOG_DEFAULT, "Direct GLX rendering context obtained");
244 }
245
246 log_message(LOG_DEFAULT, "Swap control support. glXSwapIntervalMESA: %d glXSwapIntervalEXT: %d glXSwapIntervalSGI: %d", !!glXSwapIntervalMESA, !!glXSwapIntervalEXT, !!glXSwapIntervalSGI);
247
248 vice_opengl_renderer_clear_current(context);
249 }
250
vice_opengl_renderer_set_vsync(vice_opengl_renderer_context_t * context,bool enable_vsync)251 void vice_opengl_renderer_set_vsync(vice_opengl_renderer_context_t *context, bool enable_vsync)
252 {
253 static bool set_vsync_failed;
254
255 GLint gl_int = enable_vsync ? 1 : 0;
256 int result = 0;
257
258 /* WTF opengl. */
259 if (GLX_MESA_swap_control && glXSwapIntervalMESA) {
260 result = glXSwapIntervalMESA(gl_int);
261 if (result) {
262 if (!set_vsync_failed) {
263 log_error(LOG_DEFAULT, "glXSwapIntervalMESA(%d) failed with error: %d (suppressing futher errors)", gl_int, result);
264 set_vsync_failed = true;
265 }
266 } else {
267 if (set_vsync_failed) {
268 log_message(LOG_DEFAULT, "glXSwapIntervalMESA(%d) started working again", gl_int);
269 set_vsync_failed = false;
270 }
271 }
272 } else if (GLX_EXT_swap_control && glXSwapIntervalEXT) {
273 glXSwapIntervalEXT(context->x_display, glXGetCurrentDrawable(), gl_int);
274 } else if (GLX_SGI_swap_control && glXSwapIntervalSGI) {
275 result = glXSwapIntervalSGI(gl_int);
276 if (result) {
277 if (!set_vsync_failed) {
278 log_error(LOG_DEFAULT, "glXSwapIntervalSGI(%d) failed with error: %d (suppressing futher errors)", gl_int, result);
279 set_vsync_failed = true;
280 }
281 } else {
282 if (set_vsync_failed) {
283 log_message(LOG_DEFAULT, "glXSwapIntervalSGI(%d) started working again", gl_int);
284 set_vsync_failed = false;
285 }
286 }
287 }
288 }
289
vice_opengl_renderer_resize_child_view(vice_opengl_renderer_context_t * context)290 void vice_opengl_renderer_resize_child_view(vice_opengl_renderer_context_t *context)
291 {
292 if (!context->x_overlay_window) {
293 return;
294 }
295
296 RENDER_LOCK();
297
298 XMoveResizeWindow(
299 context->x_display,
300 context->x_overlay_window,
301 context->native_view_x,
302 context->native_view_y,
303 context->gl_backing_layer_width,
304 context->gl_backing_layer_height);
305
306 RENDER_UNLOCK();
307 }
308
vice_opengl_renderer_destroy_child_view(vice_opengl_renderer_context_t * context)309 void vice_opengl_renderer_destroy_child_view(vice_opengl_renderer_context_t *context)
310 {
311 XDestroyWindow(context->x_display, context->x_overlay_window);
312 }
313
isExtensionSupported(const char * extList,const char * extension)314 static bool isExtensionSupported(const char *extList, const char *extension)
315 {
316 const char *start;
317 const char *where, *terminator;
318
319 /* Extension names should not have spaces. */
320 where = strchr(extension, ' ');
321 if (where || *extension == '\0') {
322 return false;
323 }
324
325 /* It takes a bit of care to be fool-proof about parsing the
326 OpenGL extensions string. Don't be fooled by sub-strings,
327 etc. */
328 for (start=extList;;) {
329 where = strstr(start, extension);
330
331 if (!where) {
332 break;
333 }
334
335 terminator = where + strlen(extension);
336
337 if (where == start || *(where - 1) == ' ') {
338 if (*terminator == ' ' || *terminator == '\0') {
339 return true;
340 }
341 }
342
343 start = terminator;
344 }
345
346 return false;
347 }
348