winemac.drv: Update OpenGL context immediately after the window content view is visible.
[wine.git] / dlls / winemac.drv / cocoa_opengl.m
blobfa69be7d927add683e047afb06246328d9ea2cc6
1 /*
2  * MACDRV Cocoa OpenGL code
3  *
4  * Copyright 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #define GL_SILENCE_DEPRECATION
22 #include <OpenGL/gl.h>
23 #import "cocoa_opengl.h"
25 #include "macdrv_cocoa.h"
26 #include "cocoa_app.h"
27 #include "cocoa_event.h"
29 #pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
32 @interface WineOpenGLContext ()
33 @property (retain, nonatomic) NSView* latentView;
35     + (NSView*) dummyView;
36     - (void) wine_updateBackingSize:(const CGSize*)size;
38 @end
41 @implementation WineOpenGLContext
42 @synthesize latentView, needsUpdate, needsReattach, shouldClearToBlack;
44     - (void) dealloc
45     {
46         [[self view] release];
47         [latentView release];
48         [super dealloc];
49     }
51     + (NSView*) dummyView
52     {
53         static NSWindow* dummyWindow;
54         static dispatch_once_t once;
56         dispatch_once(&once, ^{
57             OnMainThread(^{
58                 dummyWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
59                                                           styleMask:NSWindowStyleMaskBorderless
60                                                             backing:NSBackingStoreBuffered
61                                                               defer:NO];
62             });
63         });
65         return dummyWindow.contentView;
66     }
68     // Normally, we take care that disconnecting a context from a view doesn't
69     // destroy that view's GL surface (see -clearDrawableLeavingSurfaceOnScreen).
70     // However, if we're using a surface backing size and that size changes, we
71     // need to destroy and recreate the surface or we get weird behavior.
72     - (void) resetSurfaceIfBackingSizeChanged
73     {
74         if (!retina_enabled)
75             return;
77         int view_backing[2];
78         if (macdrv_get_view_backing_size((macdrv_view)self.view, view_backing) &&
79             (view_backing[0] != backing_size[0] || view_backing[1] != backing_size[1]))
80         {
81             view_backing[0] = backing_size[0];
82             view_backing[1] = backing_size[1];
83             macdrv_set_view_backing_size((macdrv_view)self.view, view_backing);
85             NSView* save = self.view;
86             if ([NSThread isMainThread])
87             {
88                 [super clearDrawable];
89                 [super setView:save];
90             }
91             else OnMainThread(^{
92                 [super clearDrawable];
93                 [super setView:save];
94             });
95             shouldClearToBlack = TRUE;
96         }
97     }
99     - (void) wine_updateBackingSize:(const CGSize*)size
100     {
101         GLint enabled;
103         if (!retina_enabled)
104             return;
106         if (size)
107         {
108             if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) != kCGLNoError)
109                 enabled = 0;
111             if (!enabled || backing_size[0] != size->width || backing_size[1] != size->height)
112             {
113                 backing_size[0] = size->width;
114                 backing_size[1] = size->height;
115                 CGLSetParameter(self.CGLContextObj, kCGLCPSurfaceBackingSize, backing_size);
116             }
118             if (!enabled)
119                 CGLEnable(self.CGLContextObj, kCGLCESurfaceBackingSize);
121             [self resetSurfaceIfBackingSizeChanged];
122         }
123         else
124         {
125             backing_size[0] = 0;
126             backing_size[1] = 0;
128             if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) == kCGLNoError && enabled)
129                CGLDisable(self.CGLContextObj, kCGLCESurfaceBackingSize);
130         }
131     }
133     - (void) setView:(NSView*)newView
134     {
135         NSView* oldView = [self view];
136         if ([NSThread isMainThread])
137             [super setView:newView];
138         else OnMainThread(^{
139             [super setView:newView];
140         });
141         [newView retain];
142         [oldView release];
143     }
145     - (void) clearDrawable
146     {
147         NSView* oldView = [self view];
148         if ([NSThread isMainThread])
149             [super clearDrawable];
150         else OnMainThread(^{
151             [super clearDrawable];
152         });
153         [oldView release];
155         [self wine_updateBackingSize:NULL];
156     }
158     /* On at least some versions of Mac OS X, -[NSOpenGLContext clearDrawable] has the
159        undesirable side effect of ordering the view's GL surface off-screen.  This isn't
160        done when just changing the context's view to a different view (which I would
161        think would be analogous, since the old view and surface end up without a
162        context attached).  So, we finesse things by first setting the context's view to
163        a different view (the content view of an off-screen window) and then letting the
164        original implementation proceed. */
165     - (void) clearDrawableLeavingSurfaceOnScreen
166     {
167         [self setView:[[self class] dummyView]];
168         [self clearDrawable];
169     }
171     - (void) clearToBlackIfNeeded
172     {
173         if (shouldClearToBlack)
174         {
175             NSOpenGLContext* origContext = [NSOpenGLContext currentContext];
176             const char *gl_version;
177             unsigned int major;
178             GLint draw_framebuffer_binding, draw_buffer;
179             GLboolean scissor_test, color_mask[4];
180             GLfloat clear_color[4];
182             [self makeCurrentContext];
184             gl_version = (const char *)glGetString(GL_VERSION);
185             major = gl_version[0] - '0';
186             /* FIXME: Should check for GL_ARB_framebuffer_object and GL_EXT_framebuffer_object
187              * for older GL versions. */
188             if (major >= 3)
189             {
190                 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer_binding);
191                 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
192             }
193             glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer);
194             scissor_test = glIsEnabled(GL_SCISSOR_TEST);
195             glGetBooleanv(GL_COLOR_WRITEMASK, color_mask);
196             glGetFloatv(GL_COLOR_CLEAR_VALUE, clear_color);
197             glDrawBuffer(GL_FRONT_AND_BACK);
198             glDisable(GL_SCISSOR_TEST);
199             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
200             glClearColor(0, 0, 0, gl_surface_mode == GL_SURFACE_IN_FRONT_TRANSPARENT ? 0 : 1);
202             glClear(GL_COLOR_BUFFER_BIT);
204             glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
205             glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
206             if (scissor_test)
207                 glEnable(GL_SCISSOR_TEST);
208             glDrawBuffer(draw_buffer);
209             if (major >= 3)
210                 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer_binding);
211             glFlush();
213             if (origContext)
214                 [origContext makeCurrentContext];
215             else
216                 [NSOpenGLContext clearCurrentContext];
218             shouldClearToBlack = FALSE;
219         }
220     }
222     - (void) removeFromViews:(BOOL)removeViews
223     {
224         if ([self view])
225         {
226             macdrv_remove_view_opengl_context((macdrv_view)[self view], (macdrv_opengl_context)self);
227             if (removeViews)
228                 [self clearDrawableLeavingSurfaceOnScreen];
229         }
230         if ([self latentView])
231         {
232             macdrv_remove_view_opengl_context((macdrv_view)[self latentView], (macdrv_opengl_context)self);
233             if (removeViews)
234                 [self setLatentView:nil];
235         }
236         needsUpdate = FALSE;
237         needsReattach = FALSE;
238     }
240 @end
243 /***********************************************************************
244  *              macdrv_create_opengl_context
246  * Returns a Cocoa OpenGL context created from a CoreGL context.  The
247  * caller is responsible for calling macdrv_dispose_opengl_context()
248  * when done with the context object.
249  */
250 macdrv_opengl_context macdrv_create_opengl_context(void* cglctx)
252 @autoreleasepool
254     WineOpenGLContext *context;
256     context = [[WineOpenGLContext alloc] initWithCGLContextObj:cglctx];
258     return (macdrv_opengl_context)context;
262 /***********************************************************************
263  *              macdrv_dispose_opengl_context
265  * Destroys a Cocoa OpenGL context previously created by
266  * macdrv_create_opengl_context();
267  */
268 void macdrv_dispose_opengl_context(macdrv_opengl_context c)
270 @autoreleasepool
272     WineOpenGLContext *context = (WineOpenGLContext*)c;
274     [context removeFromViews:YES];
275     [context release];
279 /***********************************************************************
280  *              macdrv_make_context_current
281  */
282 void macdrv_make_context_current(macdrv_opengl_context c, macdrv_view v, CGRect r)
284 @autoreleasepool
286     WineOpenGLContext *context = (WineOpenGLContext*)c;
287     NSView* view = (NSView*)v;
289     if (context && view)
290     {
291         if (view == [context view] || view == [context latentView])
292         {
293             [context wine_updateBackingSize:&r.size];
294             macdrv_update_opengl_context(c);
295         }
296         else
297         {
298             [context removeFromViews:NO];
299             macdrv_add_view_opengl_context(v, c);
301             if (context.needsUpdate)
302             {
303                 context.needsUpdate = FALSE;
304                 context.needsReattach = FALSE;
305                 if (context.view)
306                     [context setView:[[context class] dummyView]];
307                 [context wine_updateBackingSize:&r.size];
308                 [context setView:view];
309                 [context setLatentView:nil];
310                 [context resetSurfaceIfBackingSizeChanged];
311             }
312             else
313             {
314                 if ([context view])
315                     [context clearDrawableLeavingSurfaceOnScreen];
316                 [context wine_updateBackingSize:&r.size];
317                 [context setLatentView:view];
318             }
319         }
321         [context makeCurrentContext];
323         if ([context view])
324             [context clearToBlackIfNeeded];
325     }
326     else
327     {
328         WineOpenGLContext* currentContext = (WineOpenGLContext*)[WineOpenGLContext currentContext];
330         if ([currentContext isKindOfClass:[WineOpenGLContext class]])
331         {
332             [WineOpenGLContext clearCurrentContext];
333             if (currentContext != context)
334                 [currentContext removeFromViews:YES];
335         }
337         if (context)
338             [context removeFromViews:YES];
339     }
343 /***********************************************************************
344  *              macdrv_update_opengl_context
345  */
346 void macdrv_update_opengl_context(macdrv_opengl_context c)
348 @autoreleasepool
350     WineOpenGLContext *context = (WineOpenGLContext*)c;
352     if (context.needsUpdate)
353     {
354         BOOL reattach = context.needsReattach;
355         context.needsUpdate = FALSE;
356         context.needsReattach = FALSE;
357         if (context.latentView)
358         {
359             [context setView:context.latentView];
360             context.latentView = nil;
362             [context resetSurfaceIfBackingSizeChanged];
363             [context clearToBlackIfNeeded];
364         }
365         else
366         {
367             if (reattach)
368             {
369                 NSView* view = [[context.view retain] autorelease];
370                 [context clearDrawableLeavingSurfaceOnScreen];
371                 context.view = view;
372             }
373             else OnMainThread(^{
374                 [context update];
375             });
376             [context resetSurfaceIfBackingSizeChanged];
377         }
378     }
382 /***********************************************************************
383  *              macdrv_flush_opengl_context
385  * Performs an implicit glFlush() and then swaps the back buffer to the
386  * front (if the context is double-buffered).
387  */
388 void macdrv_flush_opengl_context(macdrv_opengl_context c)
390 @autoreleasepool
392     WineOpenGLContext *context = (WineOpenGLContext*)c;
394     macdrv_update_opengl_context(c);
395     [context flushBuffer];