blob: 6d27cfd397b8e99a6d68b19afd72ac5267367c0e [file] [log] [blame]
/*
* MVKImage.mm
*
* Copyright (c) 2014-2018 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 "MVKSwapchain.h"
#include "MVKCommandBuffer.h"
#include "mvk_datatypes.h"
#include "MVKFoundation.h"
#include "MVKLogging.h"
#import "MTLTextureDescriptor+MoltenVK.h"
#import "MTLSamplerDescriptor+MoltenVK.h"
using namespace std;
#pragma mark MVKImage
VkImageType MVKImage::getImageType() { return mvkVkImageTypeFromMTLTextureType(_mtlTextureType); }
VkFormat MVKImage::getVkFormat() { return mvkVkFormatFromMTLPixelFormat(_mtlPixelFormat); }
VkExtent3D MVKImage::getExtent3D(uint32_t mipLevel) {
return mvkMipmapLevelSizeFromBaseSize3D(_extent, mipLevel);
}
VkDeviceSize MVKImage::getBytesPerRow(uint32_t mipLevel) {
size_t bytesPerRow = mvkMTLPixelFormatBytesPerRow(_mtlPixelFormat, getExtent3D(mipLevel).width);
return (uint32_t)mvkAlignByteOffset(bytesPerRow, _byteAlignment);
}
VkDeviceSize MVKImage::getBytesPerLayer(uint32_t mipLevel) {
return mvkMTLPixelFormatBytesPerLayer(_mtlPixelFormat, getBytesPerRow(mipLevel), getExtent3D(mipLevel).height);
}
VkResult MVKImage::getSubresourceLayout(const VkImageSubresource* pSubresource,
VkSubresourceLayout* pLayout) {
MVKImageSubresource* pImgRez = 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::applyMemoryBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkMemoryBarrier* pMemoryBarrier,
MVKCommandEncoder* cmdEncoder,
MVKCommandUse cmdUse) {
#if MVK_MACOS
if ( needsHostReadSync(srcStageMask, dstStageMask, pMemoryBarrier) ) {
[cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeResource: getMTLTexture()];
}
#endif
}
void MVKImage::applyImageMemoryBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkImageMemoryBarrier* pImageMemoryBarrier,
MVKCommandEncoder* cmdEncoder,
MVKCommandUse cmdUse) {
const VkImageSubresourceRange& srRange = pImageMemoryBarrier->subresourceRange;
// Extract the mipmap levels that are to be updated
uint32_t mipLvlStart = srRange.baseMipLevel;
uint32_t mipLvlCnt = srRange.levelCount;
uint32_t mipLvlEnd = (mipLvlCnt == VK_REMAINING_MIP_LEVELS
? getMipLevelCount()
: (mipLvlStart + mipLvlCnt));
// Extract the cube or array layers (slices) that are to be updated
uint32_t layerStart = srRange.baseArrayLayer;
uint32_t layerCnt = srRange.layerCount;
uint32_t layerEnd = (layerCnt == VK_REMAINING_ARRAY_LAYERS
? getLayerCount()
: (layerStart + layerCnt));
#if MVK_MACOS
bool needsSync = needsHostReadSync(srcStageMask, dstStageMask, pImageMemoryBarrier);
id<MTLTexture> mtlTex = needsSync ? getMTLTexture() : nil;
id<MTLBlitCommandEncoder> mtlBlitEncoder = needsSync ? cmdEncoder->getMTLBlitEncoder(cmdUse) : nil;
#endif
// 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 = pImageMemoryBarrier->newLayout; }
#if MVK_MACOS
if (needsSync) { [mtlBlitEncoder synchronizeTexture: mtlTex slice: layer level: mipLvl]; }
#endif
}
}
}
// 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 MVKImage::needsHostReadSync(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkImageMemoryBarrier* pImageMemoryBarrier) {
#if MVK_IOS
return false;
#endif
#if MVK_MACOS
return ((pImageMemoryBarrier->newLayout == VK_IMAGE_LAYOUT_GENERAL) &&
mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) &&
mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) &&
isMemoryHostAccessible() && !isMemoryHostCoherent());
#endif
}
// Returns a pointer to the internal subresource for the specified MIP level layer.
MVKImageSubresource* MVKImage::getSubresource(uint32_t mipLevel, uint32_t arrayLayer) {
uint32_t srIdx = (mipLevel * _arrayLayers) + arrayLayer;
return (srIdx < _subresources.size()) ? &_subresources[srIdx] : NULL;
}
VkResult MVKImage::getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) {
pMemoryRequirements->size = _byteCount;
pMemoryRequirements->alignment = _byteAlignment;
pMemoryRequirements->memoryTypeBits = (_isDepthStencilAttachment
? _device->getPhysicalDevice()->getPrivateMemoryTypes()
: _device->getPhysicalDevice()->getAllMemoryTypes());
return VK_SUCCESS;
}
// Memory may have been mapped before image was bound, and needs to be loaded into the MTLTexture.
VkResult MVKImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) {
if (_deviceMemory) { _deviceMemory->removeImage(this); }
MVKResource::bindDeviceMemory(mvkMem, memOffset);
_usesTexelBuffer = validateUseTexelBuffer();
flushToDevice(getDeviceMemoryOffset(), getByteCount());
return _deviceMemory ? _deviceMemory->addImage(this) : VK_SUCCESS;
}
bool MVKImage::validateUseTexelBuffer() {
VkExtent2D blockExt = mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat);
bool isUncompressed = blockExt.width == 1 && blockExt.height == 1;
bool useTexelBuffer = _device->_pMetalFeatures->texelBuffers; // Texel buffers available
useTexelBuffer = useTexelBuffer && isMemoryHostAccessible() && _isLinear && isUncompressed; // Applicable memory layout
useTexelBuffer = useTexelBuffer && _deviceMemory && _deviceMemory->_mtlBuffer; // Buffer is available to overlay
#if MVK_MACOS
useTexelBuffer = useTexelBuffer && !isMemoryHostCoherent(); // macOS cannot use shared memory for texel buffers
#endif
return useTexelBuffer;
}
bool MVKImage::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 MVKImage::flushToDevice(VkDeviceSize offset, VkDeviceSize size) {
if (shouldFlushHostMemory()) {
for (auto& subRez : _subresources) {
switch (subRez.layoutState) {
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PREINITIALIZED:
case VK_IMAGE_LAYOUT_GENERAL: {
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 MVKImage::pullFromDevice(VkDeviceSize offset, VkDeviceSize size) {
if (shouldFlushHostMemory()) {
for (auto& subRez : _subresources) {
switch (subRez.layoutState) {
case VK_IMAGE_LAYOUT_GENERAL: {
getMTLTextureContent(subRez, offset, size);
break;
}
default:
break;
}
}
}
return VK_SUCCESS;
}
#pragma mark Metal
id<MTLTexture> MVKImage::getMTLTexture() {
if ( !_mtlTexture && _mtlPixelFormat ) {
// Lock and check again in case another thread has created the texture.
lock_guard<mutex> lock(_lock);
if (_mtlTexture) { return _mtlTexture; }
_mtlTexture = newMTLTexture(); // retained
}
return _mtlTexture;
}
VkResult MVKImage::setMTLTexture(id<MTLTexture> mtlTexture) {
resetMTLTexture();
resetIOSurface();
_mtlTexture = mtlTexture;
_mtlPixelFormat = _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 = mvkVkImageUsageFlagsFromMTLTextureUsage(_mtlTexture.usage, _mtlPixelFormat);
if (_device->_pMetalFeatures->ioSurfaces) {
_ioSurface = mtlTexture.iosurface;
CFRetain(_ioSurface);
}
return VK_SUCCESS;
}
// Creates and returns a retained Metal texture suitable for use in this instance.
// This implementation creates a new MTLTexture from a MTLTextureDescriptor and possible IOSurface.
// Subclasses may override this function to create the MTLTexture in a different manner.
id<MTLTexture> MVKImage::newMTLTexture() {
if (_ioSurface) {
return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor() iosurface: _ioSurface plane: 0];
} else if (_usesTexelBuffer) {
return [_deviceMemory->_mtlBuffer newTextureWithDescriptor: getMTLTextureDescriptor()
offset: getDeviceMemoryOffset()
bytesPerRow: _subresources[0].layout.rowPitch];
} else {
return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor()];
}
}
// Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture().
void MVKImage::resetMTLTexture() {
[_mtlTexture release];
_mtlTexture = nil;
}
void MVKImage::resetIOSurface() {
if (_ioSurface) {
CFRelease(_ioSurface);
_ioSurface = nil;
}
}
IOSurfaceRef MVKImage::getIOSurface() { return _ioSurface; }
VkResult MVKImage::useIOSurface(IOSurfaceRef ioSurface) {
if (!_device->_pMetalFeatures->ioSurfaces) { return mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); }
#if MVK_SUPPORT_IOSURFACE_BOOL
resetMTLTexture();
resetIOSurface();
if (ioSurface) {
if (IOSurfaceGetWidth(ioSurface) != _extent.width) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface width %zu does not match VkImage width %d.", IOSurfaceGetWidth(ioSurface), _extent.width); }
if (IOSurfaceGetHeight(ioSurface) != _extent.height) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface height %zu does not match VkImage height %d.", IOSurfaceGetHeight(ioSurface), _extent.height); }
if (IOSurfaceGetBytesPerElement(ioSurface) != mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface bytes per element %zu does not match VkImage bytes per element %d.", IOSurfaceGetBytesPerElement(ioSurface), mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)); }
if (IOSurfaceGetElementWidth(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element width %zu does not match VkImage element width %d.", IOSurfaceGetElementWidth(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width); }
if (IOSurfaceGetElementHeight(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element height %zu does not match VkImage element height %d.", IOSurfaceGetElementHeight(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height); }
_ioSurface = ioSurface;
CFRetain(_ioSurface);
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_ioSurface = IOSurfaceCreate((CFDictionaryRef)@{
(id)kIOSurfaceWidth: @(_extent.width),
(id)kIOSurfaceHeight: @(_extent.height),
(id)kIOSurfaceBytesPerElement: @(mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)),
(id)kIOSurfaceElementWidth: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width),
(id)kIOSurfaceElementHeight: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height),
(id)kIOSurfaceIsGlobal: @(true), // Deprecated but needed for interprocess transfers
});
#pragma clang diagnostic pop
}
#endif
return VK_SUCCESS;
}
// Returns an autoreleased Metal texture descriptor constructed from the properties of this image.
MTLTextureDescriptor* MVKImage::getMTLTextureDescriptor() {
MTLTextureDescriptor* mtlTexDesc = [[MTLTextureDescriptor alloc] init];
mtlTexDesc.pixelFormat = _mtlPixelFormat;
mtlTexDesc.textureType = _mtlTextureType;
mtlTexDesc.width = _extent.width;
mtlTexDesc.height = _extent.height;
mtlTexDesc.depth = _extent.depth;
mtlTexDesc.mipmapLevelCount = _mipLevels;
mtlTexDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(_samples);
mtlTexDesc.arrayLength = _arrayLayers;
mtlTexDesc.usageMVK = mvkMTLTextureUsageFromVkImageUsageFlags(_usage);
mtlTexDesc.storageModeMVK = getMTLStorageMode();
mtlTexDesc.cpuCacheMode = getMTLCPUCacheMode();
return [mtlTexDesc autorelease];
}
MTLStorageMode MVKImage::getMTLStorageMode() {
// For macOS, textures cannot use Shared storage mode, so change to Managed storage mode.
MTLStorageMode stgMode = _deviceMemory->getMTLStorageMode();
if (_ioSurface && stgMode == MTLStorageModePrivate) { stgMode = MTLStorageModeShared; }
#if MVK_MACOS
if (stgMode == MTLStorageModeShared) { stgMode = MTLStorageModeManaged; }
#endif
return stgMode;
}
// Updates the contents of the underlying MTLTexture, corresponding to the
//specified subresource definition, from the underlying memory buffer.
void MVKImage::updateMTLTextureContent(MVKImageSubresource& subresource,
VkDeviceSize offset, VkDeviceSize size) {
VkImageSubresource& imgSubRez = subresource.subresource;
VkSubresourceLayout& imgLayout = subresource.layout;
// Check if subresource overlaps the memory range.
VkDeviceSize memStart = offset;
VkDeviceSize memEnd = offset + size;
VkDeviceSize imgStart = imgLayout.offset;
VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size;
if (imgStart >= memEnd || imgEnd <= memStart) { return; }
// Don't update if host memory has not been mapped yet.
void* pHostMem = getHostMemoryAddress();
if ( !pHostMem ) { return; }
VkExtent3D mipExtent = getExtent3D(imgSubRez.mipLevel);
VkImageType imgType = getImageType();
void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset);
MTLRegion mtlRegion;
mtlRegion.origin = MTLOriginMake(0, 0, 0);
mtlRegion.size = mvkMTLSizeFromVkExtent3D(mipExtent);
[getMTLTexture() replaceRegion: mtlRegion
mipmapLevel: imgSubRez.mipLevel
slice: imgSubRez.arrayLayer
withBytes: pImgBytes
bytesPerRow: (imgType != VK_IMAGE_TYPE_1D ? imgLayout.rowPitch : 0)
bytesPerImage: (imgType == VK_IMAGE_TYPE_3D ? imgLayout.depthPitch : 0)];
}
// Updates the contents of the underlying memory buffer from the contents of
// the underlying MTLTexture, corresponding to the specified subresource definition.
void MVKImage::getMTLTextureContent(MVKImageSubresource& subresource,
VkDeviceSize offset, VkDeviceSize size) {
VkImageSubresource& imgSubRez = subresource.subresource;
VkSubresourceLayout& imgLayout = subresource.layout;
// Check if subresource overlaps the memory range.
VkDeviceSize memStart = offset;
VkDeviceSize memEnd = offset + size;
VkDeviceSize imgStart = imgLayout.offset;
VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size;
if (imgStart >= memEnd || imgEnd <= memStart) { return; }
// Don't update if host memory has not been mapped yet.
void* pHostMem = getHostMemoryAddress();
if ( !pHostMem ) { return; }
VkExtent3D mipExtent = getExtent3D(imgSubRez.mipLevel);
VkImageType imgType = getImageType();
void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset);
MTLRegion mtlRegion;
mtlRegion.origin = MTLOriginMake(0, 0, 0);
mtlRegion.size = mvkMTLSizeFromVkExtent3D(mipExtent);
[getMTLTexture() getBytes: pImgBytes
bytesPerRow: (imgType != VK_IMAGE_TYPE_1D ? imgLayout.rowPitch : 0)
bytesPerImage: (imgType == VK_IMAGE_TYPE_3D ? imgLayout.depthPitch : 0)
fromRegion: mtlRegion
mipmapLevel: imgSubRez.mipLevel
slice: imgSubRez.arrayLayer];
}
#pragma mark Construction
MVKImage::MVKImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo) : MVKResource(device) {
_byteAlignment = _device->_pProperties->limits.minTexelBufferOffsetAlignment;
// 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. Adjust samples and miplevels for the right texture type.
uint32_t minDim = 1;
_usage = pCreateInfo->usage;
_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);
_mipLevels = max(pCreateInfo->mipLevels, minDim);
if ( (_mipLevels > 1) && (pCreateInfo->imageType == VK_IMAGE_TYPE_1D) ) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, 1D images cannot use mipmaps. Setting mip levels to 1."));
_mipLevels = 1;
}
_mtlTexture = nil;
_ioSurface = nil;
_mtlPixelFormat = mtlPixelFormatFromVkFormat(pCreateInfo->format);
_mtlTextureType = mvkMTLTextureTypeFromVkImageType(pCreateInfo->imageType,
_arrayLayers,
(pCreateInfo->samples > 1));
_samples = pCreateInfo->samples;
if ( (_samples > 1) && (_mtlTextureType != MTLTextureType2DMultisample) ) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling can only be used with a 2D image type with an array length of 1. Setting sample count to 1."));
_samples = VK_SAMPLE_COUNT_1_BIT;
}
_isDepthStencilAttachment = (mvkAreFlagsEnabled(pCreateInfo->usage, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ||
mvkAreFlagsEnabled(mvkVkFormatProperties(pCreateInfo->format).optimalTilingFeatures, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT));
_canSupportMTLTextureView = !_isDepthStencilAttachment;
_hasExpectedTexelSize = (mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat) == mvkVkFormatBytesPerBlock(pCreateInfo->format));
_isLinear = validateLinear(pCreateInfo);
_usesTexelBuffer = false;
// Calc _byteCount after _mtlTexture & _byteAlignment
for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) {
_byteCount += getBytesPerLayer(mipLvl) * _extent.depth * _arrayLayers;
}
initSubresources(pCreateInfo);
}
bool MVKImage::validateLinear(const VkImageCreateInfo* pCreateInfo) {
if (pCreateInfo->tiling != VK_IMAGE_TILING_LINEAR) { return false; }
if (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, imageType must be VK_IMAGE_TYPE_2D."));
return false;
}
if (_isDepthStencilAttachment) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format."));
return false;
}
if (_mipLevels > 1) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1."));
return false;
}
if (_arrayLayers > 1) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, arrayLayers must be 1."));
return false;
}
if (_samples > 1) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, samples must be VK_SAMPLE_COUNT_1_BIT."));
return false;
}
if ( !mvkAreOnlyAnyFlagsEnabled(_usage, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)) ) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, usage must only include VK_IMAGE_USAGE_TRANSFER_SRC_BIT and/or VK_IMAGE_USAGE_TRANSFER_DST_BIT."));
return false;
}
return true;
}
// Initializes the subresource definitions.
void MVKImage::initSubresources(const VkImageCreateInfo* pCreateInfo) {
_subresources.reserve(_mipLevels * _arrayLayers);
MVKImageSubresource subRez;
subRez.layoutState = pCreateInfo->initialLayout;
for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) {
subRez.subresource.mipLevel = mipLvl;
for (uint32_t layer = 0; layer < _arrayLayers; layer++) {
subRez.subresource.arrayLayer = layer;
initSubresourceLayout(subRez);
_subresources.push_back(subRez);
}
}
}
// Initializes the layout element of the specified image subresource.
void MVKImage::initSubresourceLayout(MVKImageSubresource& imgSubRez) {
VkImageSubresource subresource = imgSubRez.subresource;
uint32_t currMipLevel = subresource.mipLevel;
uint32_t currArrayLayer = subresource.arrayLayer;
VkDeviceSize bytesPerLayerCurrLevel = getBytesPerLayer(currMipLevel);
// Accumulate the byte offset for the specified sub-resource.
// This is the sum of the bytes consumed by all layers in all mipmap levels before the
// desired level, plus the layers before the desired layer at the desired level.
VkDeviceSize offset = 0;
for (uint32_t mipLvl = 0; mipLvl < currMipLevel; mipLvl++) {
offset += (getBytesPerLayer(mipLvl) * _extent.depth * _arrayLayers);
}
offset += (bytesPerLayerCurrLevel * currArrayLayer);
VkSubresourceLayout& layout = imgSubRez.layout;
layout.offset = offset;
layout.size = bytesPerLayerCurrLevel;
layout.rowPitch = getBytesPerRow(currMipLevel);
layout.depthPitch = bytesPerLayerCurrLevel;
}
MVKImage::~MVKImage() {
if (_deviceMemory) { _deviceMemory->removeImage(this); }
resetMTLTexture();
resetIOSurface();
}
#pragma mark -
#pragma mark MVKImageView
void MVKImageView::populateMTLRenderPassAttachmentDescriptor(MTLRenderPassAttachmentDescriptor* mtlAttDesc) {
mtlAttDesc.texture = getMTLTexture(); // Use image view, necessary if image view format differs from image format
mtlAttDesc.level = _useMTLTextureView ? 0 : _subresourceRange.baseMipLevel;
mtlAttDesc.slice = _useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer;
mtlAttDesc.depthPlane = 0;
}
void MVKImageView::populateMTLRenderPassAttachmentDescriptorResolve(MTLRenderPassAttachmentDescriptor* mtlAttDesc) {
mtlAttDesc.resolveTexture = getMTLTexture(); // Use image view, necessary if image view format differs from image format
mtlAttDesc.resolveLevel = _useMTLTextureView ? 0 : _subresourceRange.baseMipLevel;
mtlAttDesc.resolveSlice = _useMTLTextureView ? 0 : _subresourceRange.baseArrayLayer;
mtlAttDesc.resolveDepthPlane = 0;
}
#pragma mark Metal
id<MTLTexture> MVKImageView::getMTLTexture() {
// If we can use a Metal texture view, lazily create it, otherwise use the image texture directly.
if (_useMTLTextureView) {
if ( !_mtlTexture && _mtlPixelFormat ) {
// Lock and check again in case another thread created the texture view
lock_guard<mutex> lock(_lock);
if (_mtlTexture) { return _mtlTexture; }
_mtlTexture = newMTLTexture(); // retained
}
return _mtlTexture;
} else {
return _image->getMTLTexture();
}
}
// Creates and returns a retained Metal texture as an
// overlay on the Metal texture of the underlying image.
id<MTLTexture> MVKImageView::newMTLTexture() {
return [_image->getMTLTexture() newTextureViewWithPixelFormat: _mtlPixelFormat
textureType: _mtlTextureType
levels: NSMakeRange(_subresourceRange.baseMipLevel, _subresourceRange.levelCount)
slices: NSMakeRange(_subresourceRange.baseArrayLayer, _subresourceRange.layerCount)]; // retained
}
#pragma mark Construction
MVKImageView::MVKImageView(MVKDevice* device, const VkImageViewCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
_image = (MVKImage*)pCreateInfo->image;
// 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;
}
_mtlTexture = nil;
_mtlPixelFormat = getSwizzledMTLPixelFormat(pCreateInfo->format, pCreateInfo->components);
_mtlTextureType = mvkMTLTextureTypeFromVkImageViewType(pCreateInfo->viewType, (_image->getSampleCount() != VK_SAMPLE_COUNT_1_BIT));
initMTLTextureViewSupport();
}
// Returns a MTLPixelFormat, based on the original MTLPixelFormat, as converted from the VkFormat,
// but possibly modified by the swizzles defined in the VkComponentMapping of the VkImageViewCreateInfo.
// Metal does not support general per-texture swizzles, and so this function relies on a few coincidental
// alignments of existing MTLPixelFormats of the same structure. If swizzling is not possible for a
// particular combination of format and swizzle spec, the original MTLPixelFormat is returned.
MTLPixelFormat MVKImageView::getSwizzledMTLPixelFormat(VkFormat format, VkComponentMapping components) {
MTLPixelFormat mtlPF = mtlPixelFormatFromVkFormat(format);
switch (mtlPF) {
case MTLPixelFormatR8Unorm:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_R} ) ) {
return MTLPixelFormatA8Unorm;
}
break;
case MTLPixelFormatR8Snorm:
#if MVK_IOS
case MTLPixelFormatR8Unorm_sRGB:
#endif
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_R} ) ) {
setSwizzleFormatError(format, components);
return MTLPixelFormatA8Unorm;
}
break;
case MTLPixelFormatA8Unorm:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_ZERO} ) ) {
return MTLPixelFormatR8Unorm;
}
break;
case MTLPixelFormatRGBA8Unorm:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
return MTLPixelFormatBGRA8Unorm;
}
break;
case MTLPixelFormatRGBA8Unorm_sRGB:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
return MTLPixelFormatBGRA8Unorm_sRGB;
}
break;
case MTLPixelFormatRGBA8Snorm:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
setSwizzleFormatError(format, components);
return MTLPixelFormatBGRA8Unorm;
}
break;
case MTLPixelFormatBGRA8Unorm:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
return MTLPixelFormatRGBA8Unorm;
}
break;
case MTLPixelFormatBGRA8Unorm_sRGB:
if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
return MTLPixelFormatRGBA8Unorm_sRGB;
}
break;
default:
break;
}
if ( !matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A} ) ) {
setSwizzleFormatError(format, components);
}
return mtlPF;
}
const char* MVKImageView::getSwizzleName(VkComponentSwizzle swizzle) {
switch (swizzle) {
case VK_COMPONENT_SWIZZLE_IDENTITY: return "VK_COMPONENT_SWIZZLE_IDENTITY";
case VK_COMPONENT_SWIZZLE_ZERO: return "VK_COMPONENT_SWIZZLE_ZERO";
case VK_COMPONENT_SWIZZLE_ONE: return "VK_COMPONENT_SWIZZLE_ONE";
case VK_COMPONENT_SWIZZLE_R: return "VK_COMPONENT_SWIZZLE_R";
case VK_COMPONENT_SWIZZLE_G: return "VK_COMPONENT_SWIZZLE_G";
case VK_COMPONENT_SWIZZLE_B: return "VK_COMPONENT_SWIZZLE_B";
case VK_COMPONENT_SWIZZLE_A: return "VK_COMPONENT_SWIZZLE_A";
default: return "VK_COMPONENT_SWIZZLE_UNKNOWN";
}
}
// Sets a standard swizzle format error during instance construction.
void MVKImageView::setSwizzleFormatError(VkFormat format, VkComponentMapping components) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED,
"VkImageView format %s and swizzle (%s, %s, %s, %s) does not map to a valid MTLPixelFormat.\n",
mvkVkFormatName(format),
getSwizzleName(components.r),
getSwizzleName(components.g),
getSwizzleName(components.b),
getSwizzleName(components.a)));
}
// Returns whether the swizzle components of the internal VkComponentMapping matches the
// swizzle pattern, by comparing corresponding elements of the two structures. The pattern
// supports wildcards, in that any element of pattern can be set to VK_COMPONENT_SWIZZLE_MAX_ENUM
// to indicate that any value in the corresponding element of components.
bool MVKImageView::matchesSwizzle(VkComponentMapping components, VkComponentMapping pattern) {
if ( !((pattern.r == VK_COMPONENT_SWIZZLE_MAX_ENUM) || (pattern.r == components.r) ||
((pattern.r == VK_COMPONENT_SWIZZLE_R) && (components.r == VK_COMPONENT_SWIZZLE_IDENTITY))) ) { return false; }
if ( !((pattern.g == VK_COMPONENT_SWIZZLE_MAX_ENUM) || (pattern.g == components.g) ||
((pattern.g == VK_COMPONENT_SWIZZLE_G) && (components.g == VK_COMPONENT_SWIZZLE_IDENTITY))) ) { return false; }
if ( !((pattern.b == VK_COMPONENT_SWIZZLE_MAX_ENUM) || (pattern.b == components.b) ||
((pattern.b == VK_COMPONENT_SWIZZLE_B) && (components.b == VK_COMPONENT_SWIZZLE_IDENTITY))) ) { return false; }
if ( !((pattern.a == VK_COMPONENT_SWIZZLE_MAX_ENUM) || (pattern.a == components.a) ||
((pattern.a == VK_COMPONENT_SWIZZLE_A) && (components.a == VK_COMPONENT_SWIZZLE_IDENTITY))) ) { return false; }
return true;
}
// Determine whether this image view should use a Metal texture view,
// and set the _useMTLTextureView variable appropriately.
void MVKImageView::initMTLTextureViewSupport() {
_useMTLTextureView = _image->_canSupportMTLTextureView;
// If the view is identical to underlying image, don't bother using a Metal view
if (_mtlPixelFormat == _image->_mtlPixelFormat &&
_mtlTextureType == _image->_mtlTextureType &&
_subresourceRange.levelCount == _image->_mipLevels &&
_subresourceRange.layerCount == _image->_arrayLayers) {
_useMTLTextureView = false;
}
}
MVKImageView::~MVKImageView() {
[_mtlTexture release];
}
#pragma mark -
#pragma mark MVKSampler
// Returns an autoreleased Metal sampler descriptor constructed from the properties of this image.
MTLSamplerDescriptor* MVKSampler::getMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo) {
MTLSamplerDescriptor* mtlSampDesc = [[MTLSamplerDescriptor alloc] init];
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;
mtlSampDesc.compareFunctionMVK = (pCreateInfo->compareEnable
? mvkMTLCompareFunctionFromVkCompareOp(pCreateInfo->compareOp)
: MTLCompareFunctionNever);
return [mtlSampDesc autorelease];
}
// Constructs an instance on the specified image.
MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
_mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: getMTLSamplerDescriptor(pCreateInfo)];
}
MVKSampler::~MVKSampler() {
[_mtlSamplerState release];
}
#pragma mark -
#pragma mark MVKSwapchainImage
bool MVKSwapchainImageAvailability_t::operator< (const MVKSwapchainImageAvailability_t& rhs) const {
if ( isAvailable && !rhs.isAvailable) { return true; }
if ( !isAvailable && rhs.isAvailable) { return false; }
if (waitCount < rhs.waitCount) { return true; }
if (waitCount > rhs.waitCount) { return false; }
return acquisitionID < rhs.acquisitionID;
}
// Makes this 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 MVKSwapchainImage::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 now 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.
// Becuase 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 = _preSignaled;
} else {
// If this image is not yet available, extract and signal the first semaphore and fence.
signaler = _availabilitySignalers.front();
_availabilitySignalers.pop_front();
}
// Signal the semaphore and fence, and let them know they are no longer being tracked.
signal(signaler);
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 MVKSwapchainImage::signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
lock_guard<mutex> lock(_availabilityLock);
auto signaler = make_pair(semaphore, fence);
if (_availability.isAvailable) {
_availability.isAvailable = false;
signal(signaler);
_preSignaled = 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());
}
// Signal either or both of the semaphore and fence in the specified tracker pair.
void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler) {
if (signaler.first) { signaler.first->signal(); }
if (signaler.second) { signaler.second->signal(); }
}
// Tell the semaphore and fence that they are being tracked for future signaling.
void MVKSwapchainImage::markAsTracked(MVKSwapchainSignaler& signaler) {
if (signaler.first) { signaler.first->wasAddedToSignaler(); }
if (signaler.second) { signaler.second->wasAddedToSignaler(); }
}
// Tell the semaphore and fence that they are no longer being tracked for future signaling.
void MVKSwapchainImage::unmarkAsTracked(MVKSwapchainSignaler& signaler) {
if (signaler.first) { signaler.first->wasRemovedFromSignaler(); }
if (signaler.second) { signaler.second->wasRemovedFromSignaler(); }
}
const MVKSwapchainImageAvailability* MVKSwapchainImage::getAvailability() {
lock_guard<mutex> lock(_availabilityLock);
_availability.waitCount = (uint32_t)_availabilitySignalers.size();
return &_availability;
}
#pragma mark Metal
// Creates and returns a retained Metal texture suitable for use in this instance.
// This implementation retrieves a MTLTexture from the CAMetalDrawable.
id<MTLTexture> MVKSwapchainImage::newMTLTexture() {
return [[getCAMetalDrawable() texture] retain];
}
id<CAMetalDrawable> MVKSwapchainImage::getCAMetalDrawable() {
if ( !_mtlDrawable ) {
@autoreleasepool { // Allow auto-released drawable object to be reclaimed before end of loop
_mtlDrawable = [_swapchain->getNextCAMetalDrawable() retain]; // retained
}
MVKAssert(_mtlDrawable, "Could not aquire an available CAMetalDrawable from the CAMetalLayer in MVKSwapchain image: %p.", this);
}
return _mtlDrawable;
}
void MVKSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff) {
// MVKLogDebug("Presenting swapchain image %p from present.", this);
id<CAMetalDrawable> mtlDrawable = getCAMetalDrawable();
_swapchain->willPresentSurface(getMTLTexture(), mtlCmdBuff);
// If using a command buffer, present the drawable through it,
// and make myself available only once the command buffer has completed.
// Otherwise, immediately present the drawable and make myself available.
if (mtlCmdBuff) {
[mtlCmdBuff presentDrawable: mtlDrawable];
resetMetalSurface();
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) { makeAvailable(); }];
} else {
[mtlDrawable present];
resetMetalSurface();
makeAvailable();
}
}
// Removes and releases the Metal drawable object, so that it can be lazily created by getCAMetalDrawable().
void MVKSwapchainImage::resetCAMetalDrawable() {
[_mtlDrawable release];
_mtlDrawable = nil;
}
// Resets the MTLTexture and CAMetalDrawable underlying this image.
void MVKSwapchainImage::resetMetalSurface() {
resetMTLTexture(); // Release texture first so drawable will be last to release it
resetCAMetalDrawable();
}
#pragma mark Construction
MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain) : MVKImage(device, pCreateInfo) {
_swapchain = swapchain;
_swapchainIndex = _swapchain->getImageCount();
_availability.acquisitionID = _swapchain->getNextAcquisitionID();
_availability.isAvailable = true;
_preSignaled = make_pair(nullptr, nullptr);
_mtlDrawable = nil;
_canSupportMTLTextureView = false; // Override...swapchains never support Metal image view.
}
MVKSwapchainImage::~MVKSwapchainImage() {
resetCAMetalDrawable();
}