Blender V4.5
GHOST_ContextCGL.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11/* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
12 * solvable in a short term. */
13#ifdef __clang__
14# pragma clang diagnostic ignored "-Wdeprecated-declarations"
15#endif
16
17#include "GHOST_ContextCGL.hh"
18
19#import <Cocoa/Cocoa.h>
20#import <Metal/Metal.h>
21#import <QuartzCore/QuartzCore.h>
22
23#include <cassert>
24#include <vector>
25
26static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT_EDR = MTLPixelFormatRGBA16Float;
27
28static void ghost_fatal_error_dialog(const char *msg)
29{
30 @autoreleasepool {
31 NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
32
33 NSAlert *alert = [[NSAlert alloc] init];
34
35 alert.messageText = @"Blender";
36 alert.informativeText = message;
37 alert.alertStyle = NSAlertStyleCritical;
38
39 [alert addButtonWithTitle:@"Quit"];
40 [alert runModal];
41 }
42
43 exit(1);
44}
45
46MTLCommandQueue *GHOST_ContextCGL::s_sharedMetalCommandQueue = nil;
47int GHOST_ContextCGL::s_sharedCount = 0;
48
50 NSView *metalView,
51 CAMetalLayer *metalLayer,
52 int debug)
53 : GHOST_Context(stereoVisual),
54 m_metalView(metalView),
55 m_metalLayer(metalLayer),
56 m_metalRenderPipeline(nil),
57 m_debug(debug)
58{
59 @autoreleasepool {
60 /* Initialize Metal Swap-chain. */
61 current_swapchain_index = 0;
62 for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
63 m_defaultFramebufferMetalTexture[i].texture = nil;
64 m_defaultFramebufferMetalTexture[i].index = i;
65 }
66
67 if (m_metalView) {
68 m_ownsMetalDevice = false;
69 metalInit();
70 }
71 else {
72 /* Prepare offscreen GHOST Context Metal device. */
73 id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();
74
75 if (m_debug) {
76 printf("Selected Metal Device: %s\n", [metalDevice.name UTF8String]);
77 }
78
79 m_ownsMetalDevice = true;
80 if (metalDevice) {
81 m_metalLayer = [[CAMetalLayer alloc] init];
82 m_metalLayer.edgeAntialiasingMask = 0;
83 m_metalLayer.masksToBounds = NO;
84 m_metalLayer.opaque = YES;
85 m_metalLayer.framebufferOnly = YES;
86 m_metalLayer.presentsWithTransaction = NO;
87 [m_metalLayer removeAllAnimations];
88 m_metalLayer.device = metalDevice;
89 m_metalLayer.allowsNextDrawableTimeout = NO;
90
91 /* Enable EDR support. This is done by:
92 * 1. Using a floating point render target, so that values outside 0..1 can be used
93 * 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1
94 * 3. Setting the extended sRGB color space so that the OS knows how to interpret the
95 * values.
96 */
97 m_metalLayer.wantsExtendedDynamicRangeContent = YES;
98 m_metalLayer.pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT_EDR;
99 const CFStringRef name = kCGColorSpaceExtendedSRGB;
100 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
101 m_metalLayer.colorspace = colorspace;
102 CGColorSpaceRelease(colorspace);
103
104 metalInit();
105 }
106 else {
108 "[ERROR] Failed to create Metal device for offscreen GHOST Context.\n");
109 }
110 }
111
112 /* Initialize swap-interval. */
113 mtl_SwapInterval = 60;
114 }
115}
116
118{
119 metalFree();
120
121 if (m_ownsMetalDevice) {
122 if (m_metalLayer) {
123 [m_metalLayer release];
124 m_metalLayer = nil;
125 }
126 }
127 assert(s_sharedCount);
128
129 s_sharedCount--;
130 [s_sharedMetalCommandQueue release];
131 if (s_sharedCount == 0) {
132 s_sharedMetalCommandQueue = nil;
133 }
134}
135
137{
138 if (m_metalView) {
139 metalSwapBuffers();
140 }
141 return GHOST_kSuccess;
142}
143
145{
146 mtl_SwapInterval = interval;
147 return GHOST_kSuccess;
148}
149
151{
152 intervalOut = mtl_SwapInterval;
153 return GHOST_kSuccess;
154}
155
161
167
169{
170 /* NOTE(Metal): This is not valid. */
171 return 0;
172}
173
175{
176 if (m_metalView) {
177 metalUpdateFramebuffer();
178 return GHOST_kSuccess;
179 }
180 return GHOST_kFailure;
181}
182
184{
185 /* Increment Swap-chain - Only needed if context is requesting a new texture */
186 current_swapchain_index = (current_swapchain_index + 1) % METAL_SWAPCHAIN_SIZE;
187
188 /* Ensure backing texture is ready for current swapchain index */
190
191 /* Return texture. */
192 return m_defaultFramebufferMetalTexture[current_swapchain_index].texture;
193}
194
196{
197 return s_sharedMetalCommandQueue;
198}
200{
201 id<MTLDevice> device = m_metalLayer.device;
202 return (MTLDevice *)device;
203}
204
206 MTLRenderPassDescriptor *, id<MTLRenderPipelineState>, id<MTLTexture>, id<CAMetalDrawable>))
207{
208 this->contextPresentCallback = callback;
209}
210
212{
213 @autoreleasepool {
214 if (m_metalView) {
215 metalInitFramebuffer();
216 }
217 }
218 active_context_ = this;
219 return GHOST_kSuccess;
220}
221
223{
224 m_metalView = nil;
225
226 return GHOST_kSuccess;
227}
228
229void GHOST_ContextCGL::metalInit()
230{
231 @autoreleasepool {
232 id<MTLDevice> device = m_metalLayer.device;
233
234 /* Create a command queue for blit/present operation.
235 * NOTE: All context should share a single command queue
236 * to ensure correct ordering of work submitted from multiple contexts. */
237 if (s_sharedMetalCommandQueue == nil) {
238 s_sharedMetalCommandQueue = (MTLCommandQueue *)[device
239 newCommandQueueWithMaxCommandBufferCount:GHOST_ContextCGL::max_command_buffer_count];
240 }
241 /* Ensure active GHOSTContext retains a reference to the shared context. */
242 [s_sharedMetalCommandQueue retain];
243 s_sharedCount++;
244
245 /* Create shaders for blit operation. */
246 NSString *source = @R"msl(
247 using namespace metal;
248
249 struct Vertex {
250 float4 position [[position]];
251 float2 texCoord [[attribute(0)]];
252 };
253
254 vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
255 Vertex vtx;
256
257 vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
258 vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
259 vtx.position.z = 0.0;
260 vtx.position.w = 1.0;
261
262 vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
263
264 return vtx;
265 }
266
267 constexpr sampler s {};
268
269 fragment float4 fragment_shader(Vertex v [[stage_in]],
270 texture2d<float> t [[texture(0)]]) {
271
272 /* Final blit should ensure alpha is 1.0. This resolves
273 * rendering artifacts for blitting of final back-buffer. */
274 float4 out_tex = t.sample(s, v.texCoord);
275 out_tex.a = 1.0;
276 return out_tex;
277 }
278 )msl";
279
280 MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
281 options.languageVersion = MTLLanguageVersion1_1;
282
283 NSError *error = nil;
284 id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
285 if (error) {
287 "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
288 }
289
290 /* Create a render pipeline for blit operation. */
291 MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
292
293 desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
294 desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
295 [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat =
297
298 /* Ensure library is released. */
299 [library autorelease];
300
301 m_metalRenderPipeline = (MTLRenderPipelineState *)[device
302 newRenderPipelineStateWithDescriptor:desc
303 error:&error];
304 if (error) {
306 "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
307 }
308
309 /* Create a render pipeline to composite things rendered with Metal on top
310 * of the frame-buffer contents. Uses the same vertex and fragment shader
311 * as the blit above, but with alpha blending enabled. */
312 desc.label = @"Metal Overlay";
313 desc.colorAttachments[0].blendingEnabled = YES;
314 desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
315 desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
316
317 if (error) {
319 "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed (when "
320 "creating the Metal overlay pipeline)!");
321 }
322
323 [desc.fragmentFunction release];
324 [desc.vertexFunction release];
325 }
326}
327
328void GHOST_ContextCGL::metalFree()
329{
330 if (m_metalRenderPipeline) {
331 [m_metalRenderPipeline release];
332 m_metalRenderPipeline = nil;
333 }
334
335 for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
336 if (m_defaultFramebufferMetalTexture[i].texture) {
337 [m_defaultFramebufferMetalTexture[i].texture release];
338 m_defaultFramebufferMetalTexture[i].texture = nil;
339 }
340 }
341}
342
343void GHOST_ContextCGL::metalInitFramebuffer()
344{
346}
347
348void GHOST_ContextCGL::metalUpdateFramebuffer()
349{
350 @autoreleasepool {
351 const NSRect bounds = [m_metalView bounds];
352 const NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
353 const size_t width = size_t(backingSize.width);
354 const size_t height = size_t(backingSize.height);
355
356 if (m_defaultFramebufferMetalTexture[current_swapchain_index].texture &&
357 m_defaultFramebufferMetalTexture[current_swapchain_index].texture.width == width &&
358 m_defaultFramebufferMetalTexture[current_swapchain_index].texture.height == height)
359 {
360 return;
361 }
362
363 /* Free old texture */
364 [m_defaultFramebufferMetalTexture[current_swapchain_index].texture release];
365
366 id<MTLDevice> device = m_metalLayer.device;
367 MTLTextureDescriptor *overlayDesc = [MTLTextureDescriptor
368 texture2DDescriptorWithPixelFormat:METAL_FRAMEBUFFERPIXEL_FORMAT_EDR
369 width:width
370 height:height
371 mipmapped:NO];
372 overlayDesc.storageMode = MTLStorageModePrivate;
373 overlayDesc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
374
375 id<MTLTexture> overlayTex = [device newTextureWithDescriptor:overlayDesc];
376 if (!overlayTex) {
378 "GHOST_ContextCGL::metalUpdateFramebuffer: failed to create Metal overlay texture!");
379 }
380 else {
381 overlayTex.label = [NSString
382 stringWithFormat:@"Metal Overlay for GHOST Context %p", this]; //@"";
383 }
384
385 m_defaultFramebufferMetalTexture[current_swapchain_index].texture = overlayTex;
386
387 /* Clear texture on create */
388 id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
389 MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
390 {
391 auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
392 attachment.texture = m_defaultFramebufferMetalTexture[current_swapchain_index].texture;
393 attachment.loadAction = MTLLoadActionClear;
394 attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000);
395 attachment.storeAction = MTLStoreActionStore;
396 }
397 {
398 id<MTLRenderCommandEncoder> enc = [cmdBuffer
399 renderCommandEncoderWithDescriptor:passDescriptor];
400 [enc endEncoding];
401 }
402 [cmdBuffer commit];
403
404 m_metalLayer.drawableSize = CGSizeMake(CGFloat(width), CGFloat(height));
405 }
406}
407
408void GHOST_ContextCGL::metalSwapBuffers()
409{
410 @autoreleasepool {
412
413 id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
414 if (!drawable) {
415 return;
416 }
417
418 MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
419 {
420 auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
421 attachment.texture = drawable.texture;
422 attachment.loadAction = MTLLoadActionClear;
423 attachment.clearColor = MTLClearColorMake(1.0, 0.294, 0.294, 1.000);
424 attachment.storeAction = MTLStoreActionStore;
425 }
426
427 assert(contextPresentCallback);
428 assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil);
429 (*contextPresentCallback)(passDescriptor,
430 (id<MTLRenderPipelineState>)m_metalRenderPipeline,
431 m_defaultFramebufferMetalTexture[current_swapchain_index].texture,
432 drawable);
433 }
434}
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT_EDR
static void ghost_fatal_error_dialog(const char *msg)
GHOST_TSuccess
Definition GHOST_Types.h:80
@ GHOST_kFailure
Definition GHOST_Types.h:80
@ GHOST_kSuccess
Definition GHOST_Types.h:80
void init()
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
void metalRegisterPresentCallback(void(*callback)(MTLRenderPassDescriptor *, id< MTLRenderPipelineState >, id< MTLTexture >, id< CAMetalDrawable >))
GHOST_TSuccess updateDrawingContext() override
id< MTLTexture > metalOverlayTexture()
GHOST_TSuccess swapBuffers() override
GHOST_TSuccess activateDrawingContext() override
GHOST_TSuccess getSwapInterval(int &intervalOut) override
MTLDevice * metalDevice()
GHOST_ContextCGL(bool stereoVisual, NSView *metalView, CAMetalLayer *metalLayer, int debug)
GHOST_TSuccess releaseDrawingContext() override
static const int max_command_buffer_count
GHOST_TSuccess initializeDrawingContext() override
unsigned int getDefaultFramebuffer() override
MTLCommandQueue * metalCommandQueue()
GHOST_TSuccess releaseNativeHandles() override
GHOST_TSuccess setSwapInterval(int interval) override
~GHOST_ContextCGL() override
static GHOST_Context * active_context_
GHOST_Context(bool stereoVisual)
CCL_NAMESPACE_BEGIN struct Options options
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
#define assert(assertion)
#define printf(...)
static void error(const char *str)
static void init(bNodeTree *, bNode *node)
i
Definition text_draw.cc:230