Blender V5.0
GHOST_ContextMTL.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_ContextMTL.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_ContextMTL::s_sharedMetalCommandQueue = nil;
47int GHOST_ContextMTL::s_sharedCount = 0;
48
50 NSView *metalView,
51 CAMetalLayer *metalLayer)
52 : GHOST_Context(context_params),
53 metal_view_(metalView),
54 metal_layer_(metalLayer),
55 metal_render_pipeline_(nil)
56{
57 @autoreleasepool {
58 /* Initialize Metal Swap-chain. */
59 current_swapchain_index = 0;
60 for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
61 default_framebuffer_metal_texture_[i].texture = nil;
62 default_framebuffer_metal_texture_[i].index = i;
63 }
64
65 if (metal_view_) {
66 owns_metal_device_ = false;
67 metalInit();
68 }
69 else {
70 /* Prepare offscreen GHOST Context Metal device. */
71 id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();
72
73 if (context_params_.is_debug) {
74 printf("Selected Metal Device: %s\n", [metalDevice.name UTF8String]);
75 }
76
77 owns_metal_device_ = true;
78 if (metalDevice) {
79 metal_layer_ = [[CAMetalLayer alloc] init];
80 metal_layer_.edgeAntialiasingMask = 0;
81 metal_layer_.masksToBounds = NO;
82 metal_layer_.opaque = YES;
83 metal_layer_.framebufferOnly = YES;
84 metal_layer_.presentsWithTransaction = NO;
85 [metal_layer_ removeAllAnimations];
86 metal_layer_.device = metalDevice;
87 metal_layer_.allowsNextDrawableTimeout = NO;
88
89 {
90 const GHOST_TVSyncModes vsync = getVSync();
91 if (vsync != GHOST_kVSyncModeUnset) {
92 metal_layer_.displaySyncEnabled = (vsync == GHOST_kVSyncModeOff) ? NO : YES;
93 }
94 }
95
96 /* Enable EDR support. This is done by:
97 * 1. Using a floating point render target, so that values outside 0..1 can be used
98 * 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1
99 * 3. Setting the extended sRGB color space so that the OS knows how to interpret the
100 * values.
101 */
102 metal_layer_.wantsExtendedDynamicRangeContent = YES;
103 metal_layer_.pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT_EDR;
104 const CFStringRef name = kCGColorSpaceExtendedSRGB;
105 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
106 metal_layer_.colorspace = colorspace;
107 CGColorSpaceRelease(colorspace);
108
109 metalInit();
110 }
111 else {
113 "[ERROR] Failed to create Metal device for offscreen GHOST Context.\n");
114 }
115 }
116
117 /* Initialize swap-interval. */
118 mtl_SwapInterval = 60;
119 }
120}
121
123{
124 metalFree();
125
126 if (owns_metal_device_) {
127 if (metal_layer_) {
128 [metal_layer_ release];
129 metal_layer_ = nil;
130 }
131 }
132 assert(s_sharedCount);
133
134 s_sharedCount--;
135 [s_sharedMetalCommandQueue release];
136 if (s_sharedCount == 0) {
137 s_sharedMetalCommandQueue = nil;
138 }
139}
140
142{
143 if (metal_view_) {
144 metalSwapBuffers();
145 }
146 return GHOST_kSuccess;
147}
148
150{
151 mtl_SwapInterval = interval;
152 return GHOST_kSuccess;
153}
154
156{
157 interval_out = mtl_SwapInterval;
158 return GHOST_kSuccess;
159}
160
166
172
174{
175 /* NOTE(Metal): This is not valid. */
176 return 0;
177}
178
180{
181 if (metal_view_) {
182 metalUpdateFramebuffer();
183 return GHOST_kSuccess;
184 }
185 return GHOST_kFailure;
186}
187
189{
190 /* Increment Swap-chain - Only needed if context is requesting a new texture */
191 current_swapchain_index = (current_swapchain_index + 1) % METAL_SWAPCHAIN_SIZE;
192
193 /* Ensure backing texture is ready for current swapchain index */
195
196 /* Return texture. */
197 return default_framebuffer_metal_texture_[current_swapchain_index].texture;
198}
199
201{
202 return s_sharedMetalCommandQueue;
203}
205{
206 id<MTLDevice> device = metal_layer_.device;
207 return (MTLDevice *)device;
208}
209
211 MTLRenderPassDescriptor *, id<MTLRenderPipelineState>, id<MTLTexture>, id<CAMetalDrawable>))
212{
213 this->contextPresentCallback = callback;
214}
215
217{
218 @autoreleasepool {
219 if (metal_view_) {
220 metalInitFramebuffer();
221 }
222 }
223 active_context_ = this;
224 return GHOST_kSuccess;
225}
226
228{
229 metal_view_ = nil;
230
231 return GHOST_kSuccess;
232}
233
234void GHOST_ContextMTL::metalInit()
235{
236 @autoreleasepool {
237 id<MTLDevice> device = metal_layer_.device;
238
239 /* Create a command queue for blit/present operation.
240 * NOTE: All context should share a single command queue
241 * to ensure correct ordering of work submitted from multiple contexts. */
242 if (s_sharedMetalCommandQueue == nil) {
243 s_sharedMetalCommandQueue = (MTLCommandQueue *)[device
244 newCommandQueueWithMaxCommandBufferCount:GHOST_ContextMTL::max_command_buffer_count];
245 }
246 /* Ensure active GHOSTContext retains a reference to the shared context. */
247 [s_sharedMetalCommandQueue retain];
248 s_sharedCount++;
249
250 /* Create shaders for blit operation. */
251 NSString *source = @R"msl(
252 using namespace metal;
253
254 struct Vertex {
255 float4 position [[position]];
256 float2 texCoord [[attribute(0)]];
257 };
258
259 vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
260 Vertex vtx;
261
262 vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
263 vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
264 vtx.position.z = 0.0;
265 vtx.position.w = 1.0;
266
267 vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
268
269 return vtx;
270 }
271
272 constexpr sampler s {};
273
274 fragment float4 fragment_shader(Vertex v [[stage_in]],
275 texture2d<float> t [[texture(0)]]) {
276
277 /* Final blit should ensure alpha is 1.0. This resolves
278 * rendering artifacts for blitting of final back-buffer. */
279 float4 out_tex = t.sample(s, v.texCoord);
280 out_tex.a = 1.0;
281 return out_tex;
282 }
283 )msl";
284
285 MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
286 options.languageVersion = MTLLanguageVersion1_1;
287
288 NSError *error = nil;
289 id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
290 if (error) {
292 "GHOST_ContextMTL::metalInit: newLibraryWithSource:options:error: failed!");
293 }
294
295 /* Create a render pipeline for blit operation. */
296 MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
297
298 desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
299 desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
300 [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat =
302
303 /* Ensure library is released. */
304 [library autorelease];
305
306 metal_render_pipeline_ = (MTLRenderPipelineState *)[device
307 newRenderPipelineStateWithDescriptor:desc
308 error:&error];
309 if (error) {
311 "GHOST_ContextMTL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
312 }
313
314 /* Create a render pipeline to composite things rendered with Metal on top
315 * of the frame-buffer contents. Uses the same vertex and fragment shader
316 * as the blit above, but with alpha blending enabled. */
317 desc.label = @"Metal Overlay";
318 desc.colorAttachments[0].blendingEnabled = YES;
319 desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
320 desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
321
322 if (error) {
324 "GHOST_ContextMTL::metalInit: newRenderPipelineStateWithDescriptor:error: failed (when "
325 "creating the Metal overlay pipeline)!");
326 }
327
328 [desc.fragmentFunction release];
329 [desc.vertexFunction release];
330 }
331}
332
333void GHOST_ContextMTL::metalFree()
334{
335 if (metal_render_pipeline_) {
336 [metal_render_pipeline_ release];
337 metal_render_pipeline_ = nil;
338 }
339
340 for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
341 if (default_framebuffer_metal_texture_[i].texture) {
342 [default_framebuffer_metal_texture_[i].texture release];
343 default_framebuffer_metal_texture_[i].texture = nil;
344 }
345 }
346}
347
348void GHOST_ContextMTL::metalInitFramebuffer()
349{
351}
352
353void GHOST_ContextMTL::metalUpdateFramebuffer()
354{
355 @autoreleasepool {
356 const NSRect bounds = [metal_view_ bounds];
357 const NSSize backingSize = [metal_view_ convertSizeToBacking:bounds.size];
358 const size_t width = size_t(backingSize.width);
359 const size_t height = size_t(backingSize.height);
360
361 if (default_framebuffer_metal_texture_[current_swapchain_index].texture &&
362 default_framebuffer_metal_texture_[current_swapchain_index].texture.width == width &&
363 default_framebuffer_metal_texture_[current_swapchain_index].texture.height == height)
364 {
365 return;
366 }
367
368 /* Free old texture */
369 [default_framebuffer_metal_texture_[current_swapchain_index].texture release];
370
371 id<MTLDevice> device = metal_layer_.device;
372 MTLTextureDescriptor *overlayDesc = [MTLTextureDescriptor
373 texture2DDescriptorWithPixelFormat:METAL_FRAMEBUFFERPIXEL_FORMAT_EDR
374 width:width
375 height:height
376 mipmapped:NO];
377 overlayDesc.storageMode = MTLStorageModePrivate;
378 overlayDesc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
379
380 id<MTLTexture> overlayTex = [device newTextureWithDescriptor:overlayDesc];
381 if (!overlayTex) {
383 "GHOST_ContextMTL::metalUpdateFramebuffer: failed to create Metal overlay texture!");
384 }
385 else {
386 overlayTex.label = [NSString
387 stringWithFormat:@"Metal Overlay for GHOST Context %p", this]; //@"";
388 }
389
390 default_framebuffer_metal_texture_[current_swapchain_index].texture = overlayTex;
391
392 /* Clear texture on create */
393 id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
394 MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
395 {
396 auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
397 attachment.texture = default_framebuffer_metal_texture_[current_swapchain_index].texture;
398 attachment.loadAction = MTLLoadActionClear;
399 attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000);
400 attachment.storeAction = MTLStoreActionStore;
401 }
402 {
403 id<MTLRenderCommandEncoder> enc = [cmdBuffer
404 renderCommandEncoderWithDescriptor:passDescriptor];
405 [enc endEncoding];
406 }
407 [cmdBuffer commit];
408
409 metal_layer_.drawableSize = CGSizeMake(CGFloat(width), CGFloat(height));
410 }
411}
412
413void GHOST_ContextMTL::metalSwapBuffers()
414{
415 @autoreleasepool {
417
418 id<CAMetalDrawable> drawable = [metal_layer_ nextDrawable];
419 if (!drawable) {
420 return;
421 }
422
423 MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
424 {
425 auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
426 attachment.texture = drawable.texture;
427 attachment.loadAction = MTLLoadActionClear;
428 attachment.clearColor = MTLClearColorMake(1.0, 0.294, 0.294, 1.000);
429 attachment.storeAction = MTLStoreActionStore;
430 }
431
432 assert(contextPresentCallback);
433 assert(default_framebuffer_metal_texture_[current_swapchain_index].texture != nil);
434 (*contextPresentCallback)(passDescriptor,
435 (id<MTLRenderPipelineState>)metal_render_pipeline_,
436 default_framebuffer_metal_texture_[current_swapchain_index].texture,
437 drawable);
438 }
439}
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT_EDR
static void ghost_fatal_error_dialog(const char *msg)
GHOST_TSuccess
Definition GHOST_Types.h:57
@ GHOST_kFailure
Definition GHOST_Types.h:57
@ GHOST_kSuccess
Definition GHOST_Types.h:57
GHOST_TVSyncModes
@ GHOST_kVSyncModeUnset
@ GHOST_kVSyncModeOff
void init()
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
GHOST_TSuccess releaseDrawingContext() override
static const int max_command_buffer_count
MTLCommandQueue * metalCommandQueue()
MTLDevice * metalDevice()
GHOST_TSuccess activateDrawingContext() override
void metalRegisterPresentCallback(void(*callback)(MTLRenderPassDescriptor *, id< MTLRenderPipelineState >, id< MTLTexture >, id< CAMetalDrawable >))
GHOST_TSuccess setSwapInterval(int interval) override
GHOST_ContextMTL(const GHOST_ContextParams &context_params, NSView *metalView, CAMetalLayer *metalLayer)
GHOST_TSuccess swapBufferRelease() override
GHOST_TSuccess updateDrawingContext() override
GHOST_TSuccess initializeDrawingContext() override
GHOST_TSuccess releaseNativeHandles() override
id< MTLTexture > metalOverlayTexture()
~GHOST_ContextMTL() override
unsigned int getDefaultFramebuffer() override
GHOST_TSuccess getSwapInterval(int &interval_out) override
GHOST_Context(const GHOST_ContextParams &context_params)
static GHOST_Context * active_context_
virtual GHOST_TVSyncModes getVSync()
GHOST_ContextParams context_params_
CCL_NAMESPACE_BEGIN struct Options options
#define assert(assertion)
#define printf(...)
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
static void error(const char *str)
static void init(bNodeTree *, bNode *node)
const char * name
i
Definition text_draw.cc:230