ntdll: Add a helper for platform-specific threading initialization.
[wine.git] / dlls / winemac.drv / cocoa_opengl.m
blob96e27b76e5de6177c6d433d8b34215493e441152
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_event.h"
29 @interface WineOpenGLContext ()
30 @property (retain, nonatomic) NSView* latentView;
32     + (NSView*) dummyView;
33     - (void) wine_updateBackingSize:(const CGSize*)size;
35 @end
38 @implementation WineOpenGLContext
39 @synthesize latentView, needsUpdate, needsReattach, shouldClearToBlack;
41     - (void) dealloc
42     {
43         [[self view] release];
44         [latentView release];
45         [super dealloc];
46     }
48     + (NSView*) dummyView
49     {
50         static NSWindow* dummyWindow;
51         static dispatch_once_t once;
53         dispatch_once(&once, ^{
54             OnMainThread(^{
55                 dummyWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
56                                                           styleMask:NSBorderlessWindowMask
57                                                             backing:NSBackingStoreBuffered
58                                                               defer:NO];
59             });
60         });
62         return dummyWindow.contentView;
63     }
65     // Normally, we take care that disconnecting a context from a view doesn't
66     // destroy that view's GL surface (see -clearDrawableLeavingSurfaceOnScreen).
67     // However, if we're using a surface backing size and that size changes, we
68     // need to destroy and recreate the surface or we get weird behavior.
69     - (void) resetSurfaceIfBackingSizeChanged
70     {
71         if (!retina_enabled)
72             return;
74         int view_backing[2];
75         if (macdrv_get_view_backing_size((macdrv_view)self.view, view_backing) &&
76             (view_backing[0] != backing_size[0] || view_backing[1] != backing_size[1]))
77         {
78             view_backing[0] = backing_size[0];
79             view_backing[1] = backing_size[1];
80             macdrv_set_view_backing_size((macdrv_view)self.view, view_backing);
82             NSView* save = self.view;
83             OnMainThread(^{
84                 [super clearDrawable];
85                 [super setView:save];
86             });
87             shouldClearToBlack = TRUE;
88         }
89     }
91     - (void) wine_updateBackingSize:(const CGSize*)size
92     {
93         GLint enabled;
95         if (!retina_enabled)
96             return;
98         if (size)
99         {
100             if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) != kCGLNoError)
101                 enabled = 0;
103             if (!enabled || backing_size[0] != size->width || backing_size[1] != size->height)
104             {
105                 backing_size[0] = size->width;
106                 backing_size[1] = size->height;
107                 CGLSetParameter(self.CGLContextObj, kCGLCPSurfaceBackingSize, backing_size);
108             }
110             if (!enabled)
111                 CGLEnable(self.CGLContextObj, kCGLCESurfaceBackingSize);
113             [self resetSurfaceIfBackingSizeChanged];
114         }
115         else
116         {
117             backing_size[0] = 0;
118             backing_size[1] = 0;
120             if (CGLIsEnabled(self.CGLContextObj, kCGLCESurfaceBackingSize, &enabled) == kCGLNoError && enabled)
121                CGLDisable(self.CGLContextObj, kCGLCESurfaceBackingSize);
122         }
123     }
125     - (void) setView:(NSView*)newView
126     {
127         NSView* oldView = [self view];
128         if ([NSThread isMainThread])
129             [super setView:newView];
130         else OnMainThread(^{
131             [super setView:newView];
132         });
133         [newView retain];
134         [oldView release];
135     }
137     - (void) clearDrawable
138     {
139         NSView* oldView = [self view];
140         if ([NSThread isMainThread])
141             [super clearDrawable];
142         else OnMainThread(^{
143             [super clearDrawable];
144         });
145         [oldView release];
147         [self wine_updateBackingSize:NULL];
148     }
150     /* On at least some versions of Mac OS X, -[NSOpenGLContext clearDrawable] has the
151        undesirable side effect of ordering the view's GL surface off-screen.  This isn't
152        done when just changing the context's view to a different view (which I would
153        think would be analogous, since the old view and surface end up without a
154        context attached).  So, we finesse things by first setting the context's view to
155        a different view (the content view of an off-screen window) and then letting the
156        original implementation proceed. */
157     - (void) clearDrawableLeavingSurfaceOnScreen
158     {
159         [self setView:[[self class] dummyView]];
160         [self clearDrawable];
161     }
163     - (void) clearToBlackIfNeeded
164     {
165         if (shouldClearToBlack)
166         {
167             NSOpenGLContext* origContext = [NSOpenGLContext currentContext];
168             const char *gl_version;
169             unsigned int major;
170             GLint draw_framebuffer_binding, draw_buffer;
171             GLboolean scissor_test, color_mask[4];
172             GLfloat clear_color[4];
174             [self makeCurrentContext];
176             gl_version = (const char *)glGetString(GL_VERSION);
177             major = gl_version[0] - '0';
178             /* FIXME: Should check for GL_ARB_framebuffer_object and GL_EXT_framebuffer_object
179              * for older GL versions. */
180             if (major >= 3)
181             {
182                 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer_binding);
183                 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
184             }
185             glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer);
186             scissor_test = glIsEnabled(GL_SCISSOR_TEST);
187             glGetBooleanv(GL_COLOR_WRITEMASK, color_mask);
188             glGetFloatv(GL_COLOR_CLEAR_VALUE, clear_color);
189             glDrawBuffer(GL_FRONT_AND_BACK);
190             glDisable(GL_SCISSOR_TEST);
191             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
192             glClearColor(0, 0, 0, gl_surface_mode == GL_SURFACE_IN_FRONT_TRANSPARENT ? 0 : 1);
194             glClear(GL_COLOR_BUFFER_BIT);
196             glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
197             glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
198             if (scissor_test)
199                 glEnable(GL_SCISSOR_TEST);
200             glDrawBuffer(draw_buffer);
201             if (major >= 3)
202                 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer_binding);
203             glFlush();
205             if (origContext)
206                 [origContext makeCurrentContext];
207             else
208                 [NSOpenGLContext clearCurrentContext];
210             shouldClearToBlack = FALSE;
211         }
212     }
214     - (void) removeFromViews:(BOOL)removeViews
215     {
216         if ([self view])
217         {
218             macdrv_remove_view_opengl_context((macdrv_view)[self view], (macdrv_opengl_context)self);
219             if (removeViews)
220                 [self clearDrawableLeavingSurfaceOnScreen];
221         }
222         if ([self latentView])
223         {
224             macdrv_remove_view_opengl_context((macdrv_view)[self latentView], (macdrv_opengl_context)self);
225             if (removeViews)
226                 [self setLatentView:nil];
227         }
228         needsUpdate = FALSE;
229         needsReattach = FALSE;
230     }
232 @end
235 /***********************************************************************
236  *              macdrv_create_opengl_context
238  * Returns a Cocoa OpenGL context created from a CoreGL context.  The
239  * caller is responsible for calling macdrv_dispose_opengl_context()
240  * when done with the context object.
241  */
242 macdrv_opengl_context macdrv_create_opengl_context(void* cglctx)
244     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
245     WineOpenGLContext *context;
247     context = [[WineOpenGLContext alloc] initWithCGLContextObj:cglctx];
249     [pool release];
250     return (macdrv_opengl_context)context;
253 /***********************************************************************
254  *              macdrv_dispose_opengl_context
256  * Destroys a Cocoa OpenGL context previously created by
257  * macdrv_create_opengl_context();
258  */
259 void macdrv_dispose_opengl_context(macdrv_opengl_context c)
261     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
262     WineOpenGLContext *context = (WineOpenGLContext*)c;
264     [context removeFromViews:YES];
265     [context release];
267     [pool release];
270 /***********************************************************************
271  *              macdrv_make_context_current
272  */
273 void macdrv_make_context_current(macdrv_opengl_context c, macdrv_view v, CGRect r)
275     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
276     WineOpenGLContext *context = (WineOpenGLContext*)c;
277     NSView* view = (NSView*)v;
279     if (context && view)
280     {
281         if (view == [context view] || view == [context latentView])
282         {
283             [context wine_updateBackingSize:&r.size];
284             macdrv_update_opengl_context(c);
285         }
286         else
287         {
288             [context removeFromViews:NO];
289             macdrv_add_view_opengl_context(v, c);
291             if (context.needsUpdate)
292             {
293                 context.needsUpdate = FALSE;
294                 context.needsReattach = FALSE;
295                 if (context.view)
296                     [context setView:[[context class] dummyView]];
297                 [context wine_updateBackingSize:&r.size];
298                 [context setView:view];
299                 [context setLatentView:nil];
300                 [context resetSurfaceIfBackingSizeChanged];
301             }
302             else
303             {
304                 if ([context view])
305                     [context clearDrawableLeavingSurfaceOnScreen];
306                 [context wine_updateBackingSize:&r.size];
307                 [context setLatentView:view];
308             }
309         }
311         [context makeCurrentContext];
313         if ([context view])
314             [context clearToBlackIfNeeded];
315     }
316     else
317     {
318         WineOpenGLContext* currentContext = (WineOpenGLContext*)[WineOpenGLContext currentContext];
320         if ([currentContext isKindOfClass:[WineOpenGLContext class]])
321         {
322             [WineOpenGLContext clearCurrentContext];
323             if (currentContext != context)
324                 [currentContext removeFromViews:YES];
325         }
327         if (context)
328             [context removeFromViews:YES];
329     }
331     [pool release];
334 /***********************************************************************
335  *              macdrv_update_opengl_context
336  */
337 void macdrv_update_opengl_context(macdrv_opengl_context c)
339     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
340     WineOpenGLContext *context = (WineOpenGLContext*)c;
342     if (context.needsUpdate)
343     {
344         BOOL reattach = context.needsReattach;
345         context.needsUpdate = FALSE;
346         context.needsReattach = FALSE;
347         if (context.latentView)
348         {
349             [context setView:context.latentView];
350             context.latentView = nil;
352             [context resetSurfaceIfBackingSizeChanged];
353             [context clearToBlackIfNeeded];
354         }
355         else
356         {
357             if (reattach)
358             {
359                 NSView* view = [[context.view retain] autorelease];
360                 [context clearDrawableLeavingSurfaceOnScreen];
361                 context.view = view;
362             }
363             else OnMainThread(^{
364                 [context update];
365             });
366             [context resetSurfaceIfBackingSizeChanged];
367         }
368     }
370     [pool release];
373 /***********************************************************************
374  *              macdrv_flush_opengl_context
376  * Performs an implicit glFlush() and then swaps the back buffer to the
377  * front (if the context is double-buffered).
378  */
379 void macdrv_flush_opengl_context(macdrv_opengl_context c)
381     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
382     WineOpenGLContext *context = (WineOpenGLContext*)c;
384     macdrv_update_opengl_context(c);
385     [context flushBuffer];
387     [pool release];