Changeset View
Changeset View
Standalone View
Standalone View
intern/ghost/intern/GHOST_ContextCGL.mm
| Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual, | ||||
| : GHOST_Context(stereoVisual), | : GHOST_Context(stereoVisual), | ||||
| m_metalView(metalView), | m_metalView(metalView), | ||||
| m_metalLayer(metalLayer), | m_metalLayer(metalLayer), | ||||
| m_metalCmdQueue(nil), | m_metalCmdQueue(nil), | ||||
| m_metalRenderPipeline(nil), | m_metalRenderPipeline(nil), | ||||
| m_openGLView(openGLView), | m_openGLView(openGLView), | ||||
| m_openGLContext(nil), | m_openGLContext(nil), | ||||
| m_defaultFramebuffer(0), | m_defaultFramebuffer(0), | ||||
| m_defaultFramebufferMetalTexture(nil), | |||||
| m_debug(false) | m_debug(false) | ||||
| { | { | ||||
| /* Init Metal Swapchain. */ | |||||
| current_swapchain_index = 0; | |||||
| for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) { | |||||
| m_defaultFramebufferMetalTexture[i].texture = nil; | |||||
| m_defaultFramebufferMetalTexture[i].index = i; | |||||
| } | |||||
| if (m_metalView) { | if (m_metalView) { | ||||
| m_ownsMetalDevice = false; | |||||
| metalInit(); | |||||
| } | |||||
| else { | |||||
| /* Prepare offscreen GHOST Context Metal device. */ | |||||
| id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice(); | |||||
| if (m_debug) { | |||||
| printf("Selected Metal Device: %s\n", [metalDevice.name UTF8String]); | |||||
| } | |||||
| m_ownsMetalDevice = true; | |||||
| if (metalDevice) { | |||||
| m_metalLayer = [[CAMetalLayer alloc] init]; | |||||
| [m_metalLayer setEdgeAntialiasingMask:0]; | |||||
| [m_metalLayer setMasksToBounds:NO]; | |||||
| [m_metalLayer setOpaque:YES]; | |||||
| [m_metalLayer setFramebufferOnly:YES]; | |||||
| [m_metalLayer setPresentsWithTransaction:NO]; | |||||
| [m_metalLayer removeAllAnimations]; | |||||
| [m_metalLayer setDevice:metalDevice]; | |||||
| m_metalLayer.allowsNextDrawableTimeout = NO; | |||||
| metalInit(); | metalInit(); | ||||
| } | } | ||||
| else { | |||||
| ghost_fatal_error_dialog( | |||||
| "[ERROR] Failed to create Metal device for offscreen GHOST Context.\n"); | |||||
| } | |||||
| } | |||||
| /* Initialise swapinterval. */ | |||||
| mtl_SwapInterval = 60; | |||||
| } | } | ||||
| GHOST_ContextCGL::~GHOST_ContextCGL() | GHOST_ContextCGL::~GHOST_ContextCGL() | ||||
| { | { | ||||
| metalFree(); | metalFree(); | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| if (m_openGLContext == [NSOpenGLContext currentContext]) { | if (m_openGLContext == [NSOpenGLContext currentContext]) { | ||||
| [NSOpenGLContext clearCurrentContext]; | [NSOpenGLContext clearCurrentContext]; | ||||
| if (m_openGLView) { | if (m_openGLView) { | ||||
| [m_openGLView clearGLContext]; | [m_openGLView clearGLContext]; | ||||
| } | } | ||||
| } | } | ||||
| if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) { | if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) { | ||||
| assert(s_sharedCount > 0); | assert(s_sharedCount > 0); | ||||
| s_sharedCount--; | s_sharedCount--; | ||||
| if (s_sharedCount == 0) | if (s_sharedCount == 0) | ||||
| s_sharedOpenGLContext = nil; | s_sharedOpenGLContext = nil; | ||||
| [m_openGLContext release]; | [m_openGLContext release]; | ||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| if (m_ownsMetalDevice) { | |||||
| if (m_metalLayer) { | |||||
| [m_metalLayer release]; | |||||
| m_metalLayer = nil; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::swapBuffers() | GHOST_TSuccess GHOST_ContextCGL::swapBuffers() | ||||
| { | { | ||||
| GHOST_TSuccess return_value = GHOST_kFailure; | |||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| if (m_metalView) { | if (m_metalView) { | ||||
| metalSwapBuffers(); | metalSwapBuffers(); | ||||
| } | } | ||||
| else if (m_openGLView) { | else if (m_openGLView) { | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||||
| [m_openGLContext flushBuffer]; | [m_openGLContext flushBuffer]; | ||||
| [pool drain]; | [pool drain]; | ||||
| } | } | ||||
| return GHOST_kSuccess; | return_value = GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return_value = GHOST_kFailure; | ||||
| } | |||||
| #endif | |||||
| } | |||||
| else { | |||||
| if (m_metalView) { | |||||
| metalSwapBuffers(); | |||||
| } | } | ||||
| return_value = GHOST_kSuccess; | |||||
| } | |||||
| return return_value; | |||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::setSwapInterval(int interval) | GHOST_TSuccess GHOST_ContextCGL::setSwapInterval(int interval) | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||||
| [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval]; | [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval]; | ||||
| [pool drain]; | [pool drain]; | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| mtl_SwapInterval = interval; | |||||
| return GHOST_kSuccess; | |||||
| } | |||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::getSwapInterval(int &intervalOut) | GHOST_TSuccess GHOST_ContextCGL::getSwapInterval(int &intervalOut) | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| GLint interval; | GLint interval; | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||||
| [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval]; | [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval]; | ||||
| [pool drain]; | [pool drain]; | ||||
| intervalOut = static_cast<int>(interval); | intervalOut = static_cast<int>(interval); | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| intervalOut = mtl_SwapInterval; | |||||
| return GHOST_kSuccess; | |||||
| } | |||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::activateDrawingContext() | GHOST_TSuccess GHOST_ContextCGL::activateDrawingContext() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||||
| [m_openGLContext makeCurrentContext]; | [m_openGLContext makeCurrentContext]; | ||||
| [pool drain]; | [pool drain]; | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| return GHOST_kSuccess; | |||||
| } | |||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::releaseDrawingContext() | GHOST_TSuccess GHOST_ContextCGL::releaseDrawingContext() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||||
| [NSOpenGLContext clearCurrentContext]; | [NSOpenGLContext clearCurrentContext]; | ||||
| [pool drain]; | [pool drain]; | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| return GHOST_kSuccess; | |||||
| } | |||||
| } | } | ||||
| unsigned int GHOST_ContextCGL::getDefaultFramebuffer() | unsigned int GHOST_ContextCGL::getDefaultFramebuffer() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| return m_defaultFramebuffer; | return m_defaultFramebuffer; | ||||
| } | } | ||||
| /* NOTE(Metal): This is not valid. */ | |||||
| return 0; | |||||
| } | |||||
| GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext() | GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| if (m_openGLContext != nil) { | if (m_openGLContext != nil) { | ||||
| if (m_metalView) { | if (m_metalView) { | ||||
| metalUpdateFramebuffer(); | metalUpdateFramebuffer(); | ||||
| } | } | ||||
| else if (m_openGLView) { | else if (m_openGLView) { | ||||
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | @autoreleasepool { | ||||
| [m_openGLContext update]; | [m_openGLContext update]; | ||||
| [pool drain]; | } | ||||
| } | } | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| else { | else { | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| if (m_metalView) { | |||||
| metalUpdateFramebuffer(); | |||||
| return GHOST_kSuccess; | |||||
| } | |||||
| } | |||||
| return GHOST_kFailure; | |||||
| } | |||||
| id<MTLTexture> GHOST_ContextCGL::metalOverlayTexture() | |||||
| { | |||||
| /* Increment Swapchain - Only needed if context is requesting a new texture */ | |||||
| current_swapchain_index = (current_swapchain_index + 1) % METAL_SWAPCHAIN_SIZE; | |||||
| /* Ensure backing texture is ready for current swapchain index */ | |||||
| updateDrawingContext(); | |||||
| /* Return texture. */ | |||||
| return m_defaultFramebufferMetalTexture[current_swapchain_index].texture; | |||||
| } | |||||
| MTLCommandQueue *GHOST_ContextCGL::metalCommandQueue() | |||||
| { | |||||
| return m_metalCmdQueue; | |||||
| } | |||||
| MTLDevice *GHOST_ContextCGL::metalDevice() | |||||
| { | |||||
| id<MTLDevice> device = m_metalLayer.device; | |||||
| return (MTLDevice *)device; | |||||
| } | |||||
| void GHOST_ContextCGL::metalRegisterPresentCallback(void (*callback)( | |||||
| MTLRenderPassDescriptor *, id<MTLRenderPipelineState>, id<MTLTexture>, id<CAMetalDrawable>)) | |||||
| { | |||||
| this->contextPresentCallback = callback; | |||||
| } | } | ||||
| static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs, | static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs, | ||||
| bool stereoVisual, | bool stereoVisual, | ||||
| bool needAlpha, | bool needAlpha, | ||||
| bool softwareGL, | bool softwareGL, | ||||
| bool increasedSamplerLimit) | bool increasedSamplerLimit) | ||||
| { | { | ||||
| Show All 37 Lines | |||||
| #ifdef GHOST_OPENGL_ALPHA | #ifdef GHOST_OPENGL_ALPHA | ||||
| static const bool needAlpha = true; | static const bool needAlpha = true; | ||||
| #else | #else | ||||
| static const bool needAlpha = false; | static const bool needAlpha = false; | ||||
| #endif | #endif | ||||
| /* Command-line argument would be better. */ | /* Command-line argument would be better. */ | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| /* Command-line argument would be better. */ | |||||
| static bool softwareGL = getenv("BLENDER_SOFTWAREGL"); | static bool softwareGL = getenv("BLENDER_SOFTWAREGL"); | ||||
| NSOpenGLPixelFormat *pixelFormat = nil; | NSOpenGLPixelFormat *pixelFormat = nil; | ||||
| std::vector<NSOpenGLPixelFormatAttribute> attribs; | std::vector<NSOpenGLPixelFormatAttribute> attribs; | ||||
| bool increasedSamplerLimit = false; | bool increasedSamplerLimit = false; | ||||
| /* Attempt to initialize device with increased sampler limit. | /* Attempt to initialize device with increased sampler limit. | ||||
| * If this is unsupported and initialization fails, initialize GL Context as normal. | * If this is unsupported and initialization fails, initialize GL Context as normal. | ||||
| * | * | ||||
| * NOTE: This is not available when using the SoftwareGL path, or for Intel-based | * NOTE: This is not available when using the SoftwareGL path, or for Intel-based | ||||
| * platforms. */ | * platforms. */ | ||||
| if (!softwareGL) { | if (!softwareGL) { | ||||
| if (@available(macos 11.0, *)) { | if (@available(macos 11.0, *)) { | ||||
| increasedSamplerLimit = true; | increasedSamplerLimit = true; | ||||
| } | } | ||||
| } | } | ||||
| const int max_ctx_attempts = increasedSamplerLimit ? 2 : 1; | const int max_ctx_attempts = increasedSamplerLimit ? 2 : 1; | ||||
| for (int ctx_create_attempt = 0; ctx_create_attempt < max_ctx_attempts; ctx_create_attempt++) { | for (int ctx_create_attempt = 0; ctx_create_attempt < max_ctx_attempts; | ||||
| ctx_create_attempt++) { | |||||
| attribs.clear(); | attribs.clear(); | ||||
| attribs.reserve(40); | attribs.reserve(40); | ||||
| makeAttribList(attribs, m_stereoVisual, needAlpha, softwareGL, increasedSamplerLimit); | makeAttribList(attribs, m_stereoVisual, needAlpha, softwareGL, increasedSamplerLimit); | ||||
| pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]]; | pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]]; | ||||
| if (pixelFormat == nil) { | if (pixelFormat == nil) { | ||||
| /* If pixel format creation fails when testing increased sampler limit, | /* If pixel format creation fails when testing increased sampler limit, | ||||
| * attempt initialization again with feature disabled, otherwise, fail. */ | * attempt intialisation again with feature disabled, otherwise, fail. */ | ||||
| if (increasedSamplerLimit) { | if (increasedSamplerLimit) { | ||||
| increasedSamplerLimit = false; | increasedSamplerLimit = false; | ||||
| continue; | continue; | ||||
| } | } | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| /* Attempt to create context. */ | /* Attempt to create context. */ | ||||
| m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat | m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat | ||||
| shareContext:s_sharedOpenGLContext]; | shareContext:s_sharedOpenGLContext]; | ||||
| [pixelFormat release]; | [pixelFormat release]; | ||||
| if (m_openGLContext == nil) { | if (m_openGLContext == nil) { | ||||
| /* If context creation fails when testing increased sampler limit, | /* If context creation fails when testing increased sampler limit, | ||||
| * attempt re-creation with feature disabled. Otherwise, error. */ | * attempt re-creation with feature disabled. Otherwise, error. */ | ||||
| if (increasedSamplerLimit) { | if (increasedSamplerLimit) { | ||||
| increasedSamplerLimit = false; | increasedSamplerLimit = false; | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* Default context creation attempt failed. */ | /* Default context creation attempt failed. */ | ||||
| return GHOST_kFailure; | return GHOST_kFailure; | ||||
| } | } | ||||
| /* Created GL context successfully, activate. */ | /* Created GL context successfully, activate. */ | ||||
| [m_openGLContext makeCurrentContext]; | [m_openGLContext makeCurrentContext]; | ||||
| /* When increasing sampler limit, verify created context is a supported configuration. */ | /* When increasing sampler limit, verify created context is a supported configuration. */ | ||||
| if (increasedSamplerLimit) { | if (increasedSamplerLimit) { | ||||
| const char *vendor = (const char *)glGetString(GL_VENDOR); | const char *vendor = (const char *)glGetString(GL_VENDOR); | ||||
| const char *renderer = (const char *)glGetString(GL_RENDERER); | const char *renderer = (const char *)glGetString(GL_RENDERER); | ||||
| /* If generated context type is unsupported, release existing context and | /* If generated context type is unsupported, release existing context and | ||||
| * fallback to creating a normal context below. */ | * fallback to creating a normal context below. */ | ||||
| if (strstr(vendor, "Intel") || strstr(renderer, "Software")) { | if (strstr(vendor, "Intel") || strstr(renderer, "Software")) { | ||||
| [m_openGLContext release]; | [m_openGLContext release]; | ||||
| m_openGLContext = nil; | m_openGLContext = nil; | ||||
| increasedSamplerLimit = false; | increasedSamplerLimit = false; | ||||
| continue; | continue; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (m_debug) { | if (m_debug) { | ||||
| GLint major = 0, minor = 0; | GLint major = 0, minor = 0; | ||||
| glGetIntegerv(GL_MAJOR_VERSION, &major); | glGetIntegerv(GL_MAJOR_VERSION, &major); | ||||
| glGetIntegerv(GL_MINOR_VERSION, &minor); | glGetIntegerv(GL_MINOR_VERSION, &minor); | ||||
| fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : ""); | fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : ""); | ||||
| fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER)); | fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER)); | ||||
| } | } | ||||
| #ifdef GHOST_WAIT_FOR_VSYNC | # ifdef GHOST_WAIT_FOR_VSYNC | ||||
| { | { | ||||
| GLint swapInt = 1; | GLint swapInt = 1; | ||||
| /* Wait for vertical-sync, to avoid tearing artifacts. */ | /* Wait for vertical-sync, to avoid tearing artifacts. */ | ||||
| [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; | [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; | ||||
| } | } | ||||
| #endif | # endif | ||||
| if (m_metalView) { | if (m_metalView) { | ||||
| if (m_defaultFramebuffer == 0) { | if (m_defaultFramebuffer == 0) { | ||||
| /* Create a virtual frame-buffer. */ | /* Create a virtual frame-buffer. */ | ||||
| [m_openGLContext makeCurrentContext]; | [m_openGLContext makeCurrentContext]; | ||||
| metalInitFramebuffer(); | metalInitFramebuffer(); | ||||
| initClearGL(); | initClearGL(); | ||||
| } | } | ||||
| } | } | ||||
| else if (m_openGLView) { | else if (m_openGLView) { | ||||
| [m_openGLView setOpenGLContext:m_openGLContext]; | [m_openGLView setOpenGLContext:m_openGLContext]; | ||||
| [m_openGLContext setView:m_openGLView]; | [m_openGLContext setView:m_openGLView]; | ||||
| initClearGL(); | initClearGL(); | ||||
| } | } | ||||
| [m_openGLContext flushBuffer]; | [m_openGLContext flushBuffer]; | ||||
| if (s_sharedCount == 0) | if (s_sharedCount == 0) | ||||
| s_sharedOpenGLContext = m_openGLContext; | s_sharedOpenGLContext = m_openGLContext; | ||||
| s_sharedCount++; | s_sharedCount++; | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| /* NOTE(Metal): Metal-only path. */ | |||||
| if (m_metalView) { | |||||
| metalInitFramebuffer(); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles() | GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles() | ||||
| { | { | ||||
| #if WITH_OPENGL | |||||
| m_openGLContext = nil; | m_openGLContext = nil; | ||||
| m_openGLView = nil; | m_openGLView = nil; | ||||
| #endif | |||||
| m_metalView = nil; | m_metalView = nil; | ||||
| return GHOST_kSuccess; | return GHOST_kSuccess; | ||||
| } | } | ||||
| /* OpenGL on Metal | /* OpenGL on Metal | ||||
| * | * | ||||
| * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */ | * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */ | ||||
| Show All 33 Lines | NSString *source = @R"msl( | ||||
| return vtx; | return vtx; | ||||
| } | } | ||||
| constexpr sampler s {}; | constexpr sampler s {}; | ||||
| fragment float4 fragment_shader(Vertex v [[stage_in]], | fragment float4 fragment_shader(Vertex v [[stage_in]], | ||||
| texture2d<float> t [[texture(0)]]) { | texture2d<float> t [[texture(0)]]) { | ||||
| return t.sample(s, v.texCoord); | |||||
| } | |||||
| /* Final blit should ensure alpha is 1.0. This resolves | |||||
| * rendering artifacts for blitting of final backbuffer. */ | |||||
| float4 out_tex = t.sample(s, v.texCoord); | |||||
| out_tex.a = 1.0; | |||||
| return out_tex; | |||||
| } | |||||
| )msl"; | )msl"; | ||||
| MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease]; | MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease]; | ||||
| options.languageVersion = MTLLanguageVersion1_1; | options.languageVersion = MTLLanguageVersion1_1; | ||||
| NSError *error = nil; | NSError *error = nil; | ||||
| id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error]; | id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error]; | ||||
| if (error) { | if (error) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!"); | "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!"); | ||||
| } | } | ||||
| /* Create a render pipeline for blit operation. */ | /* Create a render pipeline for blit operation. */ | ||||
| MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease]; | MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease]; | ||||
| desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"]; | desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"]; | ||||
| desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"]; | desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"]; | ||||
| /* Ensure library is released. */ | |||||
| [library autorelease]; | |||||
| [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT; | [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT; | ||||
| m_metalRenderPipeline = (MTLRenderPipelineState *)[device | m_metalRenderPipeline = (MTLRenderPipelineState *)[device | ||||
| newRenderPipelineStateWithDescriptor:desc | newRenderPipelineStateWithDescriptor:desc | ||||
| error:&error]; | error:&error]; | ||||
| if (error) { | if (error) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!"); | "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!"); | ||||
| } | } | ||||
| /* Create a render pipeline to composite things rendered with Metal on top | |||||
| * of the framebuffer contents. Uses the same vertex and fragment shader | |||||
| * as the blit above, but with alpha blending enabled. */ | |||||
| desc.label = @"Metal Overlay"; | |||||
| desc.colorAttachments[0].blendingEnabled = YES; | |||||
| desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; | |||||
| desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; | |||||
| if (error) { | |||||
| ghost_fatal_error_dialog( | |||||
| "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed (when " | |||||
| "creating the Metal overlay pipeline)!"); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| void GHOST_ContextCGL::metalFree() | void GHOST_ContextCGL::metalFree() | ||||
| { | { | ||||
| if (m_metalCmdQueue) { | if (m_metalCmdQueue) { | ||||
| [m_metalCmdQueue release]; | [m_metalCmdQueue release]; | ||||
| } | } | ||||
| if (m_metalRenderPipeline) { | if (m_metalRenderPipeline) { | ||||
| [m_metalRenderPipeline release]; | [m_metalRenderPipeline release]; | ||||
| } | } | ||||
| if (m_defaultFramebufferMetalTexture) { | |||||
| [m_defaultFramebufferMetalTexture release]; | for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) { | ||||
| if (m_defaultFramebufferMetalTexture[i].texture) { | |||||
| [m_defaultFramebufferMetalTexture[i].texture release]; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| void GHOST_ContextCGL::metalInitFramebuffer() | void GHOST_ContextCGL::metalInitFramebuffer() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| glGenFramebuffers(1, &m_defaultFramebuffer); | glGenFramebuffers(1, &m_defaultFramebuffer); | ||||
| #endif | |||||
| } | |||||
| updateDrawingContext(); | updateDrawingContext(); | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer); | glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer); | ||||
| #endif | |||||
| } | |||||
| } | } | ||||
| void GHOST_ContextCGL::metalUpdateFramebuffer() | void GHOST_ContextCGL::metalUpdateFramebuffer() | ||||
| { | { | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| assert(m_defaultFramebuffer != 0); | assert(m_defaultFramebuffer != 0); | ||||
| #endif | |||||
| } | |||||
| NSRect bounds = [m_metalView bounds]; | NSRect bounds = [m_metalView bounds]; | ||||
| NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size]; | NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size]; | ||||
| size_t width = (size_t)backingSize.width; | size_t width = (size_t)backingSize.width; | ||||
| size_t height = (size_t)backingSize.height; | size_t height = (size_t)backingSize.height; | ||||
| #if WITH_OPENGL | |||||
| unsigned int glTex; | |||||
| CVPixelBufferRef cvPixelBuffer = nil; | |||||
| CVOpenGLTextureCacheRef cvGLTexCache = nil; | |||||
| CVOpenGLTextureRef cvGLTex = nil; | |||||
| CVMetalTextureCacheRef cvMetalTexCache = nil; | |||||
| CVMetalTextureRef cvMetalTex = nil; | |||||
| #endif | |||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| /* OPENGL path */ | |||||
| { | { | ||||
| /* Test if there is anything to update */ | /* Test if there is anything to update */ | ||||
| id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture; | id<MTLTexture> tex = m_defaultFramebufferMetalTexture[current_swapchain_index].texture; | ||||
| if (tex && tex.width == width && tex.height == height) { | if (tex && tex.width == width && tex.height == height) { | ||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| activateDrawingContext(); | activateDrawingContext(); | ||||
| NSDictionary *cvPixelBufferProps = @{ | NSDictionary *cvPixelBufferProps = @{ | ||||
| (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES, | (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES, | ||||
| (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES, | (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES, | ||||
| }; | }; | ||||
| CVPixelBufferRef cvPixelBuffer = nil; | |||||
| CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault, | CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault, | ||||
| width, | width, | ||||
| height, | height, | ||||
| METAL_CORE_VIDEO_PIXEL_FORMAT, | METAL_CORE_VIDEO_PIXEL_FORMAT, | ||||
| (__bridge CFDictionaryRef)cvPixelBufferProps, | (__bridge CFDictionaryRef)cvPixelBufferProps, | ||||
| &cvPixelBuffer); | &cvPixelBuffer); | ||||
| if (cvret != kCVReturnSuccess) { | if (cvret != kCVReturnSuccess) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!"); | "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!"); | ||||
| } | } | ||||
| /* Create an OpenGL texture. */ | /* Create an OpenGL texture. */ | ||||
| CVOpenGLTextureCacheRef cvGLTexCache = nil; | |||||
| cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, | cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, | ||||
| nil, | nil, | ||||
| m_openGLContext.CGLContextObj, | m_openGLContext.CGLContextObj, | ||||
| m_openGLContext.pixelFormat.CGLPixelFormatObj, | m_openGLContext.pixelFormat.CGLPixelFormatObj, | ||||
| nil, | nil, | ||||
| &cvGLTexCache); | &cvGLTexCache); | ||||
| if (cvret != kCVReturnSuccess) { | if (cvret != kCVReturnSuccess) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!"); | "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!"); | ||||
| } | } | ||||
| CVOpenGLTextureRef cvGLTex = nil; | |||||
| cvret = CVOpenGLTextureCacheCreateTextureFromImage( | cvret = CVOpenGLTextureCacheCreateTextureFromImage( | ||||
| kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex); | kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex); | ||||
| if (cvret != kCVReturnSuccess) { | if (cvret != kCVReturnSuccess) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: " | "GHOST_ContextCGL::metalUpdateFramebuffer: " | ||||
| "CVOpenGLTextureCacheCreateTextureFromImage failed!"); | "CVOpenGLTextureCacheCreateTextureFromImage failed!"); | ||||
| } | } | ||||
| unsigned int glTex; | |||||
| glTex = CVOpenGLTextureGetName(cvGLTex); | glTex = CVOpenGLTextureGetName(cvGLTex); | ||||
| /* Create a Metal texture. */ | /* Create a Metal texture. */ | ||||
| CVMetalTextureCacheRef cvMetalTexCache = nil; | |||||
| cvret = CVMetalTextureCacheCreate( | cvret = CVMetalTextureCacheCreate( | ||||
| kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache); | kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache); | ||||
| if (cvret != kCVReturnSuccess) { | if (cvret != kCVReturnSuccess) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!"); | "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!"); | ||||
| } | } | ||||
| CVMetalTextureRef cvMetalTex = nil; | |||||
| cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, | cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, | ||||
| cvMetalTexCache, | cvMetalTexCache, | ||||
| cvPixelBuffer, | cvPixelBuffer, | ||||
| nil, | nil, | ||||
| METAL_FRAMEBUFFERPIXEL_FORMAT, | METAL_FRAMEBUFFERPIXEL_FORMAT, | ||||
| width, | width, | ||||
| height, | height, | ||||
| 0, | 0, | ||||
| &cvMetalTex); | &cvMetalTex); | ||||
| if (cvret != kCVReturnSuccess) { | if (cvret != kCVReturnSuccess) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: " | "GHOST_ContextCGL::metalUpdateFramebuffer: " | ||||
| "CVMetalTextureCacheCreateTextureFromImage failed!"); | "CVMetalTextureCacheCreateTextureFromImage failed!"); | ||||
| } | } | ||||
| MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex); | id<MTLTexture> tex = CVMetalTextureGetTexture(cvMetalTex); | ||||
| if (!tex) { | if (!tex) { | ||||
| ghost_fatal_error_dialog( | ghost_fatal_error_dialog( | ||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!"); | "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!"); | ||||
| } | } | ||||
| [m_defaultFramebufferMetalTexture release]; | [m_defaultFramebufferMetalTexture[current_swapchain_index].texture release]; | ||||
| m_defaultFramebufferMetalTexture = [tex retain]; | m_defaultFramebufferMetalTexture[current_swapchain_index].texture = [tex retain]; | ||||
| #endif | |||||
| } | |||||
| else { | |||||
| /* NOTE(Metal): Metal API Path. */ | |||||
| if (m_defaultFramebufferMetalTexture[current_swapchain_index].texture && | |||||
| m_defaultFramebufferMetalTexture[current_swapchain_index].texture.width == width && | |||||
| m_defaultFramebufferMetalTexture[current_swapchain_index].texture.height == height) { | |||||
| return; | |||||
| } | |||||
| /* Free old texture */ | |||||
| [m_defaultFramebufferMetalTexture[current_swapchain_index].texture release]; | |||||
| id<MTLDevice> device = m_metalLayer.device; | |||||
| MTLTextureDescriptor *overlayDesc = [MTLTextureDescriptor | |||||
| texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA16Float | |||||
| width:width | |||||
| height:height | |||||
| mipmapped:NO]; | |||||
| overlayDesc.storageMode = MTLStorageModePrivate; | |||||
| overlayDesc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; | |||||
| id<MTLTexture> overlayTex = [device newTextureWithDescriptor:overlayDesc]; | |||||
| if (!overlayTex) { | |||||
| ghost_fatal_error_dialog( | |||||
| "GHOST_ContextCGL::metalUpdateFramebuffer: failed to create Metal overlay texture!"); | |||||
| } | |||||
| else { | |||||
| overlayTex.label = [NSString | |||||
| stringWithFormat:@"Metal Overlay for GHOST Context %p", this]; //@""; | |||||
| // NSLog(@"Created new Metal Overlay (backbuffer) for context %p\n", this); | |||||
| } | |||||
| m_defaultFramebufferMetalTexture[current_swapchain_index].texture = | |||||
| overlayTex; //[(MTLTexture *)overlayTex retain]; | |||||
| /* Clear texture on create */ | |||||
| id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer]; | |||||
| MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; | |||||
| { | |||||
| auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0]; | |||||
| attachment.texture = m_defaultFramebufferMetalTexture[current_swapchain_index].texture; | |||||
| attachment.loadAction = MTLLoadActionClear; | |||||
| attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000); | |||||
| attachment.storeAction = MTLStoreActionStore; | |||||
| } | |||||
| { | |||||
| id<MTLRenderCommandEncoder> enc = [cmdBuffer | |||||
| renderCommandEncoderWithDescriptor:passDescriptor]; | |||||
| [enc endEncoding]; | |||||
| } | |||||
| [cmdBuffer commit]; | |||||
| } | |||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer); | glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer); | ||||
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0); | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0); | ||||
| #endif | |||||
| } | |||||
| [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)]; | [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)]; | ||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| CVPixelBufferRelease(cvPixelBuffer); | CVPixelBufferRelease(cvPixelBuffer); | ||||
| CVOpenGLTextureCacheRelease(cvGLTexCache); | CVOpenGLTextureCacheRelease(cvGLTexCache); | ||||
| CVOpenGLTextureRelease(cvGLTex); | CVOpenGLTextureRelease(cvGLTex); | ||||
| CFRelease(cvMetalTexCache); | CFRelease(cvMetalTexCache); | ||||
| CFRelease(cvMetalTex); | CFRelease(cvMetalTex); | ||||
| #endif | |||||
| } | |||||
| } | } | ||||
| void GHOST_ContextCGL::metalSwapBuffers() | void GHOST_ContextCGL::metalSwapBuffers() | ||||
| { | { | ||||
| /* clang-format off */ | /* clang-format off */ | ||||
| @autoreleasepool { | @autoreleasepool { | ||||
| /* clang-format on */ | /* clang-format on */ | ||||
| updateDrawingContext(); | updateDrawingContext(); | ||||
| glFlush(); | |||||
| assert(m_defaultFramebufferMetalTexture != 0); | if (!m_useMetalForRendering) { | ||||
| #if WITH_OPENGL | |||||
| glFlush(); | |||||
| assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil); | |||||
| #endif | |||||
| } | |||||
| id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable]; | id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable]; | ||||
| if (!drawable) { | if (!drawable) { | ||||
| return; | return; | ||||
| } | } | ||||
| id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer]; | |||||
| MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; | MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; | ||||
| { | { | ||||
| auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0]; | auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0]; | ||||
| attachment.texture = drawable.texture; | attachment.texture = drawable.texture; | ||||
| attachment.loadAction = MTLLoadActionDontCare; | attachment.loadAction = MTLLoadActionClear; | ||||
| attachment.clearColor = MTLClearColorMake(1.0, 0.294, 0.294, 1.000); | |||||
| attachment.storeAction = MTLStoreActionStore; | attachment.storeAction = MTLStoreActionStore; | ||||
| } | } | ||||
| id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture; | if (!m_useMetalForRendering) { | ||||
| id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer]; | |||||
| { | { | ||||
| assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil); | |||||
| id<MTLRenderCommandEncoder> enc = [cmdBuffer | id<MTLRenderCommandEncoder> enc = [cmdBuffer | ||||
| renderCommandEncoderWithDescriptor:passDescriptor]; | renderCommandEncoderWithDescriptor:passDescriptor]; | ||||
| [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline]; | [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline]; | ||||
| [enc setFragmentTexture:srcTexture atIndex:0]; | [enc setFragmentTexture:m_defaultFramebufferMetalTexture[current_swapchain_index].texture | ||||
| atIndex:0]; | |||||
| [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; | [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; | ||||
| [enc endEncoding]; | [enc endEncoding]; | ||||
| } | } | ||||
| [cmdBuffer presentDrawable:drawable]; | [cmdBuffer presentDrawable:drawable]; | ||||
| /* Submit command buffer */ | |||||
| [cmdBuffer commit]; | [cmdBuffer commit]; | ||||
| } | } | ||||
| else { | |||||
| assert(contextPresentCallback); | |||||
| assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil); | |||||
| (*contextPresentCallback)(passDescriptor, | |||||
| (id<MTLRenderPipelineState>)m_metalRenderPipeline, | |||||
| m_defaultFramebufferMetalTexture[current_swapchain_index].texture, | |||||
| drawable); | |||||
| } | |||||
| } | |||||
| } | |||||
| void GHOST_ContextCGL::initClear() | |||||
| { | |||||
| if (!m_useMetalForRendering) { | |||||
| #if WITH_OPENGL | |||||
| glClearColor(0.294, 0.294, 0.294, 0.000); | |||||
| glClear(GL_COLOR_BUFFER_BIT); | |||||
| glClearColor(0.000, 0.000, 0.000, 0.000); | |||||
| #endif | |||||
| } | |||||
| else { | |||||
| #if WITH_METAL | |||||
| // TODO (mg_gpusw_apple) this path is never taken, this is legacy left from inital integration | |||||
| // of metal and gl, the whole file should be cleaned up and stripped of the legacy path | |||||
| id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer]; | |||||
| MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; | |||||
| { | |||||
| auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0]; | |||||
| attachment.texture = m_defaultFramebufferMetalTexture[current_swapchain_index].texture; | |||||
| attachment.loadAction = MTLLoadActionClear; | |||||
| attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000); | |||||
| attachment.storeAction = MTLStoreActionStore; | |||||
| } | |||||
| // encoding | |||||
| { | |||||
| id<MTLRenderCommandEncoder> enc = [cmdBuffer | |||||
| renderCommandEncoderWithDescriptor:passDescriptor]; | |||||
| [enc endEncoding]; | |||||
| } | |||||
| [cmdBuffer commit]; | |||||
| #endif | |||||
| } | |||||
| } | } | ||||