| /* |
| * MVKImage.mm |
| * |
| * Copyright (c) 2015-2020 The Brenwill Workshop Ltd. (http://www.brenwill.com) |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "MVKImage.h" |
| #include "MVKQueue.h" |
| #include "MVKSwapchain.h" |
| #include "MVKCommandBuffer.h" |
| #include "MVKCmdDebug.h" |
| #include "MVKEnvironment.h" |
| #include "MVKFoundation.h" |
| #include "MVKOSExtensions.h" |
| #include "MVKLogging.h" |
| #include "MVKCodec.h" |
| #import "MTLTextureDescriptor+MoltenVK.h" |
| #import "MTLSamplerDescriptor+MoltenVK.h" |
| |
| using namespace std; |
| using namespace SPIRV_CROSS_NAMESPACE; |
| |
| #pragma mark - |
| #pragma mark MVKImagePlane |
| |
| MVKVulkanAPIObject* MVKImagePlane::getVulkanAPIObject() { return _image; } |
| |
| id<MTLTexture> MVKImagePlane::getMTLTexture() { |
| if ( !_mtlTexture && _image->_vkFormat ) { |
| // Lock and check again in case another thread has created the texture. |
| lock_guard<mutex> lock(_image->_lock); |
| if (_mtlTexture) { return _mtlTexture; } |
| |
| MTLTextureDescriptor* mtlTexDesc = newMTLTextureDescriptor(); // temp retain |
| MVKImageMemoryBinding* memoryBinding = getMemoryBinding(); |
| |
| if (_image->_ioSurface) { |
| _mtlTexture = [_image->getMTLDevice() |
| newTextureWithDescriptor: mtlTexDesc |
| iosurface: _image->_ioSurface |
| plane: _planeIndex]; |
| } else if (memoryBinding->_usesTexelBuffer) { |
| _mtlTexture = [memoryBinding->_deviceMemory->getMTLBuffer() |
| newTextureWithDescriptor: mtlTexDesc |
| offset: memoryBinding->getDeviceMemoryOffset() |
| bytesPerRow: _subresources[0].layout.rowPitch]; |
| } else if (memoryBinding->_deviceMemory->getMTLHeap() && !_image->getIsDepthStencil()) { |
| // Metal support for depth/stencil from heaps is flaky |
| _mtlTexture = [memoryBinding->_deviceMemory->getMTLHeap() |
| newTextureWithDescriptor: mtlTexDesc |
| offset: memoryBinding->getDeviceMemoryOffset()]; |
| if (_image->_isAliasable) { [_mtlTexture makeAliasable]; } |
| } else { |
| _mtlTexture = [_image->getMTLDevice() newTextureWithDescriptor: mtlTexDesc]; |
| } |
| |
| [mtlTexDesc release]; // temp release |
| |
| propagateDebugName(); |
| } |
| return _mtlTexture; |
| } |
| |
| id<MTLTexture> MVKImagePlane::getMTLTexture(MTLPixelFormat mtlPixFmt) { |
| // Note: Retrieve the base texture outside of lock to avoid deadlock if it too needs to be lazily created. |
| // Delegate to _image in case the method is overriden. (e.g. if it's a swapchain image) |
| if (mtlPixFmt == _mtlPixFmt) { return _image->getMTLTexture(_planeIndex); } |
| id<MTLTexture> mtlTex = _mtlTextureViews[mtlPixFmt]; |
| if ( !mtlTex ) { |
| // Lock and check again in case another thread has created the view texture. |
| id<MTLTexture> baseTexture = _image->getMTLTexture(_planeIndex); |
| lock_guard<mutex> lock(_image->_lock); |
| mtlTex = _mtlTextureViews[mtlPixFmt]; |
| if ( !mtlTex ) { |
| mtlTex = [baseTexture newTextureViewWithPixelFormat: mtlPixFmt]; // retained |
| _mtlTextureViews[mtlPixFmt] = mtlTex; |
| } |
| } |
| return mtlTex; |
| } |
| |
| void MVKImagePlane::releaseMTLTexture() { |
| [_mtlTexture release]; |
| for (auto elem : _mtlTextureViews) { |
| [elem.second release]; |
| } |
| _mtlTextureViews.clear(); |
| } |
| |
| // Returns a Metal texture descriptor constructed from the properties of this image. |
| // It is the caller's responsibility to release the returned descriptor object. |
| MTLTextureDescriptor* MVKImagePlane::newMTLTextureDescriptor() { |
| MTLPixelFormat mtlPixFmt = _mtlPixFmt; |
| MTLTextureUsage minUsage = MTLTextureUsageUnknown; |
| #if MVK_MACOS |
| if (_image->_is3DCompressed) { |
| // Metal before 3.0 doesn't support 3D compressed textures, so we'll decompress |
| // the texture ourselves. This, then, is the *uncompressed* format. |
| mtlPixFmt = MTLPixelFormatBGRA8Unorm; |
| minUsage = MTLTextureUsageShaderWrite; |
| } |
| #endif |
| |
| VkExtent3D extent = _image->getExtent3D(_planeIndex, 0); |
| MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor new]; // retained |
| mtlTexDesc.pixelFormat = mtlPixFmt; |
| mtlTexDesc.textureType = _image->_mtlTextureType; |
| mtlTexDesc.width = extent.width; |
| mtlTexDesc.height = extent.height; |
| mtlTexDesc.depth = extent.depth; |
| mtlTexDesc.mipmapLevelCount = _image->_mipLevels; |
| mtlTexDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(_image->_samples); |
| mtlTexDesc.arrayLength = _image->_arrayLayers; |
| mtlTexDesc.usageMVK = _image->getPixelFormats()->getMTLTextureUsage(_image->_usage, mtlPixFmt, minUsage, _image->_isLinear); |
| mtlTexDesc.storageModeMVK = _image->getMTLStorageMode(); |
| mtlTexDesc.cpuCacheMode = _image->getMTLCPUCacheMode(); |
| |
| return mtlTexDesc; |
| } |
| |
| // Initializes the subresource definitions. |
| void MVKImagePlane::initSubresources(const VkImageCreateInfo* pCreateInfo) { |
| _subresources.reserve(_image->_mipLevels * _image->_arrayLayers); |
| |
| MVKImageSubresource subRez; |
| subRez.subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << _planeIndex; |
| subRez.layoutState = pCreateInfo->initialLayout; |
| |
| VkDeviceSize offset = 0; |
| if (_planeIndex > 0 && _image->_memoryBindings.size() == 1) { |
| auto subresources = &_image->_planes[_planeIndex-1]->_subresources; |
| VkSubresourceLayout& lastLayout = (*subresources)[subresources->size()-1].layout; |
| offset = lastLayout.offset+lastLayout.size; |
| } |
| |
| for (uint32_t mipLvl = 0; mipLvl < _image->_mipLevels; mipLvl++) { |
| subRez.subresource.mipLevel = mipLvl; |
| VkDeviceSize rowPitch = _image->getBytesPerRow(_planeIndex, mipLvl); |
| VkDeviceSize depthPitch = _image->getBytesPerLayer(_planeIndex, mipLvl); |
| |
| VkExtent3D mipExtent = _image->getExtent3D(_planeIndex, mipLvl); |
| |
| for (uint32_t layer = 0; layer < _image->_arrayLayers; layer++) { |
| subRez.subresource.arrayLayer = layer; |
| |
| VkSubresourceLayout& layout = subRez.layout; |
| layout.offset = offset; |
| layout.size = depthPitch * mipExtent.depth; |
| |
| layout.rowPitch = rowPitch; |
| layout.depthPitch = depthPitch; |
| |
| _subresources.push_back(subRez); |
| offset += layout.size; |
| } |
| } |
| } |
| |
| // Returns a pointer to the internal subresource for the specified MIP level layer. |
| MVKImageSubresource* MVKImagePlane::getSubresource(uint32_t mipLevel, uint32_t arrayLayer) { |
| uint32_t srIdx = (mipLevel * _image->_arrayLayers) + arrayLayer; |
| return (srIdx < _subresources.size()) ? &_subresources[srIdx] : NULL; |
| } |
| |
| // Updates the contents of the underlying MTLTexture, corresponding to the |
| // specified subresource definition, from the underlying memory buffer. |
| void MVKImagePlane::updateMTLTextureContent(MVKImageSubresource& subresource, |
| VkDeviceSize offset, VkDeviceSize size) { |
| |
| VkImageSubresource& imgSubRez = subresource.subresource; |
| VkSubresourceLayout& imgLayout = subresource.layout; |
| size = getMemoryBinding()->getDeviceMemory()->adjustMemorySize(size, offset); |
| // Check if subresource overlaps the memory range. |
| if ( !overlaps(imgLayout, offset, size) ) { return; } |
| |
| // Don't update if host memory has not been mapped yet. |
| void* pHostMem = getMemoryBinding()->getHostMemoryAddress(); |
| if ( !pHostMem ) { return; } |
| |
| VkExtent3D mipExtent = _image->getExtent3D(_planeIndex, imgSubRez.mipLevel); |
| void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset); |
| |
| MTLRegion mtlRegion; |
| mtlRegion.origin = MTLOriginMake(0, 0, 0); |
| mtlRegion.size = mvkMTLSizeFromVkExtent3D(mipExtent); |
| |
| #if MVK_MACOS |
| std::unique_ptr<char[]> decompBuffer; |
| if (_image->_is3DCompressed) { |
| // We cannot upload the texture data directly in this case. But we |
| // can upload the decompressed image data. |
| std::unique_ptr<MVKCodec> codec = mvkCreateCodec(_image->getVkFormat()); |
| if (!codec) { |
| _image->reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "A 3D texture used a compressed format that MoltenVK does not yet support."); |
| return; |
| } |
| VkSubresourceLayout destLayout; |
| destLayout.rowPitch = 4 * mipExtent.width; |
| destLayout.depthPitch = destLayout.rowPitch * mipExtent.height; |
| destLayout.size = destLayout.depthPitch * mipExtent.depth; |
| decompBuffer = std::unique_ptr<char[]>(new char[destLayout.size]); |
| codec->decompress(decompBuffer.get(), pImgBytes, destLayout, imgLayout, mipExtent); |
| pImgBytes = decompBuffer.get(); |
| imgLayout = destLayout; |
| } |
| #endif |
| |
| VkImageType imgType = _image->getImageType(); |
| VkDeviceSize bytesPerRow = (imgType != VK_IMAGE_TYPE_1D) ? imgLayout.rowPitch : 0; |
| VkDeviceSize bytesPerImage = (imgType == VK_IMAGE_TYPE_3D) ? imgLayout.depthPitch : 0; |
| |
| id<MTLTexture> mtlTex = getMTLTexture(); |
| if (_image->getPixelFormats()->isPVRTCFormat(mtlTex.pixelFormat)) { |
| bytesPerRow = 0; |
| bytesPerImage = 0; |
| } |
| |
| [mtlTex replaceRegion: mtlRegion |
| mipmapLevel: imgSubRez.mipLevel |
| slice: imgSubRez.arrayLayer |
| withBytes: pImgBytes |
| bytesPerRow: bytesPerRow |
| bytesPerImage: bytesPerImage]; |
| } |
| |
| // Updates the contents of the underlying memory buffer from the contents of |
| // the underlying MTLTexture, corresponding to the specified subresource definition. |
| void MVKImagePlane::getMTLTextureContent(MVKImageSubresource& subresource, |
| VkDeviceSize offset, VkDeviceSize size) { |
| |
| VkImageSubresource& imgSubRez = subresource.subresource; |
| VkSubresourceLayout& imgLayout = subresource.layout; |
| |
| // Check if subresource overlaps the memory range. |
| if ( !overlaps(imgLayout, offset, size) ) { return; } |
| |
| // Don't update if host memory has not been mapped yet. |
| void* pHostMem = getMemoryBinding()->getHostMemoryAddress(); |
| if ( !pHostMem ) { return; } |
| |
| VkExtent3D mipExtent = _image->getExtent3D(_planeIndex, imgSubRez.mipLevel); |
| void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset); |
| |
| MTLRegion mtlRegion; |
| mtlRegion.origin = MTLOriginMake(0, 0, 0); |
| mtlRegion.size = mvkMTLSizeFromVkExtent3D(mipExtent); |
| |
| VkImageType imgType = _image->getImageType(); |
| VkDeviceSize bytesPerRow = (imgType != VK_IMAGE_TYPE_1D) ? imgLayout.rowPitch : 0; |
| VkDeviceSize bytesPerImage = (imgType == VK_IMAGE_TYPE_3D) ? imgLayout.depthPitch : 0; |
| |
| [_mtlTexture getBytes: pImgBytes |
| bytesPerRow: bytesPerRow |
| bytesPerImage: bytesPerImage |
| fromRegion: mtlRegion |
| mipmapLevel: imgSubRez.mipLevel |
| slice: imgSubRez.arrayLayer]; |
| } |
| |
| // Returns whether subresource layout overlaps the memory range. |
| bool MVKImagePlane::overlaps(VkSubresourceLayout& imgLayout, VkDeviceSize offset, VkDeviceSize size) { |
| VkDeviceSize memStart = offset; |
| VkDeviceSize memEnd = offset + size; |
| VkDeviceSize imgStart = getMemoryBinding()->_deviceMemoryOffset + imgLayout.offset; |
| VkDeviceSize imgEnd = imgStart + imgLayout.size; |
| return imgStart < memEnd && imgEnd > memStart; |
| } |
| |
| void MVKImagePlane::propagateDebugName() { |
| setLabelIfNotNil(_image->_planes[_planeIndex]->_mtlTexture, _image->_debugName); |
| } |
| |
| MVKImageMemoryBinding* MVKImagePlane::getMemoryBinding() const { |
| return (_image->_memoryBindings.size() > 1) ? _image->_memoryBindings[_planeIndex] : _image->_memoryBindings[0]; |
| } |
| |
| void MVKImagePlane::applyImageMemoryBarrier(VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, |
| MVKPipelineBarrier& barrier, |
| MVKCommandEncoder* cmdEncoder, |
| MVKCommandUse cmdUse) { |
| |
| // Extract the mipmap levels that are to be updated |
| uint32_t mipLvlStart = barrier.baseMipLevel; |
| uint32_t mipLvlEnd = (barrier.levelCount == (uint8_t)VK_REMAINING_MIP_LEVELS |
| ? _image->getMipLevelCount() |
| : (mipLvlStart + barrier.levelCount)); |
| |
| // Extract the cube or array layers (slices) that are to be updated |
| uint32_t layerStart = barrier.baseArrayLayer; |
| uint32_t layerEnd = (barrier.layerCount == (uint16_t)VK_REMAINING_ARRAY_LAYERS |
| ? _image->getLayerCount() |
| : (layerStart + barrier.layerCount)); |
| |
| MVKImageMemoryBinding* memBind = getMemoryBinding(); |
| bool needsSync = memBind->needsHostReadSync(srcStageMask, dstStageMask, barrier); |
| bool needsPull = (!memBind->_usesTexelBuffer && |
| memBind->isMemoryHostCoherent() && |
| barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL && |
| mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT))); |
| MVKDeviceMemory* dvcMem = memBind->getDeviceMemory(); |
| const MVKMappedMemoryRange& mappedRange = dvcMem ? dvcMem->getMappedRange() : MVKMappedMemoryRange(); |
| |
| // Iterate across mipmap levels and layers, and update the image layout state for each |
| for (uint32_t mipLvl = mipLvlStart; mipLvl < mipLvlEnd; mipLvl++) { |
| for (uint32_t layer = layerStart; layer < layerEnd; layer++) { |
| MVKImageSubresource* pImgRez = getSubresource(mipLvl, layer); |
| if (pImgRez) { pImgRez->layoutState = barrier.newLayout; } |
| if (needsSync) { |
| #if MVK_MACOS |
| [cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeTexture: _mtlTexture slice: layer level: mipLvl]; |
| #endif |
| } |
| // Check if image content should be pulled into host-mapped device memory after |
| // the GPU is done with it. This only applies if the image is intended to be |
| // host-coherent, is not using a texel buffer, is transitioning to host-readable, |
| // AND the device memory has an already-open memory mapping. If a memory mapping is |
| // created later, it will pull the image contents in at that time, so it is not needed now. |
| // The mapped range will be {0,0} if device memory is not currently mapped. |
| if (needsPull && pImgRez && overlaps(pImgRez->layout, mappedRange.offset, mappedRange.size)) { |
| pullFromDeviceOnCompletion(cmdEncoder, *pImgRez, mappedRange); |
| } |
| } |
| } |
| } |
| |
| // Once the command buffer completes, pull the content of the subresource into host memory. |
| // This is only necessary when the image memory is intended to be host-coherent, and the |
| // device memory is currently mapped to host memory |
| void MVKImagePlane::pullFromDeviceOnCompletion(MVKCommandEncoder* cmdEncoder, |
| MVKImageSubresource& subresource, |
| const MVKMappedMemoryRange& mappedRange) { |
| |
| [cmdEncoder->_mtlCmdBuffer addCompletedHandler: ^(id<MTLCommandBuffer> mcb) { |
| getMTLTextureContent(subresource, mappedRange.offset, mappedRange.size); |
| }]; |
| } |
| |
| MVKImagePlane::MVKImagePlane(MVKImage* image, uint8_t planeIndex) { |
| _image = image; |
| _planeIndex = planeIndex; |
| _mtlTexture = nil; |
| } |
| |
| MVKImagePlane::~MVKImagePlane() { |
| releaseMTLTexture(); |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKImageMemoryBinding |
| |
| VkResult MVKImageMemoryBinding::getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) { |
| pMemoryRequirements->size = _byteCount; |
| pMemoryRequirements->alignment = _byteAlignment; |
| return VK_SUCCESS; |
| } |
| |
| VkResult MVKImageMemoryBinding::getMemoryRequirements(const void*, VkMemoryRequirements2* pMemoryRequirements) { |
| pMemoryRequirements->sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; |
| for (auto* next = (VkBaseOutStructure*)pMemoryRequirements->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS: { |
| auto* dedicatedReqs = (VkMemoryDedicatedRequirements*)next; |
| bool writable = mvkIsAnyFlagEnabled(_image->_usage, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); |
| dedicatedReqs->requiresDedicatedAllocation = _requiresDedicatedMemoryAllocation; |
| dedicatedReqs->prefersDedicatedAllocation = (dedicatedReqs->requiresDedicatedAllocation || |
| (!_usesTexelBuffer && (writable || !_device->_pMetalFeatures->placementHeaps))); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return VK_SUCCESS; |
| } |
| |
| // Memory may have been mapped before image was bound, and needs to be loaded into the MTLTexture. |
| VkResult MVKImageMemoryBinding::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) { |
| if (_deviceMemory) { _deviceMemory->removeImageMemoryBinding(this); } |
| MVKResource::bindDeviceMemory(mvkMem, memOffset); |
| |
| _usesTexelBuffer = _device->_pMetalFeatures->texelBuffers && _deviceMemory && _deviceMemory->_mtlBuffer; // Texel buffers available |
| _usesTexelBuffer = _usesTexelBuffer && (isMemoryHostAccessible() || _device->_pMetalFeatures->placementHeaps) && _image->_isLinear && !_image->getIsCompressed(); // Applicable memory layout |
| |
| #if MVK_MACOS |
| // macOS cannot use shared memory for texel buffers. |
| _usesTexelBuffer = _usesTexelBuffer && !isMemoryHostCoherent(); |
| #endif |
| |
| flushToDevice(getDeviceMemoryOffset(), getByteCount()); |
| return _deviceMemory ? _deviceMemory->addImageMemoryBinding(this) : VK_SUCCESS; |
| } |
| |
| void MVKImageMemoryBinding::applyMemoryBarrier(VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, |
| MVKPipelineBarrier& barrier, |
| MVKCommandEncoder* cmdEncoder, |
| MVKCommandUse cmdUse) { |
| #if MVK_MACOS |
| if ( needsHostReadSync(srcStageMask, dstStageMask, barrier) ) { |
| for(uint8_t planeIndex = beginPlaneIndex(); planeIndex < endPlaneIndex(); planeIndex++) { |
| [cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeResource: _image->_planes[planeIndex]->_mtlTexture]; |
| } |
| } |
| #endif |
| } |
| |
| void MVKImageMemoryBinding::propagateDebugName() { |
| for(uint8_t planeIndex = beginPlaneIndex(); planeIndex < endPlaneIndex(); planeIndex++) { |
| _image->_planes[planeIndex]->propagateDebugName(); |
| } |
| } |
| |
| // Returns whether the specified image memory barrier requires a sync between this |
| // texture and host memory for the purpose of the host reading texture memory. |
| bool MVKImageMemoryBinding::needsHostReadSync(VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, |
| MVKPipelineBarrier& barrier) { |
| #if MVK_MACOS |
| // On macOS, texture memory is never host-coherent, so don't test for it. |
| return ((barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL) && |
| mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)) && |
| isMemoryHostAccessible()); |
| #endif |
| #if MVK_IOS_OR_TVOS |
| return false; |
| #endif |
| } |
| |
| bool MVKImageMemoryBinding::shouldFlushHostMemory() { return isMemoryHostAccessible() && !_usesTexelBuffer; } |
| |
| // Flushes the device memory at the specified memory range into the MTLTexture. Updates |
| // all subresources that overlap the specified range and are in an updatable layout state. |
| VkResult MVKImageMemoryBinding::flushToDevice(VkDeviceSize offset, VkDeviceSize size) { |
| if (shouldFlushHostMemory()) { |
| for(uint8_t planeIndex = beginPlaneIndex(); planeIndex < endPlaneIndex(); planeIndex++) { |
| for (auto& subRez : _image->_planes[planeIndex]->_subresources) { |
| switch (subRez.layoutState) { |
| case VK_IMAGE_LAYOUT_UNDEFINED: |
| case VK_IMAGE_LAYOUT_PREINITIALIZED: |
| case VK_IMAGE_LAYOUT_GENERAL: { |
| _image->_planes[planeIndex]->updateMTLTextureContent(subRez, offset, size); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return VK_SUCCESS; |
| } |
| |
| // Pulls content from the MTLTexture into the device memory at the specified memory range. |
| // Pulls from all subresources that overlap the specified range and are in an updatable layout state. |
| VkResult MVKImageMemoryBinding::pullFromDevice(VkDeviceSize offset, VkDeviceSize size) { |
| if (shouldFlushHostMemory()) { |
| for(uint8_t planeIndex = beginPlaneIndex(); planeIndex < endPlaneIndex(); planeIndex++) { |
| for (auto& subRez : _image->_planes[planeIndex]->_subresources) { |
| switch (subRez.layoutState) { |
| case VK_IMAGE_LAYOUT_GENERAL: { |
| _image->_planes[planeIndex]->getMTLTextureContent(subRez, offset, size); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return VK_SUCCESS; |
| } |
| |
| uint8_t MVKImageMemoryBinding::beginPlaneIndex() const { |
| return (_image->_memoryBindings.size() > 1) ? _planeIndex : 0; |
| } |
| |
| uint8_t MVKImageMemoryBinding::endPlaneIndex() const { |
| return (_image->_memoryBindings.size() > 1) ? _planeIndex : (uint8_t)_image->_memoryBindings.size(); |
| } |
| |
| MVKImageMemoryBinding::MVKImageMemoryBinding(MVKDevice* device, MVKImage* image, uint8_t planeIndex) : MVKResource(device) { |
| _image = image; |
| _planeIndex = planeIndex; |
| _usesTexelBuffer = false; |
| } |
| |
| MVKImageMemoryBinding::~MVKImageMemoryBinding() { |
| if (_deviceMemory) { _deviceMemory->removeImageMemoryBinding(this); } |
| } |
| |
| |
| #pragma mark MVKImage |
| |
| uint8_t MVKImage::getPlaneFromVkImageAspectFlags(VkImageAspectFlags aspectMask) { |
| return (aspectMask & VK_IMAGE_ASPECT_PLANE_2_BIT) ? 2 : |
| (aspectMask & VK_IMAGE_ASPECT_PLANE_1_BIT) ? 1 : |
| 0; |
| } |
| |
| void MVKImage::propagateDebugName() { |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); planeIndex++) { |
| _planes[planeIndex]->propagateDebugName(); |
| } |
| } |
| |
| void MVKImage::flushToDevice(VkDeviceSize offset, VkDeviceSize size) { |
| for (int bindingIndex = 0; bindingIndex < _memoryBindings.size(); bindingIndex++) { |
| MVKImageMemoryBinding *binding = _memoryBindings[bindingIndex]; |
| binding->flushToDevice(offset, size); |
| } |
| } |
| |
| VkImageType MVKImage::getImageType() { return mvkVkImageTypeFromMTLTextureType(_mtlTextureType); } |
| |
| bool MVKImage::getIsDepthStencil() { return getPixelFormats()->getFormatType(_vkFormat) == kMVKFormatDepthStencil; } |
| |
| bool MVKImage::getIsCompressed() { return getPixelFormats()->getFormatType(_vkFormat) == kMVKFormatCompressed; } |
| |
| VkExtent3D MVKImage::getExtent3D(uint8_t planeIndex, uint32_t mipLevel) { |
| VkExtent3D extent = _extent; |
| if (_hasChromaSubsampling) { |
| extent.width /= _planes[planeIndex]->_blockTexelSize.width; |
| extent.height /= _planes[planeIndex]->_blockTexelSize.height; |
| } |
| return mvkMipmapLevelSizeFromBaseSize3D(extent, mipLevel); |
| } |
| |
| VkDeviceSize MVKImage::getBytesPerRow(uint8_t planeIndex, uint32_t mipLevel) { |
| size_t bytesPerRow = getPixelFormats()->getBytesPerRow(_vkFormat, getExtent3D(planeIndex, mipLevel).width); |
| return mvkAlignByteCount(bytesPerRow, _rowByteAlignment); |
| } |
| |
| VkDeviceSize MVKImage::getBytesPerLayer(uint8_t planeIndex, uint32_t mipLevel) { |
| VkExtent3D extent = getExtent3D(planeIndex, mipLevel); |
| size_t bytesPerRow = getBytesPerRow(planeIndex, mipLevel); |
| return getPixelFormats()->getBytesPerLayer(_vkFormat, bytesPerRow, extent.height); |
| } |
| |
| VkResult MVKImage::getSubresourceLayout(const VkImageSubresource* pSubresource, |
| VkSubresourceLayout* pLayout) { |
| uint8_t planeIndex = MVKImage::getPlaneFromVkImageAspectFlags(pSubresource->aspectMask); |
| MVKImageSubresource* pImgRez = _planes[planeIndex]->getSubresource(pSubresource->mipLevel, pSubresource->arrayLayer); |
| if ( !pImgRez ) { return VK_INCOMPLETE; } |
| |
| *pLayout = pImgRez->layout; |
| return VK_SUCCESS; |
| } |
| |
| void MVKImage::getTransferDescriptorData(MVKImageDescriptorData& imgData) { |
| imgData.imageType = getImageType(); |
| imgData.format = getVkFormat(); |
| imgData.extent = _extent; |
| imgData.mipLevels = _mipLevels; |
| imgData.arrayLayers = _arrayLayers; |
| imgData.samples = _samples; |
| imgData.usage = _usage; |
| } |
| |
| |
| #pragma mark Resource memory |
| |
| void MVKImage::applyImageMemoryBarrier(VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, |
| MVKPipelineBarrier& barrier, |
| MVKCommandEncoder* cmdEncoder, |
| MVKCommandUse cmdUse) { |
| |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); planeIndex++) { |
| if ( !_hasChromaSubsampling || mvkIsAnyFlagEnabled(barrier.aspectMask, (VK_IMAGE_ASPECT_PLANE_0_BIT << planeIndex)) ) { |
| _planes[planeIndex]->applyImageMemoryBarrier(srcStageMask, dstStageMask, barrier, cmdEncoder, cmdUse); |
| } |
| } |
| } |
| |
| VkResult MVKImage::getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements, uint8_t planeIndex) { |
| pMemoryRequirements->memoryTypeBits = (_isDepthStencilAttachment) |
| ? _device->getPhysicalDevice()->getPrivateMemoryTypes() |
| : _device->getPhysicalDevice()->getAllMemoryTypes(); |
| #if MVK_MACOS |
| // Metal on macOS does not provide native support for host-coherent memory, but Vulkan requires it for Linear images |
| if ( !_isLinear ) { |
| mvkDisableFlags(pMemoryRequirements->memoryTypeBits, _device->getPhysicalDevice()->getHostCoherentMemoryTypes()); |
| } |
| #endif |
| #if MVK_IOS_OR_TVOS |
| // Only transient attachments may use memoryless storage |
| if (!mvkAreAllFlagsEnabled(_usage, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT) ) { |
| mvkDisableFlags(pMemoryRequirements->memoryTypeBits, _device->getPhysicalDevice()->getLazilyAllocatedMemoryTypes()); |
| } |
| #endif |
| return _memoryBindings[planeIndex]->getMemoryRequirements(pMemoryRequirements); |
| } |
| |
| VkResult MVKImage::getMemoryRequirements(const void* pInfo, VkMemoryRequirements2* pMemoryRequirements) { |
| uint8_t planeIndex = 0; |
| for (auto* next = (VkBaseOutStructure*)pMemoryRequirements->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO: { |
| auto* planeReqs = (VkImagePlaneMemoryRequirementsInfo*)next; |
| planeIndex = MVKImage::getPlaneFromVkImageAspectFlags(planeReqs->planeAspect); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return getMemoryRequirements(&pMemoryRequirements->memoryRequirements, planeIndex); |
| } |
| |
| VkResult MVKImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset, uint8_t planeIndex) { |
| return _memoryBindings[planeIndex]->bindDeviceMemory(mvkMem, memOffset); |
| } |
| |
| VkResult MVKImage::bindDeviceMemory2(const VkBindImageMemoryInfo* pBindInfo) { |
| uint8_t planeIndex = 0; |
| for (const auto* next = (const VkBaseInStructure*)pBindInfo->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO: { |
| const VkBindImagePlaneMemoryInfo* imagePlaneMemoryInfo = (const VkBindImagePlaneMemoryInfo*)next; |
| planeIndex = MVKImage::getPlaneFromVkImageAspectFlags(imagePlaneMemoryInfo->planeAspect); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return bindDeviceMemory((MVKDeviceMemory*)pBindInfo->memory, pBindInfo->memoryOffset, planeIndex); |
| } |
| |
| |
| #pragma mark Metal |
| |
| id<MTLTexture> MVKImage::getMTLTexture(uint8_t planeIndex) { |
| return _planes[planeIndex]->getMTLTexture(); |
| } |
| |
| id<MTLTexture> MVKImage::getMTLTexture(uint8_t planeIndex, MTLPixelFormat mtlPixFmt) { |
| return _planes[planeIndex]->getMTLTexture(mtlPixFmt); |
| } |
| |
| VkResult MVKImage::setMTLTexture(uint8_t planeIndex, id<MTLTexture> mtlTexture) { |
| lock_guard<mutex> lock(_lock); |
| |
| releaseIOSurface(); |
| _planes[planeIndex]->releaseMTLTexture(); |
| _planes[planeIndex]->_mtlTexture = [mtlTexture retain]; // retained |
| |
| _vkFormat = getPixelFormats()->getVkFormat(mtlTexture.pixelFormat); |
| _mtlTextureType = mtlTexture.textureType; |
| _extent.width = uint32_t(mtlTexture.width); |
| _extent.height = uint32_t(mtlTexture.height); |
| _extent.depth = uint32_t(mtlTexture.depth); |
| _mipLevels = uint32_t(mtlTexture.mipmapLevelCount); |
| _samples = mvkVkSampleCountFlagBitsFromSampleCount(mtlTexture.sampleCount); |
| _arrayLayers = uint32_t(mtlTexture.arrayLength); |
| _usage = getPixelFormats()->getVkImageUsageFlags(mtlTexture.usage, mtlTexture.pixelFormat); |
| |
| if (_device->_pMetalFeatures->ioSurfaces) { |
| _ioSurface = mtlTexture.iosurface; |
| CFRetain(_ioSurface); |
| } |
| |
| return VK_SUCCESS; |
| } |
| |
| void MVKImage::releaseIOSurface() { |
| if (_ioSurface) { |
| CFRelease(_ioSurface); |
| _ioSurface = nil; |
| } |
| } |
| |
| IOSurfaceRef MVKImage::getIOSurface() { return _ioSurface; } |
| |
| VkResult MVKImage::useIOSurface(IOSurfaceRef ioSurface) { |
| lock_guard<mutex> lock(_lock); |
| |
| if (!_device->_pMetalFeatures->ioSurfaces) { return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); } |
| |
| #if MVK_SUPPORT_IOSURFACE_BOOL |
| |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); planeIndex++) { |
| _planes[planeIndex]->releaseMTLTexture(); |
| } |
| releaseIOSurface(); |
| |
| MVKPixelFormats* pixFmts = getPixelFormats(); |
| |
| if (ioSurface) { |
| if (IOSurfaceGetWidth(ioSurface) != _extent.width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface width %zu does not match VkImage width %d.", IOSurfaceGetWidth(ioSurface), _extent.width); } |
| if (IOSurfaceGetHeight(ioSurface) != _extent.height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface height %zu does not match VkImage height %d.", IOSurfaceGetHeight(ioSurface), _extent.height); } |
| if (IOSurfaceGetBytesPerElement(ioSurface) != pixFmts->getBytesPerBlock(_vkFormat)) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface bytes per element %zu does not match VkImage bytes per element %d.", IOSurfaceGetBytesPerElement(ioSurface), pixFmts->getBytesPerBlock(_vkFormat)); } |
| if (IOSurfaceGetElementWidth(ioSurface) != pixFmts->getBlockTexelSize(_vkFormat).width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element width %zu does not match VkImage element width %d.", IOSurfaceGetElementWidth(ioSurface), pixFmts->getBlockTexelSize(_vkFormat).width); } |
| if (IOSurfaceGetElementHeight(ioSurface) != pixFmts->getBlockTexelSize(_vkFormat).height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element height %zu does not match VkImage element height %d.", IOSurfaceGetElementHeight(ioSurface), pixFmts->getBlockTexelSize(_vkFormat).height); } |
| if (IOSurfaceGetPlaneCount(ioSurface) != _planes.size()) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface plane count %zu does not match VkImage plane count %lu.", IOSurfaceGetPlaneCount(ioSurface), _planes.size()); } |
| if (_hasChromaSubsampling) { |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); ++planeIndex) { |
| if (IOSurfaceGetWidthOfPlane(ioSurface, planeIndex) != getExtent3D(planeIndex, 0).width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface width %zu of plane %d does not match VkImage width %d.", IOSurfaceGetWidthOfPlane(ioSurface, planeIndex), planeIndex, getExtent3D(planeIndex, 0).width); } |
| if (IOSurfaceGetHeightOfPlane(ioSurface, planeIndex) != getExtent3D(planeIndex, 0).height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface height %zu of plane %d does not match VkImage height %d.", IOSurfaceGetHeightOfPlane(ioSurface, planeIndex), planeIndex, getExtent3D(planeIndex, 0).height); } |
| if (IOSurfaceGetBytesPerElementOfPlane(ioSurface, planeIndex) != _planes[planeIndex]->_bytesPerBlock) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface bytes per element %zu of plane %d does not match VkImage bytes per element %d.", IOSurfaceGetBytesPerElementOfPlane(ioSurface, planeIndex), planeIndex, _planes[planeIndex]->_bytesPerBlock); } |
| if (IOSurfaceGetElementWidthOfPlane(ioSurface, planeIndex) != _planes[planeIndex]->_blockTexelSize.width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element width %zu of plane %d does not match VkImage element width %d.", IOSurfaceGetElementWidthOfPlane(ioSurface, planeIndex), planeIndex, _planes[planeIndex]->_blockTexelSize.width); } |
| if (IOSurfaceGetElementHeightOfPlane(ioSurface, planeIndex) != _planes[planeIndex]->_blockTexelSize.height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element height %zu of plane %d does not match VkImage element height %d.", IOSurfaceGetElementHeightOfPlane(ioSurface, planeIndex), planeIndex, _planes[planeIndex]->_blockTexelSize.height); } |
| } |
| } |
| |
| _ioSurface = ioSurface; |
| CFRetain(_ioSurface); |
| } else { |
| @autoreleasepool { |
| CFMutableDictionaryRef properties = CFDictionaryCreateMutableCopy(NULL, 0, (CFDictionaryRef)@{ |
| (id)kIOSurfaceWidth: @(_extent.width), |
| (id)kIOSurfaceHeight: @(_extent.height), |
| (id)kIOSurfaceBytesPerElement: @(pixFmts->getBytesPerBlock(_vkFormat)), |
| (id)kIOSurfaceElementWidth: @(pixFmts->getBlockTexelSize(_vkFormat).width), |
| (id)kIOSurfaceElementHeight: @(pixFmts->getBlockTexelSize(_vkFormat).height), |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| (id)kIOSurfaceIsGlobal: @(true), // Deprecated but needed for interprocess transfers |
| #pragma clang diagnostic pop |
| }); |
| if(_hasChromaSubsampling) { |
| CFMutableArrayRef planeProperties = CFArrayCreateMutable(NULL, _planes.size(), NULL); |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); ++planeIndex) { |
| CFArrayAppendValue(planeProperties, (CFDictionaryRef)@{ |
| (id)kIOSurfacePlaneWidth: @(getExtent3D(planeIndex, 0).width), |
| (id)kIOSurfacePlaneHeight: @(getExtent3D(planeIndex, 0).height), |
| (id)kIOSurfacePlaneBytesPerElement: @(_planes[planeIndex]->_bytesPerBlock), |
| (id)kIOSurfacePlaneElementWidth: @(_planes[planeIndex]->_blockTexelSize.width), |
| (id)kIOSurfacePlaneElementHeight: @(_planes[planeIndex]->_blockTexelSize.height), |
| }); |
| } |
| CFDictionaryAddValue(properties, (id)kIOSurfacePlaneInfo, planeProperties); |
| } |
| _ioSurface = IOSurfaceCreate(properties); |
| } |
| } |
| |
| #endif |
| |
| return VK_SUCCESS; |
| } |
| |
| MTLStorageMode MVKImage::getMTLStorageMode() { |
| if ( !_memoryBindings[0]->_deviceMemory ) return MTLStorageModePrivate; |
| |
| MTLStorageMode stgMode = _memoryBindings[0]->_deviceMemory->getMTLStorageMode(); |
| |
| if (_ioSurface && stgMode == MTLStorageModePrivate) { stgMode = MTLStorageModeShared; } |
| |
| #if MVK_MACOS |
| // For macOS, textures cannot use Shared storage mode, so change to Managed storage mode. |
| if (stgMode == MTLStorageModeShared) { stgMode = MTLStorageModeManaged; } |
| #endif |
| return stgMode; |
| } |
| |
| MTLCPUCacheMode MVKImage::getMTLCPUCacheMode() { |
| return _memoryBindings[0]->_deviceMemory ? _memoryBindings[0]->_deviceMemory->getMTLCPUCacheMode() : MTLCPUCacheModeDefaultCache; |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKImage::MVKImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) { |
| _ioSurface = nil; |
| |
| // Adjust the info components to be compatible with Metal, then use the modified versions to set other |
| // config info. Vulkan allows unused extent dimensions to be zero, but Metal requires minimum of one. |
| uint32_t minDim = 1; |
| _extent.width = max(pCreateInfo->extent.width, minDim); |
| _extent.height = max(pCreateInfo->extent.height, minDim); |
| _extent.depth = max(pCreateInfo->extent.depth, minDim); |
| _arrayLayers = max(pCreateInfo->arrayLayers, minDim); |
| |
| // Perform validation and adjustments before configuring other settings |
| bool isAttachment = mvkIsAnyFlagEnabled(pCreateInfo->usage, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | |
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | |
| VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | |
| VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)); |
| // Texture type depends on validated samples. Other validation depends on possibly modified texture type. |
| _samples = validateSamples(pCreateInfo, isAttachment); |
| _mtlTextureType = mvkMTLTextureTypeFromVkImageType(pCreateInfo->imageType, _arrayLayers, _samples > VK_SAMPLE_COUNT_1_BIT); |
| |
| validateConfig(pCreateInfo, isAttachment); |
| _mipLevels = validateMipLevels(pCreateInfo, isAttachment); |
| _isLinear = validateLinear(pCreateInfo, isAttachment); |
| |
| MVKPixelFormats* pixFmts = getPixelFormats(); |
| _vkFormat = pCreateInfo->format; |
| _usage = pCreateInfo->usage; |
| |
| _is3DCompressed = (getImageType() == VK_IMAGE_TYPE_3D) && (pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatCompressed) && !_device->_pMetalFeatures->native3DCompressedTextures; |
| _isDepthStencilAttachment = (mvkAreAllFlagsEnabled(pCreateInfo->usage, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) || |
| mvkAreAllFlagsEnabled(pixFmts->getVkFormatProperties(pCreateInfo->format).optimalTilingFeatures, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)); |
| _canSupportMTLTextureView = !_isDepthStencilAttachment || _device->_pMetalFeatures->stencilViews; |
| _rowByteAlignment = _isLinear ? _device->getVkFormatTexelBufferAlignment(pCreateInfo->format, this) : mvkEnsurePowerOfTwo(pixFmts->getBytesPerBlock(pCreateInfo->format)); |
| |
| VkExtent2D blockTexelSizeOfPlane[3]; |
| uint32_t bytesPerBlockOfPlane[3]; |
| MTLPixelFormat mtlPixFmtOfPlane[3]; |
| uint8_t subsamplingPlaneCount = pixFmts->getChromaSubsamplingPlanes(_vkFormat, blockTexelSizeOfPlane, bytesPerBlockOfPlane, mtlPixFmtOfPlane); |
| uint8_t planeCount = std::max(subsamplingPlaneCount, (uint8_t)1); |
| uint8_t memoryBindingCount = (pCreateInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT) ? planeCount : 1; |
| _hasChromaSubsampling = (subsamplingPlaneCount > 0); |
| |
| for (uint8_t planeIndex = 0; planeIndex < memoryBindingCount; ++planeIndex) { |
| _memoryBindings.push_back(new MVKImageMemoryBinding(device, this, planeIndex)); |
| } |
| |
| for (uint8_t planeIndex = 0; planeIndex < planeCount; ++planeIndex) { |
| _planes.push_back(new MVKImagePlane(this, planeIndex)); |
| if (_hasChromaSubsampling) { |
| _planes[planeIndex]->_blockTexelSize = blockTexelSizeOfPlane[planeIndex]; |
| _planes[planeIndex]->_bytesPerBlock = bytesPerBlockOfPlane[planeIndex]; |
| _planes[planeIndex]->_mtlPixFmt = mtlPixFmtOfPlane[planeIndex]; |
| } else { |
| _planes[planeIndex]->_mtlPixFmt = getPixelFormats()->getMTLPixelFormat(_vkFormat); |
| } |
| _planes[planeIndex]->initSubresources(pCreateInfo); |
| MVKImageMemoryBinding* memoryBinding = _planes[planeIndex]->getMemoryBinding(); |
| if (!_isLinear && _device->_pMetalFeatures->placementHeaps) { |
| MTLTextureDescriptor* mtlTexDesc = _planes[planeIndex]->newMTLTextureDescriptor(); // temp retain |
| MTLSizeAndAlign sizeAndAlign = [_device->getMTLDevice() heapTextureSizeAndAlignWithDescriptor: mtlTexDesc]; |
| [mtlTexDesc release]; |
| memoryBinding->_byteCount += sizeAndAlign.size; |
| memoryBinding->_byteAlignment = std::max(memoryBinding->_byteAlignment, (VkDeviceSize)sizeAndAlign.align); |
| _isAliasable = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_ALIAS_BIT); |
| } else { |
| for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) { |
| VkExtent3D mipExtent = getExtent3D(planeIndex, mipLvl); |
| memoryBinding->_byteCount += getBytesPerLayer(planeIndex, mipLvl) * mipExtent.depth * _arrayLayers; |
| } |
| memoryBinding->_byteAlignment = std::max(memoryBinding->_byteAlignment, _rowByteAlignment); |
| } |
| } |
| _hasExpectedTexelSize = _hasChromaSubsampling || (pixFmts->getBytesPerBlock(_planes[0]->_mtlPixFmt) == pixFmts->getBytesPerBlock(_vkFormat)); |
| |
| for (const auto* next = (const VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO: { |
| auto* pExtMemInfo = (const VkExternalMemoryImageCreateInfo*)next; |
| initExternalMemory(pExtMemInfo->handleTypes); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| |
| VkSampleCountFlagBits MVKImage::validateSamples(const VkImageCreateInfo* pCreateInfo, bool isAttachment) { |
| |
| VkSampleCountFlagBits validSamples = pCreateInfo->samples; |
| |
| if (validSamples == VK_SAMPLE_COUNT_1_BIT) { return validSamples; } |
| |
| // Don't use getImageType() because it hasn't been set yet. |
| if ( !((pCreateInfo->imageType == VK_IMAGE_TYPE_2D) || ((pCreateInfo->imageType == VK_IMAGE_TYPE_1D) && mvkTreatTexture1DAs2D())) ) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling can only be used with a 2D image type. Setting sample count to 1.")); |
| validSamples = VK_SAMPLE_COUNT_1_BIT; |
| } |
| |
| if (getPixelFormats()->getFormatType(pCreateInfo->format) == kMVKFormatCompressed) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling cannot be used with compressed images. Setting sample count to 1.")); |
| validSamples = VK_SAMPLE_COUNT_1_BIT; |
| } |
| |
| if (pCreateInfo->arrayLayers > 1) { |
| if ( !_device->_pMetalFeatures->multisampleArrayTextures ) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This device does not support multisampled array textures. Setting sample count to 1.")); |
| validSamples = VK_SAMPLE_COUNT_1_BIT; |
| } |
| if (isAttachment && !_device->_pMetalFeatures->multisampleLayeredRendering) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This device does not support rendering to multisampled array (layered) attachments. Setting sample count to 1.")); |
| validSamples = VK_SAMPLE_COUNT_1_BIT; |
| } |
| } |
| |
| return validSamples; |
| } |
| |
| void MVKImage::validateConfig(const VkImageCreateInfo* pCreateInfo, bool isAttachment) { |
| MVKPixelFormats* pixFmts = getPixelFormats(); |
| |
| bool is2D = (getImageType() == VK_IMAGE_TYPE_2D); |
| bool isCompressed = pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatCompressed; |
| |
| #if MVK_IOS_OR_TVOS |
| if (isCompressed && !is2D) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D images.")); |
| } |
| #endif |
| #if MVK_MACOS |
| if (isCompressed && !is2D) { |
| if (getImageType() != VK_IMAGE_TYPE_3D) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D or 3D images.")); |
| } else if (!_device->_pMetalFeatures->native3DCompressedTextures && !mvkCanDecodeFormat(pCreateInfo->format)) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, the %s compressed format may only be used with 2D images.", getPixelFormats()->getName(pCreateInfo->format))); |
| } |
| } |
| #endif |
| |
| if ((pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatDepthStencil) && !is2D ) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, depth/stencil formats may only be used with 2D images.")); |
| } |
| if (isAttachment && (pCreateInfo->arrayLayers > 1) && !_device->_pMetalFeatures->layeredRendering) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This device does not support rendering to array (layered) attachments.")); |
| } |
| if (isAttachment && (getImageType() == VK_IMAGE_TYPE_1D)) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Metal does not support rendering to native 1D attachments. Consider enabling MVK_CONFIG_TEXTURE_1D_AS_2D.")); |
| } |
| if (mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT)) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Metal does not allow uncompressed views of compressed images.")); |
| } |
| if (mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT)) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Metal does not support split-instance memory binding.")); |
| } |
| } |
| |
| uint32_t MVKImage::validateMipLevels(const VkImageCreateInfo* pCreateInfo, bool isAttachment) { |
| uint32_t minDim = 1; |
| uint32_t validMipLevels = max(pCreateInfo->mipLevels, minDim); |
| |
| if (validMipLevels == 1) { return validMipLevels; } |
| |
| if (getImageType() == VK_IMAGE_TYPE_1D) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, native 1D images cannot use mipmaps. Setting mip levels to 1. Consider enabling MVK_CONFIG_TEXTURE_1D_AS_2D.")); |
| validMipLevels = 1; |
| } |
| |
| return validMipLevels; |
| } |
| |
| bool MVKImage::validateLinear(const VkImageCreateInfo* pCreateInfo, bool isAttachment) { |
| |
| if (pCreateInfo->tiling != VK_IMAGE_TILING_LINEAR ) { return false; } |
| |
| bool isLin = true; |
| |
| if (getImageType() != VK_IMAGE_TYPE_2D) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, imageType must be VK_IMAGE_TYPE_2D.")); |
| isLin = false; |
| } |
| |
| if (getPixelFormats()->getFormatType(pCreateInfo->format) == kMVKFormatDepthStencil) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format.")); |
| isLin = false; |
| } |
| |
| if (pCreateInfo->mipLevels > 1) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1.")); |
| isLin = false; |
| } |
| |
| if (pCreateInfo->arrayLayers > 1) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, arrayLayers must be 1.")); |
| isLin = false; |
| } |
| |
| if (pCreateInfo->samples > VK_SAMPLE_COUNT_1_BIT) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, samples must be VK_SAMPLE_COUNT_1_BIT.")); |
| isLin = false; |
| } |
| |
| #if MVK_MACOS |
| if (isAttachment) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This device does not support rendering to linear (VK_IMAGE_TILING_LINEAR) images.")); |
| isLin = false; |
| } |
| #endif |
| |
| return isLin; |
| } |
| |
| void MVKImage::initExternalMemory(VkExternalMemoryHandleTypeFlags handleTypes) { |
| if (mvkIsOnlyAnyFlagEnabled(handleTypes, VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_KHR)) { |
| auto& xmProps = _device->getPhysicalDevice()->getExternalImageProperties(VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_KHR); |
| for(auto& memoryBinding : _memoryBindings) { |
| memoryBinding->_externalMemoryHandleTypes = handleTypes; |
| memoryBinding->_requiresDedicatedMemoryAllocation = memoryBinding->_requiresDedicatedMemoryAllocation || mvkIsAnyFlagEnabled(xmProps.externalMemoryFeatures, VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT); |
| } |
| } else { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage(): Only external memory handle type VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_KHR is supported.")); |
| } |
| } |
| |
| MVKImage::~MVKImage() { |
| mvkDestroyContainerContents(_memoryBindings); |
| mvkDestroyContainerContents(_planes); |
| releaseIOSurface(); |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKSwapchainImage |
| |
| VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset, uint8_t planeIndex) { |
| return VK_ERROR_OUT_OF_DEVICE_MEMORY; |
| } |
| |
| |
| #pragma mark Metal |
| |
| // Overridden to always retrieve the MTLTexture directly from the CAMetalDrawable. |
| id<MTLTexture> MVKSwapchainImage::getMTLTexture(uint8_t planeIndex) { return [getCAMetalDrawable() texture]; } |
| |
| |
| #pragma mark Construction |
| |
| MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device, |
| const VkImageCreateInfo* pCreateInfo, |
| MVKSwapchain* swapchain, |
| uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) { |
| _swapchain = swapchain; |
| _swapchainIndex = swapchainIndex; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKPresentableSwapchainImage |
| |
| bool MVKSwapchainImageAvailability::operator< (const MVKSwapchainImageAvailability& rhs) const { |
| if ( isAvailable && !rhs.isAvailable) { return true; } |
| if ( !isAvailable && rhs.isAvailable) { return false; } |
| return acquisitionID < rhs.acquisitionID; |
| } |
| |
| MVKSwapchainImageAvailability MVKPresentableSwapchainImage::getAvailability() { |
| lock_guard<mutex> lock(_availabilityLock); |
| |
| return _availability; |
| } |
| |
| // Makes an image available for acquisition by the app. |
| // If any semaphores are waiting to be signaled when this image becomes available, the |
| // earliest semaphore is signaled, and this image remains unavailable for other uses. |
| void MVKPresentableSwapchainImage::makeAvailable() { |
| lock_guard<mutex> lock(_availabilityLock); |
| |
| // Mark when this event happened, relative to that of other images |
| _availability.acquisitionID = _swapchain->getNextAcquisitionID(); |
| |
| // Mark this image as available if no semaphores or fences are waiting to be signaled. |
| _availability.isAvailable = _availabilitySignalers.empty(); |
| |
| MVKSwapchainSignaler signaler; |
| if (_availability.isAvailable) { |
| // If this image is available, signal the semaphore and fence that were associated |
| // with the last time this image was acquired while available. This is a workaround for |
| // when an app uses a single semaphore or fence for more than one swapchain image. |
| // Because the semaphore or fence will be signaled by more than one image, it will |
| // get out of sync, and the final use of the image would not be signaled as a result. |
| signaler = _preSignaler; |
| } else { |
| // If this image is not yet available, extract and signal the first semaphore and fence. |
| auto sigIter = _availabilitySignalers.begin(); |
| signaler = *sigIter; |
| _availabilitySignalers.erase(sigIter); |
| } |
| |
| // Signal the semaphore and fence, and let them know they are no longer being tracked. |
| signal(signaler, nil); |
| unmarkAsTracked(signaler); |
| |
| // MVKLogDebug("Signaling%s swapchain image %p semaphore %p from present, with %lu remaining semaphores.", (_availability.isAvailable ? " pre-signaled" : ""), this, signaler.first, _availabilitySignalers.size()); |
| } |
| |
| void MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) { |
| lock_guard<mutex> lock(_availabilityLock); |
| |
| // Now that this image is being acquired, release the existing drawable and its texture. |
| // This is not done earlier so the texture is retained for any post-processing such as screen captures, etc. |
| releaseMetalDrawable(); |
| |
| auto signaler = make_pair(semaphore, fence); |
| if (_availability.isAvailable) { |
| _availability.isAvailable = false; |
| |
| // If signalling through a MTLEvent, use an ephemeral MTLCommandBuffer. |
| // Another option would be to use MTLSharedEvent in MVKSemaphore, but that might |
| // impose unacceptable performance costs to handle this particular case. |
| @autoreleasepool { |
| MVKSemaphore* mvkSem = signaler.first; |
| id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding() |
| ? [_device->getAnyQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences] |
| : nil); |
| signal(signaler, mtlCmdBuff); |
| [mtlCmdBuff commit]; |
| } |
| |
| _preSignaler = signaler; |
| } else { |
| _availabilitySignalers.push_back(signaler); |
| } |
| markAsTracked(signaler); |
| |
| // MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size()); |
| } |
| |
| // If present, signal the semaphore for the first waiter for the given image. |
| void MVKPresentableSwapchainImage::signalPresentationSemaphore(id<MTLCommandBuffer> mtlCmdBuff) { |
| lock_guard<mutex> lock(_availabilityLock); |
| |
| if ( !_availabilitySignalers.empty() ) { |
| MVKSemaphore* mvkSem = _availabilitySignalers.front().first; |
| if (mvkSem) { mvkSem->encodeSignal(mtlCmdBuff); } |
| } |
| } |
| |
| // Signal either or both of the semaphore and fence in the specified tracker pair. |
| void MVKPresentableSwapchainImage::signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff) { |
| if (signaler.first) { signaler.first->encodeSignal(mtlCmdBuff); } |
| if (signaler.second) { signaler.second->signal(); } |
| } |
| |
| // Tell the semaphore and fence that they are being tracked for future signaling. |
| void MVKPresentableSwapchainImage::markAsTracked(MVKSwapchainSignaler& signaler) { |
| if (signaler.first) { signaler.first->retain(); } |
| if (signaler.second) { signaler.second->retain(); } |
| } |
| |
| // Tell the semaphore and fence that they are no longer being tracked for future signaling. |
| void MVKPresentableSwapchainImage::unmarkAsTracked(MVKSwapchainSignaler& signaler) { |
| if (signaler.first) { signaler.first->release(); } |
| if (signaler.second) { signaler.second->release(); } |
| } |
| |
| |
| #pragma mark Metal |
| |
| id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() { |
| while ( !_mtlDrawable ) { |
| @autoreleasepool { // Reclaim auto-released drawable object before end of loop |
| uint64_t startTime = _device->getPerformanceTimestamp(); |
| |
| _mtlDrawable = [_swapchain->_mtlLayer.nextDrawable retain]; |
| if ( !_mtlDrawable ) { MVKLogError("CAMetalDrawable could not be acquired."); } |
| |
| _device->addActivityPerformance(_device->_performanceStatistics.queue.nextCAMetalDrawable, startTime); |
| } |
| } |
| return _mtlDrawable; |
| } |
| |
| // Present the drawable and make myself available only once the command buffer has completed. |
| void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff, bool hasPresentTime, uint32_t presentID, uint64_t desiredPresentTime) { |
| _swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff); |
| |
| NSString* scName = _swapchain->getDebugName(); |
| if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); } |
| if (!hasPresentTime) { |
| [mtlCmdBuff presentDrawable: getCAMetalDrawable()]; |
| } |
| else { |
| // Convert from nsecs to seconds |
| CFTimeInterval presentTimeSeconds = ( double ) desiredPresentTime * 1.0e-9; |
| [mtlCmdBuff presentDrawable: getCAMetalDrawable() atTime:(CFTimeInterval)presentTimeSeconds]; |
| } |
| if (scName) { mvkPopDebugGroup(mtlCmdBuff); } |
| |
| signalPresentationSemaphore(mtlCmdBuff); |
| |
| retain(); // Ensure this image is not destroyed while awaiting MTLCommandBuffer completion |
| [mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) { |
| makeAvailable(); |
| release(); |
| }]; |
| |
| if (hasPresentTime) { |
| if ([_mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) { |
| [_mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) { |
| // Record the presentation time |
| CFTimeInterval presentedTimeSeconds = drawable.presentedTime; |
| uint64_t presentedTimeNanoseconds = (uint64_t)(presentedTimeSeconds * 1.0e9); |
| _swapchain->recordPresentTime(presentID, desiredPresentTime, presentedTimeNanoseconds); |
| }]; |
| } else { |
| // If MTLDrawable.presentedTime/addPresentedHandler isn't supported, just treat it as if the |
| // present happened when requested |
| _swapchain->recordPresentTime(presentID, desiredPresentTime, desiredPresentTime); |
| } |
| } |
| } |
| |
| // Resets the MTLTexture and CAMetalDrawable underlying this image. |
| void MVKPresentableSwapchainImage::releaseMetalDrawable() { |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); ++planeIndex) { |
| _planes[planeIndex]->releaseMTLTexture(); |
| } |
| [_mtlDrawable release]; |
| _mtlDrawable = nil; |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device, |
| const VkImageCreateInfo* pCreateInfo, |
| MVKSwapchain* swapchain, |
| uint32_t swapchainIndex) : |
| MVKSwapchainImage(device, pCreateInfo, swapchain, swapchainIndex) { |
| |
| _mtlDrawable = nil; |
| |
| _availability.acquisitionID = _swapchain->getNextAcquisitionID(); |
| _availability.isAvailable = true; |
| _preSignaler = make_pair(nullptr, nullptr); |
| } |
| |
| MVKPresentableSwapchainImage::~MVKPresentableSwapchainImage() { |
| releaseMetalDrawable(); |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKPeerSwapchainImage |
| |
| VkResult MVKPeerSwapchainImage::bindDeviceMemory2(const VkBindImageMemoryInfo* pBindInfo) { |
| const VkBindImageMemorySwapchainInfoKHR* swapchainInfo = nullptr; |
| for (const auto* next = (const VkBaseInStructure*)pBindInfo->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR: |
| swapchainInfo = (const VkBindImageMemorySwapchainInfoKHR*)next; |
| break; |
| default: |
| break; |
| } |
| } |
| if (!swapchainInfo) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } |
| |
| _swapchainIndex = swapchainInfo->imageIndex; |
| return VK_SUCCESS; |
| } |
| |
| |
| #pragma mark Metal |
| |
| id<CAMetalDrawable> MVKPeerSwapchainImage::getCAMetalDrawable() { |
| return ((MVKSwapchainImage*)_swapchain->getPresentableImage(_swapchainIndex))->getCAMetalDrawable(); |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKPeerSwapchainImage::MVKPeerSwapchainImage(MVKDevice* device, |
| const VkImageCreateInfo* pCreateInfo, |
| MVKSwapchain* swapchain, |
| uint32_t swapchainIndex) : |
| MVKSwapchainImage(device, pCreateInfo, swapchain, swapchainIndex) {} |
| |
| |
| |
| #pragma mark - |
| #pragma mark MVKImageViewPlane |
| |
| MVKVulkanAPIObject* MVKImageViewPlane::getVulkanAPIObject() { return _imageView; } |
| |
| void MVKImageViewPlane::propagateDebugName() { setLabelIfNotNil(_mtlTexture, _imageView->_debugName); } |
| |
| |
| #pragma mark Metal |
| |
| id<MTLTexture> MVKImageViewPlane::getMTLTexture() { |
| // If we can use a Metal texture view, lazily create it, otherwise use the image texture directly. |
| if (_useMTLTextureView) { |
| if ( !_mtlTexture && _mtlPixFmt ) { |
| |
| // Lock and check again in case another thread created the texture view |
| lock_guard<mutex> lock(_imageView->_lock); |
| if (_mtlTexture) { return _mtlTexture; } |
| |
| _mtlTexture = newMTLTexture(); // retained |
| |
| propagateDebugName(); |
| } |
| return _mtlTexture; |
| } else { |
| return _imageView->_image->getMTLTexture(_planeIndex); |
| } |
| } |
| |
| // Creates and returns a retained Metal texture as an |
| // overlay on the Metal texture of the underlying image. |
| id<MTLTexture> MVKImageViewPlane::newMTLTexture() { |
| MTLTextureType mtlTextureType = _imageView->_mtlTextureType; |
| NSRange sliceRange = NSMakeRange(_imageView->_subresourceRange.baseArrayLayer, _imageView->_subresourceRange.layerCount); |
| // Fake support for 2D views of 3D textures. |
| if (_imageView->_image->getImageType() == VK_IMAGE_TYPE_3D && |
| (mtlTextureType == MTLTextureType2D || mtlTextureType == MTLTextureType2DArray)) { |
| mtlTextureType = MTLTextureType3D; |
| sliceRange = NSMakeRange(0, 1); |
| } |
| id<MTLTexture> mtlTex = _imageView->_image->getMTLTexture(MVKImage::getPlaneFromVkImageAspectFlags(_imageView->_subresourceRange.aspectMask)); |
| if (_device->_pMetalFeatures->nativeTextureSwizzle && _packedSwizzle) { |
| return [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt |
| textureType: mtlTextureType |
| levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount) |
| slices: sliceRange |
| swizzle: mvkMTLTextureSwizzleChannelsFromVkComponentMapping(mvkUnpackSwizzle(_packedSwizzle))]; // retained |
| } else { |
| return [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt |
| textureType: mtlTextureType |
| levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount) |
| slices: sliceRange]; // retained |
| } |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKImageViewPlane::MVKImageViewPlane(MVKImageView* imageView, |
| uint8_t planeIndex, |
| MTLPixelFormat mtlPixFmt, |
| const VkImageViewCreateInfo* pCreateInfo) : MVKBaseDeviceObject(imageView->_device) { |
| _imageView = imageView; |
| _planeIndex = planeIndex; |
| _mtlPixFmt = mtlPixFmt; |
| _mtlTexture = nil; |
| |
| bool useSwizzle; |
| _imageView->setConfigurationResult(_imageView->validateSwizzledMTLPixelFormat(pCreateInfo, |
| _imageView, |
| _device->_pMetalFeatures->nativeTextureSwizzle, |
| _device->_pMVKConfig->fullImageViewSwizzle, |
| _mtlPixFmt, |
| useSwizzle)); |
| _packedSwizzle = (useSwizzle) ? mvkPackSwizzle(pCreateInfo->components) : 0; |
| |
| // Determine whether this image view should use a Metal texture view, |
| // and set the _useMTLTextureView variable appropriately. |
| if ( _imageView->_image ) { |
| _useMTLTextureView = _imageView->_image->_canSupportMTLTextureView; |
| bool is3D = _imageView->_image->_mtlTextureType == MTLTextureType3D; |
| // If the view is identical to underlying image, don't bother using a Metal view |
| if (_mtlPixFmt == _imageView->_image->getMTLPixelFormat(planeIndex) && |
| (_imageView->_mtlTextureType == _imageView->_image->_mtlTextureType || |
| ((_imageView->_mtlTextureType == MTLTextureType2D || _imageView->_mtlTextureType == MTLTextureType2DArray) && is3D)) && |
| _imageView->_subresourceRange.levelCount == _imageView->_image->_mipLevels && |
| (is3D || _imageView->_subresourceRange.layerCount == _imageView->_image->_arrayLayers) && |
| (!_device->_pMetalFeatures->nativeTextureSwizzle || !_packedSwizzle)) { |
| _useMTLTextureView = false; |
| } |
| } else { |
| _useMTLTextureView = false; |
| } |
| } |
| |
| MVKImageViewPlane::~MVKImageViewPlane() { |
| [_mtlTexture release]; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKImageView |
| |
| void MVKImageView::propagateDebugName() { |
| for (uint8_t planeIndex = 0; planeIndex < _planes.size(); planeIndex++) { |
| _planes[planeIndex]->propagateDebugName(); |
| } |
| } |
| |
| void MVKImageView::populateMTLRenderPassAttachmentDescriptor(MTLRenderPassAttachmentDescriptor* mtlAttDesc) { |
| MVKImageViewPlane* plane = _planes[0]; |
| mtlAttDesc.texture = plane->getMTLTexture(); // Use image view, necessary if image view format differs from image format |
| mtlAttDesc.level = plane->_useMTLTextureView ? 0 : _subresourceRange.baseMipLevel; |
| if (mtlAttDesc.texture.textureType == MTLTextureType3D) { |
| mtlAttDesc.slice = 0; |
| mtlAttDesc.depthPlane = plane->_useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer; |
| } else { |
| mtlAttDesc.slice = plane->_useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer; |
| mtlAttDesc.depthPlane = 0; |
| } |
| } |
| |
| void MVKImageView::populateMTLRenderPassAttachmentDescriptorResolve(MTLRenderPassAttachmentDescriptor* mtlAttDesc) { |
| MVKImageViewPlane* plane = _planes[0]; |
| mtlAttDesc.resolveTexture = plane->getMTLTexture(); // Use image view, necessary if image view format differs from image format |
| mtlAttDesc.resolveLevel = plane->_useMTLTextureView ? 0 : _subresourceRange.baseMipLevel; |
| if (mtlAttDesc.resolveTexture.textureType == MTLTextureType3D) { |
| mtlAttDesc.resolveSlice = 0; |
| mtlAttDesc.resolveDepthPlane = plane->_useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer; |
| } else { |
| mtlAttDesc.resolveSlice = plane->_useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer; |
| mtlAttDesc.resolveDepthPlane = 0; |
| } |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKImageView::MVKImageView(MVKDevice* device, |
| const VkImageViewCreateInfo* pCreateInfo, |
| const MVKConfiguration* pAltMVKConfig) : MVKVulkanAPIDeviceObject(device) { |
| _image = (MVKImage*)pCreateInfo->image; |
| _usage = _image->_usage; |
| _mtlTextureType = mvkMTLTextureTypeFromVkImageViewType(pCreateInfo->viewType, |
| _image->getSampleCount() != VK_SAMPLE_COUNT_1_BIT); |
| |
| for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO: { |
| auto* pViewUsageInfo = (VkImageViewUsageCreateInfo*)next; |
| if (!(pViewUsageInfo->usage & ~_usage)) { _usage = pViewUsageInfo->usage; } |
| break; |
| } |
| /* case VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR: { |
| const VkSamplerYcbcrConversionInfoKHR* sampConvInfo = (const VkSamplerYcbcrConversionInfoKHR*)next; |
| break; |
| } */ |
| default: |
| break; |
| } |
| } |
| |
| // Validate whether the image view configuration can be supported |
| if ( _image ) { |
| VkImageType imgType = _image->getImageType(); |
| VkImageViewType viewType = pCreateInfo->viewType; |
| |
| // VK_KHR_maintenance1 supports taking 2D image views of 3D slices for sampling. |
| // No dice in Metal. But we are able to fake out a 3D render attachment by making the Metal view |
| // itself a 3D texture (when we create it), and setting the rendering depthPlane appropriately. |
| if ((viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) && (imgType == VK_IMAGE_TYPE_3D)) { |
| if (!mvkIsOnlyAnyFlagEnabled(_usage, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images can only be used as color attachments.")); |
| } |
| } |
| } |
| |
| // Remember the subresource range, and determine the actual number of mip levels and texture slices |
| _subresourceRange = pCreateInfo->subresourceRange; |
| if (_subresourceRange.levelCount == VK_REMAINING_MIP_LEVELS) { |
| _subresourceRange.levelCount = _image->getMipLevelCount() - _subresourceRange.baseMipLevel; |
| } |
| if (_subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS) { |
| _subresourceRange.layerCount = _image->getLayerCount() - _subresourceRange.baseArrayLayer; |
| } |
| |
| VkExtent2D blockTexelSizeOfPlane[3]; |
| uint32_t bytesPerBlockOfPlane[3]; |
| MTLPixelFormat mtlPixFmtOfPlane[3]; |
| uint8_t subsamplingPlaneCount = getPixelFormats()->getChromaSubsamplingPlanes(pCreateInfo->format, blockTexelSizeOfPlane, bytesPerBlockOfPlane, mtlPixFmtOfPlane), |
| beginPlaneIndex = 0, |
| endPlaneIndex = subsamplingPlaneCount; |
| if (subsamplingPlaneCount == 0) { |
| endPlaneIndex = 1; |
| mtlPixFmtOfPlane[0] = getPixelFormats()->getMTLPixelFormat(pCreateInfo->format); |
| } else { |
| if (!mvkVkComponentMappingsMatch(pCreateInfo->components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A})) { |
| setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Image view swizzling for multi planar formats is not supported.")); |
| } |
| if (_subresourceRange.aspectMask & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT)) { |
| beginPlaneIndex = endPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(_subresourceRange.aspectMask); |
| } |
| } |
| for (uint8_t planeIndex = beginPlaneIndex; planeIndex < endPlaneIndex; planeIndex++) { |
| _planes.push_back(new MVKImageViewPlane(this, planeIndex, mtlPixFmtOfPlane[planeIndex], pCreateInfo)); |
| } |
| } |
| |
| VkResult MVKImageView::validateSwizzledMTLPixelFormat(const VkImageViewCreateInfo* pCreateInfo, |
| MVKVulkanAPIObject* apiObject, |
| bool hasNativeSwizzleSupport, |
| bool hasShaderSwizzleSupport, |
| MTLPixelFormat& mtlPixFmt, |
| bool& useSwizzle) { |
| useSwizzle = false; |
| VkComponentMapping components = pCreateInfo->components; |
| |
| #define SWIZZLE_MATCHES(R, G, B, A) mvkVkComponentMappingsMatch(components, {VK_COMPONENT_SWIZZLE_ ##R, VK_COMPONENT_SWIZZLE_ ##G, VK_COMPONENT_SWIZZLE_ ##B, VK_COMPONENT_SWIZZLE_ ##A} ) |
| #define VK_COMPONENT_SWIZZLE_ANY VK_COMPONENT_SWIZZLE_MAX_ENUM |
| |
| // If we have an identity swizzle, we're all good. |
| if (SWIZZLE_MATCHES(R, G, B, A)) { |
| // Change to stencil-only format if only stencil aspect is requested |
| if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) { |
| if (mtlPixFmt == MTLPixelFormatDepth32Float_Stencil8) |
| mtlPixFmt = MTLPixelFormatX32_Stencil8; |
| #if MVK_MACOS |
| else if (mtlPixFmt == MTLPixelFormatDepth24Unorm_Stencil8) |
| mtlPixFmt = MTLPixelFormatX24_Stencil8; |
| #endif |
| } |
| |
| return VK_SUCCESS; |
| } |
| |
| switch (mtlPixFmt) { |
| case MTLPixelFormatR8Unorm: |
| if (SWIZZLE_MATCHES(ZERO, ANY, ANY, R)) { |
| mtlPixFmt = MTLPixelFormatA8Unorm; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatA8Unorm: |
| if (SWIZZLE_MATCHES(A, ANY, ANY, ZERO)) { |
| mtlPixFmt = MTLPixelFormatR8Unorm; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatRGBA8Unorm: |
| if (SWIZZLE_MATCHES(B, G, R, A)) { |
| mtlPixFmt = MTLPixelFormatBGRA8Unorm; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatRGBA8Unorm_sRGB: |
| if (SWIZZLE_MATCHES(B, G, R, A)) { |
| mtlPixFmt = MTLPixelFormatBGRA8Unorm_sRGB; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatBGRA8Unorm: |
| if (SWIZZLE_MATCHES(B, G, R, A)) { |
| mtlPixFmt = MTLPixelFormatRGBA8Unorm; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatBGRA8Unorm_sRGB: |
| if (SWIZZLE_MATCHES(B, G, R, A)) { |
| mtlPixFmt = MTLPixelFormatRGBA8Unorm_sRGB; |
| return VK_SUCCESS; |
| } |
| break; |
| |
| case MTLPixelFormatDepth32Float_Stencil8: |
| // If aspect mask looking only for stencil then change to stencil-only format even if shader swizzling is needed |
| if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) { |
| mtlPixFmt = MTLPixelFormatX32_Stencil8; |
| if (SWIZZLE_MATCHES(R, ANY, ANY, ANY)) { |
| return VK_SUCCESS; |
| } |
| } |
| break; |
| |
| #if MVK_MACOS |
| case MTLPixelFormatDepth24Unorm_Stencil8: |
| // If aspect mask looking only for stencil then change to stencil-only format even if shader swizzling is needed |
| if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) { |
| mtlPixFmt = MTLPixelFormatX24_Stencil8; |
| if (SWIZZLE_MATCHES(R, ANY, ANY, ANY)) { |
| return VK_SUCCESS; |
| } |
| } |
| break; |
| #endif |
| |
| default: |
| break; |
| } |
| |
| // No format transformation swizzles were found, so we'll need to use either native or shader swizzling. |
| useSwizzle = true; |
| if (hasNativeSwizzleSupport || hasShaderSwizzleSupport ) { |
| return VK_SUCCESS; |
| } |
| |
| // Oh, oh. Neither native or shader swizzling is supported. |
| return apiObject->reportError(VK_ERROR_FEATURE_NOT_PRESENT, |
| "The value of %s::components) (%s, %s, %s, %s), when applied to a VkImageView, requires full component swizzling to be enabled both at the" |
| " time when the VkImageView is created and at the time any pipeline that uses that VkImageView is compiled. Full component swizzling can" |
| " be enabled via the MVKConfiguration::fullImageViewSwizzle config parameter or MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE environment variable.", |
| pCreateInfo->image ? "vkCreateImageView(VkImageViewCreateInfo" : "vkGetPhysicalDeviceImageFormatProperties2KHR(VkPhysicalDeviceImageViewSupportEXTX", |
| mvkVkComponentSwizzleName(components.r), mvkVkComponentSwizzleName(components.g), |
| mvkVkComponentSwizzleName(components.b), mvkVkComponentSwizzleName(components.a)); |
| } |
| |
| MVKImageView::~MVKImageView() { |
| mvkDestroyContainerContents(_planes); |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKSamplerYcbcrConversion |
| |
| void MVKSamplerYcbcrConversion::updateConstExprSampler(MSLConstexprSampler& constExprSampler) const { |
| constExprSampler.planes = _planes; |
| constExprSampler.resolution = _resolution; |
| constExprSampler.chroma_filter = _chroma_filter; |
| constExprSampler.x_chroma_offset = _x_chroma_offset; |
| constExprSampler.y_chroma_offset = _y_chroma_offset; |
| for (uint32_t i = 0; i < 4; ++i) { |
| constExprSampler.swizzle[i] = _swizzle[i]; |
| } |
| constExprSampler.ycbcr_model = _ycbcr_model; |
| constExprSampler.ycbcr_range = _ycbcr_range; |
| constExprSampler.bpc = _bpc; |
| constExprSampler.ycbcr_conversion_enable = true; |
| } |
| |
| static MSLSamplerFilter getSpvMinMagFilterFromVkFilter(VkFilter vkFilter) { |
| switch (vkFilter) { |
| case VK_FILTER_LINEAR: return MSL_SAMPLER_FILTER_LINEAR; |
| |
| case VK_FILTER_NEAREST: |
| default: |
| return MSL_SAMPLER_FILTER_NEAREST; |
| } |
| } |
| |
| static MSLChromaLocation getSpvChromaLocationFromVkChromaLocation(VkChromaLocation vkChromaLocation) { |
| switch (vkChromaLocation) { |
| default: |
| case VK_CHROMA_LOCATION_COSITED_EVEN: return MSL_CHROMA_LOCATION_COSITED_EVEN; |
| case VK_CHROMA_LOCATION_MIDPOINT: return MSL_CHROMA_LOCATION_MIDPOINT; |
| } |
| } |
| |
| static MSLComponentSwizzle getSpvComponentSwizzleFromVkComponentMapping(VkComponentSwizzle vkComponentSwizzle) { |
| switch (vkComponentSwizzle) { |
| default: |
| case VK_COMPONENT_SWIZZLE_IDENTITY: return MSL_COMPONENT_SWIZZLE_IDENTITY; |
| case VK_COMPONENT_SWIZZLE_ZERO: return MSL_COMPONENT_SWIZZLE_ZERO; |
| case VK_COMPONENT_SWIZZLE_ONE: return MSL_COMPONENT_SWIZZLE_ONE; |
| case VK_COMPONENT_SWIZZLE_R: return MSL_COMPONENT_SWIZZLE_R; |
| case VK_COMPONENT_SWIZZLE_G: return MSL_COMPONENT_SWIZZLE_G; |
| case VK_COMPONENT_SWIZZLE_B: return MSL_COMPONENT_SWIZZLE_B; |
| case VK_COMPONENT_SWIZZLE_A: return MSL_COMPONENT_SWIZZLE_A; |
| } |
| } |
| |
| static MSLSamplerYCbCrModelConversion getSpvSamplerYCbCrModelConversionFromVkSamplerYcbcrModelConversion(VkSamplerYcbcrModelConversion vkSamplerYcbcrModelConversion) { |
| switch (vkSamplerYcbcrModelConversion) { |
| default: |
| case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY: return MSL_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY; |
| case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY: return MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY; |
| case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709: return MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_BT_709; |
| case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601: return MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_BT_601; |
| case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020: return MSL_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_BT_2020; |
| } |
| } |
| |
| static MSLSamplerYCbCrRange getSpvSamplerYcbcrRangeFromVkSamplerYcbcrRange(VkSamplerYcbcrRange vkSamplerYcbcrRange) { |
| switch (vkSamplerYcbcrRange) { |
| default: |
| case VK_SAMPLER_YCBCR_RANGE_ITU_FULL: return MSL_SAMPLER_YCBCR_RANGE_ITU_FULL; |
| case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW: return MSL_SAMPLER_YCBCR_RANGE_ITU_NARROW; |
| } |
| } |
| |
| MVKSamplerYcbcrConversion::MVKSamplerYcbcrConversion(MVKDevice* device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) { |
| MVKPixelFormats* pixFmts = getPixelFormats(); |
| _planes = pixFmts->getChromaSubsamplingPlaneCount(pCreateInfo->format); |
| _bpc = pixFmts->getChromaSubsamplingComponentBits(pCreateInfo->format); |
| _resolution = pixFmts->getChromaSubsamplingResolution(pCreateInfo->format); |
| _chroma_filter = getSpvMinMagFilterFromVkFilter(pCreateInfo->chromaFilter); |
| _x_chroma_offset = getSpvChromaLocationFromVkChromaLocation(pCreateInfo->xChromaOffset); |
| _y_chroma_offset = getSpvChromaLocationFromVkChromaLocation(pCreateInfo->yChromaOffset); |
| _swizzle[0] = getSpvComponentSwizzleFromVkComponentMapping(pCreateInfo->components.r); |
| _swizzle[1] = getSpvComponentSwizzleFromVkComponentMapping(pCreateInfo->components.g); |
| _swizzle[2] = getSpvComponentSwizzleFromVkComponentMapping(pCreateInfo->components.b); |
| _swizzle[3] = getSpvComponentSwizzleFromVkComponentMapping(pCreateInfo->components.a); |
| _ycbcr_model = getSpvSamplerYCbCrModelConversionFromVkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel); |
| _ycbcr_range = getSpvSamplerYcbcrRangeFromVkSamplerYcbcrRange(pCreateInfo->ycbcrRange); |
| _forceExplicitReconstruction = pCreateInfo->forceExplicitReconstruction; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKSampler |
| |
| bool MVKSampler::getConstexprSampler(mvk::MSLResourceBinding& resourceBinding) { |
| resourceBinding.requiresConstExprSampler = _requiresConstExprSampler; |
| if (_requiresConstExprSampler) { |
| resourceBinding.constExprSampler = _constExprSampler; |
| } |
| return _requiresConstExprSampler; |
| } |
| |
| // Returns an Metal sampler descriptor constructed from the properties of this image. |
| // It is the caller's responsibility to release the returned descriptor object. |
| MTLSamplerDescriptor* MVKSampler::newMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo) { |
| |
| MTLSamplerDescriptor* mtlSampDesc = [MTLSamplerDescriptor new]; // retained |
| mtlSampDesc.sAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeU); |
| mtlSampDesc.tAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeV); |
| mtlSampDesc.rAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeW); |
| mtlSampDesc.minFilter = mvkMTLSamplerMinMagFilterFromVkFilter(pCreateInfo->minFilter); |
| mtlSampDesc.magFilter = mvkMTLSamplerMinMagFilterFromVkFilter(pCreateInfo->magFilter); |
| mtlSampDesc.mipFilter = (pCreateInfo->unnormalizedCoordinates |
| ? MTLSamplerMipFilterNotMipmapped |
| : mvkMTLSamplerMipFilterFromVkSamplerMipmapMode(pCreateInfo->mipmapMode)); |
| mtlSampDesc.lodMinClamp = pCreateInfo->minLod; |
| mtlSampDesc.lodMaxClamp = pCreateInfo->maxLod; |
| mtlSampDesc.maxAnisotropy = (pCreateInfo->anisotropyEnable |
| ? mvkClamp(pCreateInfo->maxAnisotropy, 1.0f, _device->_pProperties->limits.maxSamplerAnisotropy) |
| : 1); |
| mtlSampDesc.normalizedCoordinates = !pCreateInfo->unnormalizedCoordinates; |
| |
| // If compareEnable is true, but dynamic samplers with depth compare are not available |
| // on this device, this sampler must only be used as an immutable sampler, and will |
| // be automatically hardcoded into the shader MSL. An error will be triggered if this |
| // sampler is used to update or push a descriptor. |
| if (pCreateInfo->compareEnable && !_requiresConstExprSampler) { |
| mtlSampDesc.compareFunctionMVK = mvkMTLCompareFunctionFromVkCompareOp(pCreateInfo->compareOp); |
| } |
| |
| #if MVK_MACOS |
| mtlSampDesc.borderColorMVK = mvkMTLSamplerBorderColorFromVkBorderColor(pCreateInfo->borderColor); |
| if (_device->getPhysicalDevice()->getMetalFeatures()->samplerClampToBorder) { |
| if (pCreateInfo->addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) { |
| mtlSampDesc.sAddressMode = MTLSamplerAddressModeClampToBorderColor; |
| } |
| if (pCreateInfo->addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) { |
| mtlSampDesc.tAddressMode = MTLSamplerAddressModeClampToBorderColor; |
| } |
| if (pCreateInfo->addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) { |
| mtlSampDesc.rAddressMode = MTLSamplerAddressModeClampToBorderColor; |
| } |
| } |
| #endif |
| return mtlSampDesc; |
| } |
| |
| MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) { |
| _ycbcrConversion = NULL; |
| for (const auto* next = (const VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) { |
| switch (next->sType) { |
| case VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR: { |
| const VkSamplerYcbcrConversionInfoKHR* sampConvInfo = (const VkSamplerYcbcrConversionInfoKHR*)next; |
| _ycbcrConversion = (MVKSamplerYcbcrConversion*)(sampConvInfo->conversion); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| _requiresConstExprSampler = (pCreateInfo->compareEnable && !_device->_pMetalFeatures->depthSampleCompare) || _ycbcrConversion; |
| |
| MTLSamplerDescriptor* mtlSampDesc = newMTLSamplerDescriptor(pCreateInfo); // temp retain |
| _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: mtlSampDesc]; |
| [mtlSampDesc release]; // temp release |
| |
| initConstExprSampler(pCreateInfo); |
| } |
| |
| static MSLSamplerMipFilter getSpvMipFilterFromVkMipMode(VkSamplerMipmapMode vkMipMode) { |
| switch (vkMipMode) { |
| case VK_SAMPLER_MIPMAP_MODE_LINEAR: return MSL_SAMPLER_MIP_FILTER_LINEAR; |
| case VK_SAMPLER_MIPMAP_MODE_NEAREST: return MSL_SAMPLER_MIP_FILTER_NEAREST; |
| |
| default: |
| return MSL_SAMPLER_MIP_FILTER_NONE; |
| } |
| } |
| |
| static MSLSamplerAddress getSpvAddressModeFromVkAddressMode(VkSamplerAddressMode vkAddrMode) { |
| switch (vkAddrMode) { |
| case VK_SAMPLER_ADDRESS_MODE_REPEAT: return MSL_SAMPLER_ADDRESS_REPEAT; |
| case VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: return MSL_SAMPLER_ADDRESS_MIRRORED_REPEAT; |
| case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: return MSL_SAMPLER_ADDRESS_CLAMP_TO_BORDER; |
| |
| case VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE: |
| case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: |
| default: |
| return MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE; |
| } |
| } |
| |
| static MSLSamplerCompareFunc getSpvCompFuncFromVkCompOp(VkCompareOp vkCompOp) { |
| switch (vkCompOp) { |
| case VK_COMPARE_OP_LESS: return MSL_SAMPLER_COMPARE_FUNC_LESS; |
| case VK_COMPARE_OP_EQUAL: return MSL_SAMPLER_COMPARE_FUNC_EQUAL; |
| case VK_COMPARE_OP_LESS_OR_EQUAL: return MSL_SAMPLER_COMPARE_FUNC_LESS_EQUAL; |
| case VK_COMPARE_OP_GREATER: return MSL_SAMPLER_COMPARE_FUNC_GREATER; |
| case VK_COMPARE_OP_NOT_EQUAL: return MSL_SAMPLER_COMPARE_FUNC_NOT_EQUAL; |
| case VK_COMPARE_OP_GREATER_OR_EQUAL: return MSL_SAMPLER_COMPARE_FUNC_GREATER_EQUAL; |
| case VK_COMPARE_OP_ALWAYS: return MSL_SAMPLER_COMPARE_FUNC_ALWAYS; |
| |
| case VK_COMPARE_OP_NEVER: |
| default: |
| return MSL_SAMPLER_COMPARE_FUNC_NEVER; |
| } |
| } |
| |
| static MSLSamplerBorderColor getSpvBorderColorFromVkBorderColor(VkBorderColor vkBorderColor) { |
| switch (vkBorderColor) { |
| case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK: |
| case VK_BORDER_COLOR_INT_OPAQUE_BLACK: |
| return MSL_SAMPLER_BORDER_COLOR_OPAQUE_BLACK; |
| |
| case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE: |
| case VK_BORDER_COLOR_INT_OPAQUE_WHITE: |
| return MSL_SAMPLER_BORDER_COLOR_OPAQUE_WHITE; |
| |
| case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK: |
| case VK_BORDER_COLOR_INT_TRANSPARENT_BLACK: |
| default: |
| return MSL_SAMPLER_BORDER_COLOR_TRANSPARENT_BLACK; |
| } |
| } |
| |
| void MVKSampler::initConstExprSampler(const VkSamplerCreateInfo* pCreateInfo) { |
| if ( !_requiresConstExprSampler ) { return; } |
| |
| _constExprSampler.coord = pCreateInfo->unnormalizedCoordinates ? MSL_SAMPLER_COORD_PIXEL : MSL_SAMPLER_COORD_NORMALIZED; |
| _constExprSampler.min_filter = getSpvMinMagFilterFromVkFilter(pCreateInfo->minFilter); |
| _constExprSampler.mag_filter = getSpvMinMagFilterFromVkFilter(pCreateInfo->magFilter); |
| _constExprSampler.mip_filter = getSpvMipFilterFromVkMipMode(pCreateInfo->mipmapMode); |
| _constExprSampler.s_address = getSpvAddressModeFromVkAddressMode(pCreateInfo->addressModeU); |
| _constExprSampler.t_address = getSpvAddressModeFromVkAddressMode(pCreateInfo->addressModeV); |
| _constExprSampler.r_address = getSpvAddressModeFromVkAddressMode(pCreateInfo->addressModeW); |
| _constExprSampler.compare_func = getSpvCompFuncFromVkCompOp(pCreateInfo->compareOp); |
| _constExprSampler.border_color = getSpvBorderColorFromVkBorderColor(pCreateInfo->borderColor); |
| _constExprSampler.lod_clamp_min = pCreateInfo->minLod; |
| _constExprSampler.lod_clamp_max = pCreateInfo->maxLod; |
| _constExprSampler.max_anisotropy = pCreateInfo->maxAnisotropy; |
| _constExprSampler.compare_enable = pCreateInfo->compareEnable; |
| _constExprSampler.lod_clamp_enable = false; |
| _constExprSampler.anisotropy_enable = pCreateInfo->anisotropyEnable; |
| if (_ycbcrConversion) { |
| _ycbcrConversion->updateConstExprSampler(_constExprSampler); |
| } |
| } |
| |
| MVKSampler::~MVKSampler() { |
| [_mtlSamplerState release]; |
| } |