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