Fix T58466: bug in macOS GHOST_GetSwapInterval.
[blender.git] / intern / ghost / intern / GHOST_ContextCGL.mm
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2013 Blender Foundation.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Jason Wilkins
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file ghost/intern/GHOST_ContextCGL.mm
29  *  \ingroup GHOST
30  *
31  * Definition of GHOST_ContextCGL class.
32  */
33
34 #include "GHOST_ContextCGL.h"
35
36 #include <Cocoa/Cocoa.h>
37
38 #ifdef GHOST_MULTITHREADED_OPENGL
39 #include <OpenGL/OpenGL.h>
40 #endif
41
42 #include <vector>
43 #include <cassert>
44
45
46 NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
47 int              GHOST_ContextCGL::s_sharedCount         = 0;
48
49
50 GHOST_ContextCGL::GHOST_ContextCGL(
51         bool stereoVisual,
52         GHOST_TUns16  numOfAASamples,
53         NSWindow *window,
54         NSOpenGLView *openGLView,
55         int contextProfileMask,
56         int contextMajorVersion,
57         int contextMinorVersion,
58         int contextFlags,
59         int contextResetNotificationStrategy)
60     : GHOST_Context(stereoVisual, numOfAASamples),
61       m_openGLView(openGLView),
62       m_openGLContext(nil),
63       m_debug(contextFlags)
64 {
65         assert(openGLView != nil);
66 }
67
68
69 GHOST_ContextCGL::~GHOST_ContextCGL()
70 {
71         if (m_openGLContext != nil) {
72                 if (m_openGLContext == [NSOpenGLContext currentContext]) {
73                         [NSOpenGLContext clearCurrentContext];
74                         [m_openGLView clearGLContext];
75                 }
76
77                 if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) {
78                         assert(s_sharedCount > 0);
79
80                         s_sharedCount--;
81
82                         if (s_sharedCount == 0)
83                                 s_sharedOpenGLContext = nil;
84
85                         [m_openGLContext release];
86                 }
87         }
88 }
89
90
91 GHOST_TSuccess GHOST_ContextCGL::swapBuffers()
92 {
93         if (m_openGLContext != nil) {
94                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
95                 [m_openGLContext flushBuffer];
96                 [pool drain];
97                 return GHOST_kSuccess;
98         }
99         else {
100                 return GHOST_kFailure;
101         }
102 }
103
104
105 GHOST_TSuccess GHOST_ContextCGL::setSwapInterval(int interval)
106 {
107         if (m_openGLContext != nil) {
108                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
109                 [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval];
110                 [pool drain];
111                 return GHOST_kSuccess;
112         }
113         else {
114                 return GHOST_kFailure;
115         }
116 }
117
118
119 GHOST_TSuccess GHOST_ContextCGL::getSwapInterval(int &intervalOut)
120 {
121         if (m_openGLContext != nil) {
122                 GLint interval;
123
124                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
125
126                 [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval];
127
128                 [pool drain];
129
130                 intervalOut = static_cast<int>(interval);
131
132                 return GHOST_kSuccess;
133         }
134         else {
135                 return GHOST_kFailure;
136         }
137 }
138
139
140 GHOST_TSuccess GHOST_ContextCGL::activateDrawingContext()
141 {
142         if (m_openGLContext != nil) {
143                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
144                 [m_openGLContext makeCurrentContext];
145
146                 activateGLEW();
147
148                 [pool drain];
149                 return GHOST_kSuccess;
150         }
151         else {
152                 return GHOST_kFailure;
153         }
154 }
155
156
157 GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext()
158 {
159         if (m_openGLContext != nil) {
160                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
161                 [m_openGLContext update];
162                 [pool drain];
163                 return GHOST_kSuccess;
164         }
165         else {
166                 return GHOST_kFailure;
167         }
168 }
169
170
171 static void makeAttribList(
172         std::vector<NSOpenGLPixelFormatAttribute>& attribs,
173         bool stereoVisual,
174         int numOfAASamples,
175         bool needAlpha,
176         bool needStencil,
177         bool softwareGL)
178 {
179         attribs.clear();
180
181         // Pixel Format Attributes for the windowed NSOpenGLContext
182         attribs.push_back(NSOpenGLPFADoubleBuffer);
183
184         if (softwareGL) {
185                 attribs.push_back(NSOpenGLPFARendererID);
186                 attribs.push_back(kCGLRendererGenericFloatID);
187         }
188         else {
189                 attribs.push_back(NSOpenGLPFAAccelerated);
190                 attribs.push_back(NSOpenGLPFANoRecovery);
191         }
192
193         /* Removed to allow 10.4 builds, and 2 GPUs rendering is not used anyway */
194         //attribs.push_back(NSOpenGLPFAAllowOfflineRenderers);
195
196         attribs.push_back(NSOpenGLPFADepthSize);
197         attribs.push_back((NSOpenGLPixelFormatAttribute) 32);
198
199         attribs.push_back(NSOpenGLPFAAccumSize);
200         attribs.push_back((NSOpenGLPixelFormatAttribute) 32);
201
202         if (stereoVisual)
203                 attribs.push_back(NSOpenGLPFAStereo);
204
205         if (needAlpha) {
206                 attribs.push_back(NSOpenGLPFAAlphaSize);
207                 attribs.push_back((NSOpenGLPixelFormatAttribute) 8);
208         }
209
210         if (needStencil) {
211                 attribs.push_back(NSOpenGLPFAStencilSize);
212                 attribs.push_back((NSOpenGLPixelFormatAttribute) 8);
213         }
214
215         if (numOfAASamples > 0) {
216                 // Multisample anti-aliasing
217                 attribs.push_back(NSOpenGLPFAMultisample);
218
219                 attribs.push_back(NSOpenGLPFASampleBuffers);
220                 attribs.push_back((NSOpenGLPixelFormatAttribute) 1);
221
222                 attribs.push_back(NSOpenGLPFASamples);
223                 attribs.push_back((NSOpenGLPixelFormatAttribute) numOfAASamples);
224         }
225
226         attribs.push_back((NSOpenGLPixelFormatAttribute) 0);
227 }
228
229 // TODO(merwin): make this available to all platforms
230 static void getVersion(int *major, int *minor)
231 {
232 #if 1 // legacy GL
233         sscanf((const char*)glGetString(GL_VERSION), "%d.%d", major, minor);
234 #else // 3.0+
235         glGetIntegerv(GL_MAJOR_VERSION, major);
236         glGetIntegerv(GL_MINOR_VERSION, minor);
237 #endif
238 }
239
240 GHOST_TSuccess GHOST_ContextCGL::initializeDrawingContext()
241 {
242         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
243
244         std::vector<NSOpenGLPixelFormatAttribute> attribs;
245         attribs.reserve(40);
246
247         NSOpenGLContext *prev_openGLContext = [m_openGLView openGLContext];
248
249 #ifdef GHOST_OPENGL_ALPHA
250         static const bool needAlpha   = true;
251 #else
252         static const bool needAlpha   = false;
253 #endif
254
255 #ifdef GHOST_OPENGL_STENCIL
256         static const bool needStencil = true;
257 #else
258         static const bool needStencil = false;
259 #endif
260
261         static bool softwareGL = getenv("BLENDER_SOFTWAREGL"); // command-line argument would be better
262         GLint major = 0, minor = 0;
263         NSOpenGLPixelFormat *pixelFormat;
264         // TODO: keep pixel format for subsequent windows/contexts instead of recreating each time
265
266         makeAttribList(attribs, m_stereoVisual, m_numOfAASamples, needAlpha, needStencil, softwareGL);
267
268         pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
269
270         // Fall back to no multisampling if Antialiasing init failed
271         if (m_numOfAASamples > 0 && pixelFormat == nil) {
272                 // XXX jwilkins: Does CGL only succeed when it makes an exact match on the number of samples?
273                 // Does this need to explicitly try for a lesser match before giving up?
274                 // (Now that I think about it, does WGL really require the code that it has for finding a lesser match?)
275
276                 attribs.clear();
277                 makeAttribList(attribs, m_stereoVisual, 0, needAlpha, needStencil, softwareGL);
278                 pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
279         }
280
281         if (pixelFormat == nil)
282                 goto error;
283
284         if (m_numOfAASamples > 0) { //Set m_numOfAASamples to the actual value
285                 GLint actualSamples;
286                 [pixelFormat getValues:&actualSamples forAttribute:NSOpenGLPFASamples forVirtualScreen:0];
287
288                 if (m_numOfAASamples != (GHOST_TUns16)actualSamples) {
289                         fprintf(stderr,
290                                 "Warning! Unable to find a multisample pixel format that supports exactly %d samples. "
291                                 "Substituting one that uses %d samples.\n",
292                                 m_numOfAASamples, actualSamples);
293
294                         m_numOfAASamples = (GHOST_TUns16)actualSamples;
295                 }
296         }
297
298         m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:s_sharedOpenGLContext];
299         [pixelFormat release];
300
301         [m_openGLContext makeCurrentContext];
302
303         getVersion(&major, &minor);
304         if (m_debug) {
305                 fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
306                 fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
307         }
308
309         if (major < 2 || (major == 2 && minor < 1)) {
310                 // fall back to software renderer if GL < 2.1
311                 fprintf(stderr, "OpenGL 2.1 is not supported on your hardware, falling back to software");
312                 softwareGL = true;
313
314                 // discard hardware GL context
315                 [NSOpenGLContext clearCurrentContext];
316                 [m_openGLContext release];
317
318                 // create software GL context
319                 makeAttribList(attribs, m_stereoVisual, m_numOfAASamples, needAlpha, needStencil, softwareGL);
320                 pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
321                 m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:s_sharedOpenGLContext];
322                 [pixelFormat release];
323
324                 [m_openGLContext makeCurrentContext];
325
326                 getVersion(&major, &minor);
327                 if (m_debug) {
328                         fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
329                         fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
330                 }
331         }
332
333 #ifdef GHOST_MULTITHREADED_OPENGL
334         //Switch openGL to multhreaded mode
335         CGLContextObj cglCtx = (CGLContextObj)[tmpOpenGLContext CGLContextObj];
336         if (CGLEnable(cglCtx, kCGLCEMPEngine) == kCGLNoError)
337                 if (m_debug)
338                         fprintf(stderr, "\nSwitched OpenGL to multithreaded mode\n");
339 #endif
340
341 #ifdef GHOST_WAIT_FOR_VSYNC
342         {
343                 GLint swapInt = 1;
344                 /* wait for vsync, to avoid tearing artifacts */
345                 [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
346         }
347 #endif
348
349         initContextGLEW();
350
351         [m_openGLView setOpenGLContext:m_openGLContext];
352         [m_openGLContext setView:m_openGLView];
353
354         if (s_sharedCount == 0)
355                 s_sharedOpenGLContext = m_openGLContext;
356         
357         s_sharedCount++;
358
359
360         initClearGL();
361         [m_openGLContext flushBuffer];
362
363         [pool drain];
364
365         return GHOST_kSuccess;
366
367 error:
368
369         [m_openGLView setOpenGLContext:prev_openGLContext];
370         [pixelFormat release];
371
372         [pool drain];
373
374         return GHOST_kFailure;
375 }
376
377
378 GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles()
379 {
380         m_openGLContext = NULL;
381         m_openGLView    = NULL;
382
383         return GHOST_kSuccess;
384 }