Fix T71554: 'Hide Unselected' not working for certain selections
[blender.git] / intern / ghost / intern / GHOST_ContextCGL.mm
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2013 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file ghost/intern/GHOST_ContextCGL.mm
21  *  \ingroup GHOST
22  *
23  * Definition of GHOST_ContextCGL class.
24  */
25
26 #include "GHOST_ContextCGL.h"
27
28 #include <Cocoa/Cocoa.h>
29 #include <QuartzCore/QuartzCore.h>
30 #include <Metal/Metal.h>
31
32 #include <vector>
33 #include <cassert>
34
35 static void ghost_fatal_error_dialog(const char *msg)
36 {
37   @autoreleasepool {
38     NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
39
40     NSAlert *alert = [[NSAlert alloc] init];
41     [alert addButtonWithTitle:@"Quit"];
42     [alert setMessageText:@"Blender"];
43     [alert setInformativeText:message];
44     [alert setAlertStyle:NSAlertStyleCritical];
45     [alert runModal];
46   }
47
48   exit(1);
49 }
50
51 NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
52 int GHOST_ContextCGL::s_sharedCount = 0;
53
54 GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
55                                    NSView *metalView,
56                                    CAMetalLayer *metalLayer,
57                                    NSOpenGLView *openGLView)
58     : GHOST_Context(stereoVisual),
59       m_metalView(metalView),
60       m_metalLayer(metalLayer),
61       m_metalCmdQueue(nil),
62       m_metalRenderPipeline(nil),
63       m_openGLView(openGLView),
64       m_openGLContext(nil),
65       m_defaultFramebuffer(0),
66       m_defaultFramebufferMetalTexture(nil),
67       m_debug(false)
68 {
69 #if defined(WITH_GL_PROFILE_CORE)
70   m_coreProfile = true;
71 #else
72   m_coreProfile = false;
73 #endif
74
75   if (m_metalView) {
76     metalInit();
77   }
78 }
79
80 GHOST_ContextCGL::~GHOST_ContextCGL()
81 {
82   metalFree();
83
84   if (m_openGLContext != nil) {
85     if (m_openGLContext == [NSOpenGLContext currentContext]) {
86       [NSOpenGLContext clearCurrentContext];
87
88       if (m_openGLView) {
89         [m_openGLView clearGLContext];
90       }
91     }
92
93     if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) {
94       assert(s_sharedCount > 0);
95
96       s_sharedCount--;
97
98       if (s_sharedCount == 0)
99         s_sharedOpenGLContext = nil;
100
101       [m_openGLContext release];
102     }
103   }
104 }
105
106 GHOST_TSuccess GHOST_ContextCGL::swapBuffers()
107 {
108   if (m_openGLContext != nil) {
109     if (m_metalView) {
110       metalSwapBuffers();
111     }
112     else if (m_openGLView) {
113       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
114       [m_openGLContext flushBuffer];
115       [pool drain];
116     }
117     return GHOST_kSuccess;
118   }
119   else {
120     return GHOST_kFailure;
121   }
122 }
123
124 GHOST_TSuccess GHOST_ContextCGL::setSwapInterval(int interval)
125 {
126   if (m_openGLContext != nil) {
127     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
128     [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval];
129     [pool drain];
130     return GHOST_kSuccess;
131   }
132   else {
133     return GHOST_kFailure;
134   }
135 }
136
137 GHOST_TSuccess GHOST_ContextCGL::getSwapInterval(int &intervalOut)
138 {
139   if (m_openGLContext != nil) {
140     GLint interval;
141
142     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
143
144     [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval];
145
146     [pool drain];
147
148     intervalOut = static_cast<int>(interval);
149
150     return GHOST_kSuccess;
151   }
152   else {
153     return GHOST_kFailure;
154   }
155 }
156
157 GHOST_TSuccess GHOST_ContextCGL::activateDrawingContext()
158 {
159   if (m_openGLContext != nil) {
160     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
161     [m_openGLContext makeCurrentContext];
162     [pool drain];
163     return GHOST_kSuccess;
164   }
165   else {
166     return GHOST_kFailure;
167   }
168 }
169
170 GHOST_TSuccess GHOST_ContextCGL::releaseDrawingContext()
171 {
172   if (m_openGLContext != nil) {
173     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
174     [NSOpenGLContext clearCurrentContext];
175     [pool drain];
176     return GHOST_kSuccess;
177   }
178   else {
179     return GHOST_kFailure;
180   }
181 }
182
183 unsigned int GHOST_ContextCGL::getDefaultFramebuffer()
184 {
185   return m_defaultFramebuffer;
186 }
187
188 GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext()
189 {
190   if (m_openGLContext != nil) {
191     if (m_metalView) {
192       metalUpdateFramebuffer();
193     }
194     else if (m_openGLView) {
195       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
196       [m_openGLContext update];
197       [pool drain];
198     }
199
200     return GHOST_kSuccess;
201   }
202   else {
203     return GHOST_kFailure;
204   }
205 }
206
207 static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
208                            bool coreProfile,
209                            bool stereoVisual,
210                            bool needAlpha,
211                            bool softwareGL)
212 {
213   attribs.clear();
214
215   attribs.push_back(NSOpenGLPFAOpenGLProfile);
216   attribs.push_back(coreProfile ? NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy);
217
218   // Pixel Format Attributes for the windowed NSOpenGLContext
219   attribs.push_back(NSOpenGLPFADoubleBuffer);
220
221   if (softwareGL) {
222     attribs.push_back(NSOpenGLPFARendererID);
223     attribs.push_back(kCGLRendererGenericFloatID);
224   }
225   else {
226     attribs.push_back(NSOpenGLPFAAccelerated);
227     attribs.push_back(NSOpenGLPFANoRecovery);
228   }
229
230   attribs.push_back(NSOpenGLPFAAllowOfflineRenderers);  // for automatic GPU switching
231
232   if (stereoVisual)
233     attribs.push_back(NSOpenGLPFAStereo);
234
235   if (needAlpha) {
236     attribs.push_back(NSOpenGLPFAAlphaSize);
237     attribs.push_back((NSOpenGLPixelFormatAttribute)8);
238   }
239
240   attribs.push_back((NSOpenGLPixelFormatAttribute)0);
241 }
242
243 GHOST_TSuccess GHOST_ContextCGL::initializeDrawingContext()
244 {
245   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
246
247 #ifdef GHOST_OPENGL_ALPHA
248   static const bool needAlpha = true;
249 #else
250   static const bool needAlpha = false;
251 #endif
252
253   static bool softwareGL = getenv("BLENDER_SOFTWAREGL");  // command-line argument would be better
254
255   std::vector<NSOpenGLPixelFormatAttribute> attribs;
256   attribs.reserve(40);
257   makeAttribList(attribs, m_coreProfile, m_stereoVisual, needAlpha, softwareGL);
258
259   NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
260   if (pixelFormat == nil) {
261     goto error;
262   }
263
264   m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
265                                                shareContext:s_sharedOpenGLContext];
266   [pixelFormat release];
267
268   [m_openGLContext makeCurrentContext];
269
270   if (m_debug) {
271     GLint major = 0, minor = 0;
272     glGetIntegerv(GL_MAJOR_VERSION, &major);
273     glGetIntegerv(GL_MINOR_VERSION, &minor);
274     fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
275     fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
276   }
277
278 #ifdef GHOST_WAIT_FOR_VSYNC
279   {
280     GLint swapInt = 1;
281     /* wait for vsync, to avoid tearing artifacts */
282     [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
283   }
284 #endif
285
286   initContextGLEW();
287
288   if (m_metalView) {
289     if (m_defaultFramebuffer == 0) {
290       // Create a virtual framebuffer
291       [m_openGLContext makeCurrentContext];
292       metalInitFramebuffer();
293       initClearGL();
294     }
295   }
296   else if (m_openGLView) {
297     [m_openGLView setOpenGLContext:m_openGLContext];
298     [m_openGLContext setView:m_openGLView];
299     initClearGL();
300   }
301
302   [m_openGLContext flushBuffer];
303
304   if (s_sharedCount == 0)
305     s_sharedOpenGLContext = m_openGLContext;
306
307   s_sharedCount++;
308
309   [pool drain];
310
311   return GHOST_kSuccess;
312
313 error:
314
315   [pixelFormat release];
316
317   [pool drain];
318
319   return GHOST_kFailure;
320 }
321
322 GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles()
323 {
324   m_openGLContext = nil;
325   m_openGLView = nil;
326   m_metalView = nil;
327
328   return GHOST_kSuccess;
329 }
330
331 /* OpenGL on Metal
332  *
333  * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */
334
335 static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm;
336 static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT = kCVPixelFormatType_32BGRA;
337
338 void GHOST_ContextCGL::metalInit()
339 {
340   @autoreleasepool {
341     id<MTLDevice> device = m_metalLayer.device;
342
343     // Create a command queue for blit/present operation
344     m_metalCmdQueue = (MTLCommandQueue *)[device newCommandQueue];
345     [m_metalCmdQueue retain];
346
347     // Create shaders for blit operation
348     NSString *source = @R"msl(
349       using namespace metal;
350
351       struct Vertex {
352         float4 position [[position]];
353         float2 texCoord [[attribute(0)]];
354       };
355
356       vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
357         Vertex vtx;
358
359         vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
360         vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
361         vtx.position.z = 0.0;
362         vtx.position.w = 1.0;
363
364         vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
365
366         return vtx;
367       }
368
369       constexpr sampler s {};
370
371       fragment float4 fragment_shader(Vertex v [[stage_in]],
372                       texture2d<float> t [[texture(0)]]) {
373         return t.sample(s, v.texCoord);
374       }
375
376       )msl";
377
378     MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
379     options.languageVersion = MTLLanguageVersion1_1;
380
381     NSError *error = nil;
382     id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
383     if (error) {
384       ghost_fatal_error_dialog(
385           "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
386     }
387
388     // Create a render pipeline for blit operation
389     MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
390
391     desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
392     desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
393
394     [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT;
395
396     m_metalRenderPipeline = (MTLRenderPipelineState *)[device
397         newRenderPipelineStateWithDescriptor:desc
398                                        error:&error];
399     if (error) {
400       ghost_fatal_error_dialog(
401           "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
402     }
403   }
404 }
405
406 void GHOST_ContextCGL::metalFree()
407 {
408   if (m_metalCmdQueue) {
409     [m_metalCmdQueue release];
410   }
411   if (m_metalRenderPipeline) {
412     [m_metalRenderPipeline release];
413   }
414   if (m_defaultFramebufferMetalTexture) {
415     [m_defaultFramebufferMetalTexture release];
416   }
417 }
418
419 void GHOST_ContextCGL::metalInitFramebuffer()
420 {
421   glGenFramebuffers(1, &m_defaultFramebuffer);
422   updateDrawingContext();
423   glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
424 }
425
426 void GHOST_ContextCGL::metalUpdateFramebuffer()
427 {
428   assert(m_defaultFramebuffer != 0);
429
430   NSRect bounds = [m_metalView bounds];
431   NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
432   size_t width = (size_t)backingSize.width;
433   size_t height = (size_t)backingSize.height;
434
435   {
436     /* Test if there is anything to update */
437     id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
438     if (tex && tex.width == width && tex.height == height) {
439       return;
440     }
441   }
442
443   activateDrawingContext();
444
445   NSDictionary *cvPixelBufferProps = @{
446     (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES,
447     (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
448   };
449   CVPixelBufferRef cvPixelBuffer = nil;
450   CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault,
451                                        width,
452                                        height,
453                                        METAL_CORE_VIDEO_PIXEL_FORMAT,
454                                        (__bridge CFDictionaryRef)cvPixelBufferProps,
455                                        &cvPixelBuffer);
456   if (cvret != kCVReturnSuccess) {
457     ghost_fatal_error_dialog(
458         "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!");
459   }
460
461   // Create an OpenGL texture
462   CVOpenGLTextureCacheRef cvGLTexCache = nil;
463   cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
464                                      nil,
465                                      m_openGLContext.CGLContextObj,
466                                      m_openGLContext.pixelFormat.CGLPixelFormatObj,
467                                      nil,
468                                      &cvGLTexCache);
469   if (cvret != kCVReturnSuccess) {
470     ghost_fatal_error_dialog(
471         "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!");
472   }
473
474   CVOpenGLTextureRef cvGLTex = nil;
475   cvret = CVOpenGLTextureCacheCreateTextureFromImage(
476       kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex);
477   if (cvret != kCVReturnSuccess) {
478     ghost_fatal_error_dialog(
479         "GHOST_ContextCGL::metalUpdateFramebuffer: "
480         "CVOpenGLTextureCacheCreateTextureFromImage failed!");
481   }
482
483   unsigned int glTex;
484   glTex = CVOpenGLTextureGetName(cvGLTex);
485
486   // Create a Metal texture
487   CVMetalTextureCacheRef cvMetalTexCache = nil;
488   cvret = CVMetalTextureCacheCreate(
489       kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache);
490   if (cvret != kCVReturnSuccess) {
491     ghost_fatal_error_dialog(
492         "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!");
493   }
494
495   CVMetalTextureRef cvMetalTex = nil;
496   cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
497                                                     cvMetalTexCache,
498                                                     cvPixelBuffer,
499                                                     nil,
500                                                     METAL_FRAMEBUFFERPIXEL_FORMAT,
501                                                     width,
502                                                     height,
503                                                     0,
504                                                     &cvMetalTex);
505   if (cvret != kCVReturnSuccess) {
506     ghost_fatal_error_dialog(
507         "GHOST_ContextCGL::metalUpdateFramebuffer: "
508         "CVMetalTextureCacheCreateTextureFromImage failed!");
509   }
510
511   MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex);
512
513   if (!tex) {
514     ghost_fatal_error_dialog(
515         "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!");
516   }
517
518   [m_defaultFramebufferMetalTexture release];
519   m_defaultFramebufferMetalTexture = [tex retain];
520
521   glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
522   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0);
523
524   [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)];
525
526   CVPixelBufferRelease(cvPixelBuffer);
527   CVOpenGLTextureCacheRelease(cvGLTexCache);
528   CVOpenGLTextureRelease(cvGLTex);
529   CFRelease(cvMetalTexCache);
530   CFRelease(cvMetalTex);
531 }
532
533 void GHOST_ContextCGL::metalSwapBuffers()
534 {
535   @autoreleasepool {
536     updateDrawingContext();
537     glFlush();
538
539     assert(m_defaultFramebufferMetalTexture != 0);
540
541     id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
542     if (!drawable) {
543       return;
544     }
545
546     id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
547
548     MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
549     {
550       auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
551       attachment.texture = drawable.texture;
552       attachment.loadAction = MTLLoadActionDontCare;
553       attachment.storeAction = MTLStoreActionStore;
554     }
555
556     id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
557
558     {
559       id<MTLRenderCommandEncoder> enc = [cmdBuffer
560           renderCommandEncoderWithDescriptor:passDescriptor];
561
562       [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline];
563       [enc setFragmentTexture:srcTexture atIndex:0];
564       [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
565
566       [enc endEncoding];
567     }
568
569     [cmdBuffer presentDrawable:drawable];
570
571     [cmdBuffer commit];
572   }
573 }