1// Copyright 2019 The SwiftShader Authors. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15#include "MetalSurface.hpp" 16#include "Vulkan/VkDeviceMemory.hpp" 17#include "Vulkan/VkImage.hpp" 18 19#include <Metal/Metal.h> 20#include <QuartzCore/CAMetalLayer.h> 21#include <AppKit/NSView.h> 22 23namespace vk { 24 25class MetalLayer 26{ 27public: 28 void initWithLayer(const void* pLayer) API_AVAILABLE(macosx(10.11)) 29 { 30 view = nullptr; 31 layer = nullptr; 32 33 id<NSObject> obj = (id<NSObject>)pLayer; 34 35 if(!NSThread.isMainThread) 36 { 37 UNREACHABLE("MetalLayer::init(): not called from main thread"); 38 } 39 if([obj isKindOfClass: [CAMetalLayer class]]) 40 { 41 layer = (CAMetalLayer*)[obj retain]; 42 layer.framebufferOnly = false; 43 layer.device = MTLCreateSystemDefaultDevice(); 44 } 45 else 46 { 47 UNREACHABLE("MetalLayer::init(): view doesn't have metal backed layer"); 48 } 49 } 50 51 void initWithView(const void* pView) API_AVAILABLE(macosx(10.11)) 52 { 53 view = nullptr; 54 layer = nullptr; 55 56 id<NSObject> obj = (id<NSObject>)pView; 57 58 if([obj isKindOfClass: [NSView class]]) 59 { 60 NSView* objView = (NSView*)[obj retain]; 61 62 initWithLayer(objView.layer); 63 64 view = objView; 65 } 66 } 67 68 void release() API_AVAILABLE(macosx(10.11)) 69 { 70 if(layer) 71 { 72 [layer.device release]; 73 [layer release]; 74 } 75 if(view) 76 { 77 [view release]; 78 } 79 } 80 81 // Synchronizes the drawableSize to layer.bounds.size * layer.contentsScale and returns the new value of 82 // drawableSize. 83 VkExtent2D syncExtent() const API_AVAILABLE(macosx(10.11)) 84 { 85 if(layer) 86 { 87 CGSize drawSize = layer.bounds.size; 88 CGFloat scaleFactor = layer.contentsScale; 89 drawSize.width = trunc(drawSize.width * scaleFactor); 90 drawSize.height = trunc(drawSize.height * scaleFactor); 91 92 [layer setDrawableSize: drawSize]; 93 94 return { static_cast<uint32_t>(drawSize.width), static_cast<uint32_t>(drawSize.height) }; 95 } 96 else 97 { 98 return { 0, 0 }; 99 } 100 } 101 102 id<CAMetalDrawable> getNextDrawable() const API_AVAILABLE(macosx(10.11)) 103 { 104 if(layer) 105 { 106 return [layer nextDrawable]; 107 } 108 109 return nil; 110 } 111 112 VkExtent2D getDrawableSize() const API_AVAILABLE(macosx(10.11)) { 113 if (layer) { 114 return { 115 static_cast<uint32_t>([layer drawableSize].width), 116 static_cast<uint32_t>([layer drawableSize].height), 117 }; 118 } 119 return {0, 0}; 120 } 121 122private: 123 NSView* view; 124 CAMetalLayer* layer API_AVAILABLE(macosx(10.11)); 125}; 126 127MetalSurface::MetalSurface(const void *pCreateInfo, void *mem) : metalLayer(reinterpret_cast<MetalLayer*>(mem)) 128{ 129 130} 131 132void MetalSurface::destroySurface(const VkAllocationCallbacks *pAllocator) API_AVAILABLE(macosx(10.11)) 133{ 134 if(metalLayer) 135 { 136 metalLayer->release(); 137 } 138 139 vk::freeHostMemory(metalLayer, pAllocator); 140} 141 142size_t MetalSurface::ComputeRequiredAllocationSize(const void *pCreateInfo) API_AVAILABLE(macosx(10.11)) 143{ 144 return sizeof(MetalLayer); 145} 146 147VkResult MetalSurface::getSurfaceCapabilities(const void *pSurfaceInfoPNext, 148 VkSurfaceCapabilitiesKHR *pSurfaceCapabilities, 149 void *pSurfaceCapabilitiesPNext) const 150 API_AVAILABLE(macosx(10.11)) 151{ 152 // The value of drawableSize in CAMetalLayer is set the first time a drawable is queried but after that it is the 153 // (Metal) application's responsibility to resize the drawable when the window is resized. The best time for Swiftshader 154 // to resize the drawable is when querying the capabilities of the swapchain as that's done when the Vulkan application 155 // is trying to handle a window resize. 156 VkExtent2D extent = metalLayer->syncExtent(); 157 pSurfaceCapabilities->currentExtent = extent; 158 pSurfaceCapabilities->minImageExtent = extent; 159 pSurfaceCapabilities->maxImageExtent = extent; 160 161 setCommonSurfaceCapabilities(pSurfaceInfoPNext, pSurfaceCapabilities, 162 pSurfaceCapabilitiesPNext); 163 return VK_SUCCESS; 164} 165 166VkResult MetalSurface::present(PresentImage* image) API_AVAILABLE(macosx(10.11)) 167{ 168 @autoreleasepool 169 { 170 auto drawable = metalLayer->getNextDrawable(); 171 if(drawable) 172 { 173 const VkExtent3D &extent = image->getImage()->getExtent(); 174 VkExtent2D drawableExtent = metalLayer->getDrawableSize(); 175 176 if(drawableExtent.width != extent.width || drawableExtent.height != extent.height) 177 { 178 return VK_ERROR_OUT_OF_DATE_KHR; 179 } 180 181 [drawable.texture replaceRegion:MTLRegionMake2D(0, 0, extent.width, extent.height) 182 mipmapLevel:0 183 withBytes:image->getImageMemory()->getOffsetPointer(0) 184 bytesPerRow:image->getImage()->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0)]; 185 [drawable present]; 186 187 } 188 } 189 return VK_SUCCESS; 190} 191 192#ifdef VK_USE_PLATFORM_METAL_EXT 193MetalSurfaceEXT::MetalSurfaceEXT(const VkMetalSurfaceCreateInfoEXT *pCreateInfo, void *mem) API_AVAILABLE(macosx(10.11)) 194 : MetalSurface(pCreateInfo, mem) 195{ 196 metalLayer->initWithLayer(pCreateInfo->pLayer); 197} 198#endif 199 200#ifdef VK_USE_PLATFORM_MACOS_MVK 201MacOSSurfaceMVK::MacOSSurfaceMVK(const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, void *mem) API_AVAILABLE(macosx(10.11)) 202 : MetalSurface(pCreateInfo, mem) 203{ 204 metalLayer->initWithView(pCreateInfo->pView); 205} 206#endif 207 208} 209