Reduce memory requirements in MVKCmdTransfer.h/mm.

MVKCmdCopyImage, MVKCmdBlitImage, MVKCmdResolveImage generate derivative
arrays inline during encoding instead of holding as member variables.
Separate MVKCmdBlitImage from MVKCmdCopyImage and derive from MVKCommand instead.
This commit is contained in:
Bill Hollings 2020-05-13 20:43:26 -04:00
parent 5f1ea16808
commit ac1889534c
2 changed files with 306 additions and 432 deletions

View File

@ -54,29 +54,13 @@ public:
protected:
MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
VkResult setContent(MVKCommandBuffer* cmdBuff,
VkImage srcImage,
VkImageLayout srcImageLayout,
VkImage dstImage,
VkImageLayout dstImageLayout,
bool formatsMustMatch,
MVKCommandUse commandUse);
void addImageCopyRegion(const VkImageCopy& region, MVKPixelFormats* pixFmts);
void addTempBufferImageCopyRegion(const VkImageCopy& region, MVKPixelFormats* pixFmts);
MVKVectorInline<VkImageCopy, N> _imageCopyRegions;
MVKVectorInline<VkBufferImageCopy, N> _srcTmpBuffImgCopies;
MVKVectorInline<VkBufferImageCopy, N> _dstTmpBuffImgCopies;
size_t _tmpBuffSize;
MVKVectorInline<VkImageCopy, N> _vkImageCopies;
MVKImage* _srcImage;
MVKImage* _dstImage;
VkImageLayout _srcLayout;
VkImageLayout _dstLayout;
MVKCommandUse _commandUse;
bool _isSrcCompressed;
bool _isDstCompressed;
bool _canCopyFormats;
bool _useTempBuffer;
};
// Concrete template class implementations.
@ -101,7 +85,7 @@ typedef struct {
* Template class to balance vector pre-allocations between very common low counts and fewer larger counts.
*/
template <size_t N>
class MVKCmdBlitImage : public MVKCmdCopyImage<N> {
class MVKCmdBlitImage : public MVKCommand {
public:
VkResult setContent(MVKCommandBuffer* cmdBuff,
@ -116,21 +100,19 @@ public:
void encode(MVKCommandEncoder* cmdEncoder) override;
MVKCmdBlitImage();
~MVKCmdBlitImage() override;
protected:
MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
bool canCopyFormats();
bool canCopy(const VkImageBlit& region);
void addImageBlitRegion(const VkImageBlit& region, MVKPixelFormats* pixFmts);
void addImageCopyRegionFromBlitRegion(const VkImageBlit& region, MVKPixelFormats* pixFmts);
void populateVertices(MVKVertexPosTex* vertices, const VkImageBlit& region);
void initMTLRenderPassDescriptor();
MVKVectorInline<MVKImageBlitRender, N> _mvkImageBlitRenders;
MVKRPSKeyBlitImg _blitKey;
MTLRenderPassDescriptor* _mtlRenderPassDescriptor;
MVKVectorInline<VkImageBlit, N> _vkImageBlits;
MVKImage* _srcImage;
MVKImage* _dstImage;
VkImageLayout _srcLayout;
VkImageLayout _dstLayout;
VkFilter _filter;
MVKCommandUse _commandUse;
};
// Concrete template class implementations.
@ -165,22 +147,10 @@ public:
void encode(MVKCommandEncoder* cmdEncoder) override;
MVKCmdResolveImage();
~MVKCmdResolveImage() override;
protected:
MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
void addExpansionRegion(const VkImageResolve& resolveRegion);
void addCopyRegion(const VkImageResolve& resolveRegion);
void addResolveSlices(const VkImageResolve& resolveRegion);
void initMTLRenderPassDescriptor();
MVKVectorInline<VkImageCopy, N> _copyRegions;
MVKVectorInline<VkImageBlit, N> _expansionRegions;
MVKVectorInline<MVKMetalResolveSlice, N> _mtlResolveSlices;
MVKImageDescriptorData _transferImageData;
MTLRenderPassDescriptor* _mtlRenderPassDescriptor;
MVKVectorInline<VkImageResolve, N> _vkImageResolves;
MVKImage* _srcImage;
MVKImage* _dstImage;
VkImageLayout _srcLayout;

View File

@ -56,17 +56,24 @@ VkResult MVKCmdCopyImage<N>::setContent(MVKCommandBuffer* cmdBuff,
uint32_t regionCount,
const VkImageCopy* pRegions,
MVKCommandUse commandUse) {
_srcImage = (MVKImage*)srcImage;
_srcLayout = srcImageLayout;
setContent(cmdBuff, srcImage, srcImageLayout, dstImage, dstImageLayout, false, commandUse);
_dstImage = (MVKImage*)dstImage;
_dstLayout = dstImageLayout;
MVKPixelFormats* pixFmts = cmdBuff->getPixelFormats();
_commandUse = commandUse;
_vkImageCopies.clear(); // Clear for reuse
for (uint32_t i = 0; i < regionCount; i++) {
addImageCopyRegion(pRegions[i], pixFmts);
_vkImageCopies.push_back(pRegions[i]);
}
// Validate
if ( !_canCopyFormats ) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Cannot copy between incompatible formats, such as formats of different pixel sizes.");
MVKPixelFormats* pixFmts = cmdBuff->getPixelFormats();
if ((_dstImage->getSampleCount() != _srcImage->getSampleCount()) ||
(pixFmts->getBytesPerBlock(_dstImage->getMTLPixelFormat()) != pixFmts->getBytesPerBlock(_srcImage->getMTLPixelFormat()))) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Cannot copy between incompatible formats, such as formats of different pixel sizes, or between images with different sample counts.");
}
if ((_srcImage->getMTLTextureType() == MTLTextureType3D) != (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying to or from slices of a 3D texture.");
@ -75,165 +82,111 @@ VkResult MVKCmdCopyImage<N>::setContent(MVKCommandBuffer* cmdBuff,
return VK_SUCCESS;
}
// Sets common content for use by this class and subclasses
template <size_t N>
VkResult MVKCmdCopyImage<N>::setContent(MVKCommandBuffer* cmdBuff,
VkImage srcImage,
VkImageLayout srcImageLayout,
VkImage dstImage,
VkImageLayout dstImageLayout,
bool formatsMustMatch,
MVKCommandUse commandUse) {
MVKPixelFormats* pixFmts = cmdBuff->getPixelFormats();
_srcImage = (MVKImage*)srcImage;
_srcLayout = srcImageLayout;
_isSrcCompressed = _srcImage->getIsCompressed();
MTLPixelFormat srcMTLPixFmt = _srcImage->getMTLPixelFormat();
uint32_t srcSampleCount = mvkSampleCountFromVkSampleCountFlagBits(_srcImage->getSampleCount());
uint32_t srcBytesPerBlock = pixFmts->getBytesPerBlock(srcMTLPixFmt);
_dstImage = (MVKImage*)dstImage;
_dstLayout = dstImageLayout;
_isDstCompressed = _dstImage->getIsCompressed();
MTLPixelFormat dstMTLPixFmt = _dstImage->getMTLPixelFormat();
uint32_t dstSampleCount = mvkSampleCountFromVkSampleCountFlagBits(_dstImage->getSampleCount());
uint32_t dstBytesPerBlock = pixFmts->getBytesPerBlock(dstMTLPixFmt);
_canCopyFormats = (dstSampleCount == srcSampleCount) && (formatsMustMatch
? (dstMTLPixFmt == srcMTLPixFmt)
: (dstBytesPerBlock == srcBytesPerBlock));
_useTempBuffer = (srcMTLPixFmt != dstMTLPixFmt) && (_isSrcCompressed || _isDstCompressed); // Different formats and at least one is compressed
_commandUse = commandUse;
_tmpBuffSize = 0;
_imageCopyRegions.clear(); // Clear for reuse
_srcTmpBuffImgCopies.clear(); // Clear for reuse
_dstTmpBuffImgCopies.clear(); // Clear for reuse
return VK_SUCCESS;
}
template <size_t N>
void MVKCmdCopyImage<N>::addImageCopyRegion(const VkImageCopy& region, MVKPixelFormats* pixFmts) {
if (_useTempBuffer) {
addTempBufferImageCopyRegion(region, pixFmts); // Convert to image->buffer->image copies
} else {
_imageCopyRegions.push_back(region);
}
}
// Add an image->buffer copy and buffer->image copy to replace the image->image copy
template <size_t N>
void MVKCmdCopyImage<N>::addTempBufferImageCopyRegion(const VkImageCopy& region, MVKPixelFormats* pixFmts) {
// Add copy from source image to temp buffer.
VkBufferImageCopy buffImgCpy;
buffImgCpy.bufferOffset = _tmpBuffSize;
buffImgCpy.bufferRowLength = 0;
buffImgCpy.bufferImageHeight = 0;
buffImgCpy.imageSubresource = region.srcSubresource;
buffImgCpy.imageOffset = region.srcOffset;
buffImgCpy.imageExtent = region.extent;
_srcTmpBuffImgCopies.push_back(buffImgCpy);
// Add copy from temp buffer to destination image.
// Extent is provided in source texels. If the source is compressed but the
// destination is not, each destination pixel will consume an entire source block,
// so we must downscale the destination extent by the size of the source block.
MTLPixelFormat srcMTLPixFmt = _srcImage->getMTLPixelFormat();
VkExtent3D dstExtent = region.extent;
if (_isSrcCompressed && !_isDstCompressed) {
VkExtent2D srcBlockExtent = pixFmts->getBlockTexelSize(srcMTLPixFmt);
dstExtent.width /= srcBlockExtent.width;
dstExtent.height /= srcBlockExtent.height;
}
buffImgCpy.bufferOffset = _tmpBuffSize;
buffImgCpy.bufferRowLength = 0;
buffImgCpy.bufferImageHeight = 0;
buffImgCpy.imageSubresource = region.dstSubresource;
buffImgCpy.imageOffset = region.dstOffset;
buffImgCpy.imageExtent = dstExtent;
_dstTmpBuffImgCopies.push_back(buffImgCpy);
NSUInteger bytesPerRow = pixFmts->getBytesPerRow(srcMTLPixFmt, region.extent.width);
NSUInteger bytesPerRegion = pixFmts->getBytesPerLayer(srcMTLPixFmt, bytesPerRow, region.extent.height);
_tmpBuffSize += bytesPerRegion;
}
template <size_t N>
void MVKCmdCopyImage<N>::encode(MVKCommandEncoder* cmdEncoder) {
// Unless we need to use an intermediary buffer copy, map the source pixel format to the
// dest pixel format through a texture view on the source texture. If the source and dest
// pixel formats are the same, this will simply degenerate to the source texture itself.
MTLPixelFormat mapSrcMTLPixFmt = (_useTempBuffer ? _srcImage : _dstImage)->getMTLPixelFormat();
id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture(mapSrcMTLPixFmt);
id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
if ( !srcMTLTex || !dstMTLTex ) { return; }
id<MTLBlitCommandEncoder> mtlBlitEnc = cmdEncoder->getMTLBlitEncoder(_commandUse);
MTLPixelFormat srcMTLPixFmt = _srcImage->getMTLPixelFormat();
bool isSrcCompressed = _srcImage->getIsCompressed();
// If copies can be performed using direct texture-texture copying, do so
for (auto& cpyRgn : _imageCopyRegions) {
uint32_t srcLevel = cpyRgn.srcSubresource.mipLevel;
MTLOrigin srcOrigin = mvkMTLOriginFromVkOffset3D(cpyRgn.srcOffset);
MTLSize srcSize = mvkClampMTLSize(mvkMTLSizeFromVkExtent3D(cpyRgn.extent),
srcOrigin,
mvkMTLSizeFromVkExtent3D(_srcImage->getExtent3D(srcLevel)));
uint32_t dstLevel = cpyRgn.dstSubresource.mipLevel;
MTLOrigin dstOrigin = mvkMTLOriginFromVkOffset3D(cpyRgn.dstOffset);
uint32_t srcBaseLayer = cpyRgn.srcSubresource.baseArrayLayer;
uint32_t dstBaseLayer = cpyRgn.dstSubresource.baseArrayLayer;
uint32_t layCnt = cpyRgn.srcSubresource.layerCount;
MTLPixelFormat dstMTLPixFmt = _dstImage->getMTLPixelFormat();
bool isDstCompressed = _dstImage->getIsCompressed();
for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
[mtlBlitEnc copyFromTexture: srcMTLTex
sourceSlice: srcBaseLayer + layIdx
sourceLevel: srcLevel
sourceOrigin: srcOrigin
sourceSize: srcSize
toTexture: dstMTLTex
destinationSlice: dstBaseLayer + layIdx
destinationLevel: dstLevel
destinationOrigin: dstOrigin];
// If source and destination have different formats and at least one is compressed, use a temporary intermediary buffer
bool useTempBuffer = (srcMTLPixFmt != dstMTLPixFmt) && (isSrcCompressed || isDstCompressed);
if (useTempBuffer) {
MVKPixelFormats* pixFmts = cmdEncoder->getPixelFormats();
uint32_t copyCnt = (uint32_t)_vkImageCopies.size();
VkBufferImageCopy vkSrcCopies[copyCnt];
VkBufferImageCopy vkDstCopies[copyCnt];
size_t tmpBuffSize = 0;
for (uint32_t copyIdx = 0; copyIdx < copyCnt; copyIdx++) {
auto& vkIC = _vkImageCopies[copyIdx];
// Add copy from source image to temp buffer.
auto& srcCpy = vkSrcCopies[copyIdx];
srcCpy.bufferOffset = tmpBuffSize;
srcCpy.bufferRowLength = 0;
srcCpy.bufferImageHeight = 0;
srcCpy.imageSubresource = vkIC.srcSubresource;
srcCpy.imageOffset = vkIC.srcOffset;
srcCpy.imageExtent = vkIC.extent;
// Add copy from temp buffer to destination image.
// Extent is provided in source texels. If the source is compressed but the
// destination is not, each destination pixel will consume an entire source block,
// so we must downscale the destination extent by the size of the source block.
VkExtent3D dstExtent = vkIC.extent;
if (isSrcCompressed && !isDstCompressed) {
VkExtent2D srcBlockExtent = pixFmts->getBlockTexelSize(srcMTLPixFmt);
dstExtent.width /= srcBlockExtent.width;
dstExtent.height /= srcBlockExtent.height;
}
auto& dstCpy = vkDstCopies[copyIdx];
dstCpy.bufferOffset = tmpBuffSize;
dstCpy.bufferRowLength = 0;
dstCpy.bufferImageHeight = 0;
dstCpy.imageSubresource = vkIC.dstSubresource;
dstCpy.imageOffset = vkIC.dstOffset;
dstCpy.imageExtent = dstExtent;
size_t bytesPerRow = pixFmts->getBytesPerRow(srcMTLPixFmt, vkIC.extent.width);
size_t bytesPerRegion = pixFmts->getBytesPerLayer(srcMTLPixFmt, bytesPerRow, vkIC.extent.height);
tmpBuffSize += bytesPerRegion;
}
}
// If copies could not be performed directly between images,
// use a temporary buffer acting as a waystation between the images.
if ( !_srcTmpBuffImgCopies.empty() ) {
MVKBufferDescriptorData tempBuffData;
tempBuffData.size = _tmpBuffSize;
tempBuffData.size = tmpBuffSize;
tempBuffData.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
MVKBuffer* tempBuff = cmdEncoder->getCommandEncodingPool()->getTransferMVKBuffer(tempBuffData);
VkBuffer tempBuff = (VkBuffer)cmdEncoder->getCommandEncodingPool()->getTransferMVKBuffer(tempBuffData);
MVKCmdBufferImageCopy<N> cpyCmd;
// Copy from source image to buffer
// Create and execute a temporary buffer image command.
// To be threadsafe...do NOT acquire and return the command from the pool.
cpyCmd.setContent(cmdEncoder->_cmdBuffer,
(VkBuffer) tempBuff,
(VkImage) _srcImage,
_srcLayout,
(uint32_t)_srcTmpBuffImgCopies.size(),
_srcTmpBuffImgCopies.data(),
false);
cpyCmd.setContent(cmdEncoder->_cmdBuffer, tempBuff, (VkImage)_srcImage, _srcLayout, copyCnt, vkSrcCopies, false);
cpyCmd.encode(cmdEncoder);
// Copy from buffer to destination image
// Create and execute a temporary buffer image command.
// To be threadsafe...do NOT acquire and return the command from the pool.
cpyCmd.setContent(cmdEncoder->_cmdBuffer,
(VkBuffer) tempBuff,
(VkImage) _dstImage,
_dstLayout,
(uint32_t)_dstTmpBuffImgCopies.size(),
_dstTmpBuffImgCopies.data(),
true);
cpyCmd.setContent(cmdEncoder->_cmdBuffer, tempBuff, (VkImage)_dstImage, _dstLayout, copyCnt, vkDstCopies, true);
cpyCmd.encode(cmdEncoder);
} else {
// Map the source pixel format to the dest pixel format through a texture view on the source texture.
// If the source and dest pixel formats are the same, this will simply degenerate to the source texture itself.
id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture(_dstImage->getMTLPixelFormat());
id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
if ( !srcMTLTex || !dstMTLTex ) { return; }
id<MTLBlitCommandEncoder> mtlBlitEnc = cmdEncoder->getMTLBlitEncoder(_commandUse);
// If copies can be performed using direct texture-texture copying, do so
for (auto& cpyRgn : _vkImageCopies) {
uint32_t srcLevel = cpyRgn.srcSubresource.mipLevel;
MTLOrigin srcOrigin = mvkMTLOriginFromVkOffset3D(cpyRgn.srcOffset);
MTLSize srcSize = mvkClampMTLSize(mvkMTLSizeFromVkExtent3D(cpyRgn.extent),
srcOrigin,
mvkMTLSizeFromVkExtent3D(_srcImage->getExtent3D(srcLevel)));
uint32_t dstLevel = cpyRgn.dstSubresource.mipLevel;
MTLOrigin dstOrigin = mvkMTLOriginFromVkOffset3D(cpyRgn.dstOffset);
uint32_t srcBaseLayer = cpyRgn.srcSubresource.baseArrayLayer;
uint32_t dstBaseLayer = cpyRgn.dstSubresource.baseArrayLayer;
uint32_t layCnt = cpyRgn.srcSubresource.layerCount;
for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
[mtlBlitEnc copyFromTexture: srcMTLTex
sourceSlice: srcBaseLayer + layIdx
sourceLevel: srcLevel
sourceOrigin: srcOrigin
sourceSize: srcSize
toTexture: dstMTLTex
destinationSlice: dstBaseLayer + layIdx
destinationLevel: dstLevel
destinationOrigin: dstOrigin];
}
}
}
}
@ -255,47 +208,39 @@ VkResult MVKCmdBlitImage<N>::setContent(MVKCommandBuffer* cmdBuff,
VkFilter filter,
MVKCommandUse commandUse) {
VkResult rslt = MVKCmdCopyImage<N>::setContent(cmdBuff, srcImage, srcImageLayout, dstImage, dstImageLayout, true, commandUse);
MVKImage* srcImg = MVKCmdCopyImage<N>::_srcImage;
MVKImage* dstImg = MVKCmdCopyImage<N>::_dstImage;
_blitKey.srcMTLPixelFormat = srcImg->getMTLPixelFormat();
_blitKey.srcMTLTextureType = srcImg->getMTLTextureType();
_blitKey.dstMTLPixelFormat = dstImg->getMTLPixelFormat();
_blitKey.srcFilter = mvkMTLSamplerMinMagFilterFromVkFilter(filter);
_blitKey.dstSampleCount = mvkSampleCountFromVkSampleCountFlagBits(dstImg->getSampleCount());
MVKPixelFormats* pixFmts = cmdBuff->getPixelFormats();
_mvkImageBlitRenders.clear(); // Clear for reuse
_srcImage = (MVKImage*)srcImage;
_srcLayout = srcImageLayout;
_dstImage = (MVKImage*)dstImage;
_dstLayout = dstImageLayout;
_filter = filter;
_commandUse = commandUse;
_vkImageBlits.clear(); // Clear for reuse
for (uint32_t i = 0; i < regionCount; i++) {
addImageBlitRegion(pRegions[i], pixFmts);
_vkImageBlits.push_back(pRegions[i]);
}
// Validate
MTLPixelFormat srcMTLPixFmt = srcImg->getMTLPixelFormat();
if ( !_mvkImageBlitRenders.empty() &&
(pixFmts->isDepthFormat(srcMTLPixFmt) || pixFmts->isStencilFormat(srcMTLPixFmt)) ) {
_mvkImageBlitRenders.clear();
return MVKCmdCopyImage<N>::reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Scaling or inverting depth/stencil images is not supported.");
// Validate - depth stencil formats cannot be scaled or inverted
MTLPixelFormat srcMTLPixFmt = _srcImage->getMTLPixelFormat();
if (pixFmts->isDepthFormat(srcMTLPixFmt) || pixFmts->isStencilFormat(srcMTLPixFmt)) {
bool canCopyFmts = canCopyFormats();
for (auto& vkIB : _vkImageBlits) {
if ( !(canCopyFmts && canCopy(vkIB)) ) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Scaling or inverting depth/stencil images is not supported.");
}
}
}
return rslt;
return VK_SUCCESS;
}
template <size_t N>
void MVKCmdBlitImage<N>::addImageBlitRegion(const VkImageBlit& region,
MVKPixelFormats* pixFmts) {
if (MVKCmdCopyImage<N>::_canCopyFormats && canCopy(region)) {
addImageCopyRegionFromBlitRegion(region, pixFmts); // Convert to image copy
} else {
MVKImageBlitRender blitRender;
blitRender.region = region;
populateVertices(blitRender.vertices, region);
_mvkImageBlitRenders.push_back(blitRender);
}
bool MVKCmdBlitImage<N>::canCopyFormats() {
return ((_srcImage->getMTLPixelFormat() == _dstImage->getMTLPixelFormat()) &&
(_dstImage->getSampleCount() == _srcImage->getSampleCount()));
}
// The source and destination sizes must be equal and not be negative in any direction
@ -307,24 +252,6 @@ bool MVKCmdBlitImage<N>::canCopy(const VkImageBlit& region) {
(srcSize.x >= 0) && (srcSize.y >= 0) && (srcSize.z >= 0));
}
template <size_t N>
void MVKCmdBlitImage<N>::addImageCopyRegionFromBlitRegion(const VkImageBlit& region,
MVKPixelFormats* pixFmts) {
const VkOffset3D& so0 = region.srcOffsets[0];
const VkOffset3D& so1 = region.srcOffsets[1];
VkImageCopy cpyRgn;
cpyRgn.srcSubresource = region.srcSubresource;
cpyRgn.srcOffset = region.srcOffsets[0];
cpyRgn.dstSubresource = region.dstSubresource;
cpyRgn.dstOffset = region.dstOffsets[0];
cpyRgn.extent.width = so1.x - so0.x;
cpyRgn.extent.height = so1.y - so0.y;
cpyRgn.extent.depth = so1.z - so0.z;
MVKCmdCopyImage<N>::addImageCopyRegion(cpyRgn, pixFmts);
}
template <size_t N>
void MVKCmdBlitImage<N>::populateVertices(MVKVertexPosTex* vertices, const VkImageBlit& region) {
const VkOffset3D& so0 = region.srcOffsets[0];
@ -333,8 +260,8 @@ void MVKCmdBlitImage<N>::populateVertices(MVKVertexPosTex* vertices, const VkIma
const VkOffset3D& do1 = region.dstOffsets[1];
// Get the extents of the source and destination textures.
VkExtent3D srcExtent = MVKCmdCopyImage<N>::_srcImage->getExtent3D(region.srcSubresource.mipLevel);
VkExtent3D dstExtent = MVKCmdCopyImage<N>::_dstImage->getExtent3D(region.dstSubresource.mipLevel);
VkExtent3D srcExtent = _srcImage->getExtent3D(region.srcSubresource.mipLevel);
VkExtent3D dstExtent = _dstImage->getExtent3D(region.dstSubresource.mipLevel);
// Determine the bottom-left and top-right corners of the source and destination
// texture regions, each as a fraction of the corresponding texture size.
@ -387,48 +314,93 @@ void MVKCmdBlitImage<N>::populateVertices(MVKVertexPosTex* vertices, const VkIma
template <size_t N>
void MVKCmdBlitImage<N>::encode(MVKCommandEncoder* cmdEncoder) {
size_t vkIBCnt = _vkImageBlits.size();
VkImageCopy vkImageCopies[vkIBCnt];
MVKImageBlitRender mvkBlitRenders[vkIBCnt];
uint32_t copyCnt = 0;
uint32_t blitCnt = 0;
// Separate BLITs into those that are really just simple texure region copies,
// and those that require rendering
bool canCopyFmts = canCopyFormats();
for (auto& vkIB : _vkImageBlits) {
if (canCopyFmts && canCopy(vkIB)) {
const VkOffset3D& so0 = vkIB.srcOffsets[0];
const VkOffset3D& so1 = vkIB.srcOffsets[1];
auto& vkIC = vkImageCopies[copyCnt++];
vkIC.srcSubresource = vkIB.srcSubresource;
vkIC.srcOffset = vkIB.srcOffsets[0];
vkIC.dstSubresource = vkIB.dstSubresource;
vkIC.dstOffset = vkIB.dstOffsets[0];
vkIC.extent.width = so1.x - so0.x;
vkIC.extent.height = so1.y - so0.y;
vkIC.extent.depth = so1.z - so0.z;
} else {
auto& mvkIBR = mvkBlitRenders[blitCnt++];
mvkIBR.region = vkIB;
populateVertices(mvkIBR.vertices, vkIB);
}
}
// Perform those BLITs that can be covered by simple texture copying.
if ( !MVKCmdCopyImage<N>::_imageCopyRegions.empty() ) {
MVKCmdCopyImage<N>::encode(cmdEncoder);
if (copyCnt) {
MVKCmdCopyImage<N> copyCmd;
copyCmd.setContent(cmdEncoder->_cmdBuffer,
(VkImage)_srcImage, _srcLayout,
(VkImage)_dstImage, _dstLayout,
copyCnt, vkImageCopies, kMVKCommandUseBlitImage);
copyCmd.encode(cmdEncoder);
}
// Perform those BLITs that require rendering to destination texture.
if ( !_mvkImageBlitRenders.empty() ) {
id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture();
id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
if (blitCnt && srcMTLTex && dstMTLTex) {
cmdEncoder->endCurrentMetalEncoding();
id<MTLTexture> srcMTLTex = MVKCmdCopyImage<N>::_srcImage->getMTLTexture();
id<MTLTexture> dstMTLTex = MVKCmdCopyImage<N>::_dstImage->getMTLTexture();
if ( !srcMTLTex || !dstMTLTex ) { return; }
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = _mtlRenderPassDescriptor.colorAttachments[0];
MTLRenderPassDescriptor* mtlRPD = [MTLRenderPassDescriptor renderPassDescriptor];
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = mtlRPD.colorAttachments[0];
mtlColorAttDesc.loadAction = MTLLoadActionLoad;
mtlColorAttDesc.storeAction = MTLStoreActionStore;
mtlColorAttDesc.texture = dstMTLTex;
MVKRPSKeyBlitImg blitKey;
blitKey.srcMTLPixelFormat = _srcImage->getMTLPixelFormat();
blitKey.srcMTLTextureType = _srcImage->getMTLTextureType();
blitKey.dstMTLPixelFormat = _dstImage->getMTLPixelFormat();
blitKey.srcFilter = mvkMTLSamplerMinMagFilterFromVkFilter(_filter);
blitKey.dstSampleCount = mvkSampleCountFromVkSampleCountFlagBits(_dstImage->getSampleCount());
id<MTLRenderPipelineState> mtlRPS = cmdEncoder->getCommandEncodingPool()->getCmdBlitImageMTLRenderPipelineState(blitKey);
uint32_t vtxBuffIdx = cmdEncoder->getDevice()->getMetalBufferIndexForVertexAttributeBinding(kMVKVertexContentBufferIndex);
id<MTLRenderPipelineState> mtlRPS = cmdEncoder->getCommandEncodingPool()->getCmdBlitImageMTLRenderPipelineState(_blitKey);
for (auto& bltRend : _mvkImageBlitRenders) {
for (uint32_t blitIdx = 0; blitIdx < blitCnt; blitIdx++) {
auto& mvkIBR = mvkBlitRenders[blitIdx];
mtlColorAttDesc.level = bltRend.region.dstSubresource.mipLevel;
mtlColorAttDesc.level = mvkIBR.region.dstSubresource.mipLevel;
uint32_t layCnt = bltRend.region.srcSubresource.layerCount;
uint32_t layCnt = mvkIBR.region.srcSubresource.layerCount;
for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
// Update the render pass descriptor for the texture level and slice, and create a render encoder.
mtlColorAttDesc.slice = bltRend.region.dstSubresource.baseArrayLayer + layIdx;
id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: _mtlRenderPassDescriptor];
setLabelIfNotNil(mtlRendEnc, mvkMTLRenderCommandEncoderLabel(MVKCmdCopyImage<N>::_commandUse));
mtlColorAttDesc.slice = mvkIBR.region.dstSubresource.baseArrayLayer + layIdx;
id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPD];
setLabelIfNotNil(mtlRendEnc, mvkMTLRenderCommandEncoderLabel(_commandUse));
[mtlRendEnc pushDebugGroup: @"vkCmdBlitImage"];
[mtlRendEnc setRenderPipelineState: mtlRPS];
cmdEncoder->setVertexBytes(mtlRendEnc, bltRend.vertices, sizeof(bltRend.vertices), vtxBuffIdx);
cmdEncoder->setVertexBytes(mtlRendEnc, mvkIBR.vertices, sizeof(mvkIBR.vertices), vtxBuffIdx);
[mtlRendEnc setFragmentTexture: srcMTLTex atIndex: 0];
struct {
uint slice;
float lod;
} texSubRez;
texSubRez.slice = bltRend.region.srcSubresource.baseArrayLayer + layIdx;
texSubRez.lod = bltRend.region.srcSubresource.mipLevel;
texSubRez.slice = mvkIBR.region.srcSubresource.baseArrayLayer + layIdx;
texSubRez.lod = mvkIBR.region.srcSubresource.mipLevel;
cmdEncoder->setFragmentBytes(mtlRendEnc, &texSubRez, sizeof(texSubRez), 0);
[mtlRendEnc drawPrimitives: MTLPrimitiveTypeTriangleStrip vertexStart: 0 vertexCount: kMVKBlitVertexCount];
@ -439,28 +411,6 @@ void MVKCmdBlitImage<N>::encode(MVKCommandEncoder* cmdEncoder) {
}
}
#pragma mark Construction
template <size_t N>
MVKCmdBlitImage<N>::MVKCmdBlitImage() {
initMTLRenderPassDescriptor();
}
// Create and configure the render pass descriptor
template <size_t N>
void MVKCmdBlitImage<N>::initMTLRenderPassDescriptor() {
_mtlRenderPassDescriptor = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; // retained
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = _mtlRenderPassDescriptor.colorAttachments[0];
mtlColorAttDesc.loadAction = MTLLoadActionLoad;
mtlColorAttDesc.storeAction = MTLStoreActionStore;
}
template <size_t N>
MVKCmdBlitImage<N>::~MVKCmdBlitImage() {
[_mtlRenderPassDescriptor release];
}
template class MVKCmdBlitImage<1>;
template class MVKCmdBlitImage<4>;
@ -481,183 +431,137 @@ VkResult MVKCmdResolveImage<N>::setContent(MVKCommandBuffer* cmdBuff,
_dstImage = (MVKImage*)dstImage;
_dstLayout = dstImageLayout;
// Deterine the total number of texture layers being affected
uint32_t layerCnt = 0;
_vkImageResolves.clear(); // Clear for reuse
_vkImageResolves.reserve(regionCount);
for (uint32_t i = 0; i < regionCount; i++) {
layerCnt += pRegions[i].dstSubresource.layerCount;
_vkImageResolves.push_back(pRegions[i]);
}
// Resize the region arrays accordingly
_expansionRegions.clear(); // Clear for reuse
_expansionRegions.reserve(regionCount);
_copyRegions.clear(); // Clear for reuse
_copyRegions.reserve(regionCount);
_mtlResolveSlices.clear(); // Clear for reuse
_mtlResolveSlices.reserve(layerCnt);
// Add image regions
for (uint32_t i = 0; i < regionCount; i++) {
const VkImageResolve& rslvRgn = pRegions[i];
addExpansionRegion(rslvRgn);
addCopyRegion(rslvRgn);
addResolveSlices(rslvRgn);
}
_dstImage->getTransferDescriptorData(_transferImageData);
_transferImageData.samples = _srcImage->getSampleCount();
// Validate
if ( !mvkAreAllFlagsEnabled(cmdBuff->getPixelFormats()->getCapabilities(_dstImage->getMTLPixelFormat()), kMVKMTLFmtCapsResolve) ) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdResolveImage(): %s cannot be used as a resolve destination on this device.", cmdBuff->getPixelFormats()->getName(_dstImage->getVkFormat()));
MVKPixelFormats* pixFmts = cmdBuff->getPixelFormats();
if ( !mvkAreAllFlagsEnabled(pixFmts->getCapabilities(_dstImage->getMTLPixelFormat()), kMVKMTLFmtCapsResolve) ) {
return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdResolveImage(): %s cannot be used as a resolve destination on this device.", pixFmts->getName(_dstImage->getVkFormat()));
}
return VK_SUCCESS;
}
// Adds a VkImageBlit region, constructed from the resolve region, to the internal collection
// of expansion regions, unless the entire content of the destination texture of this command
// is to be resolved, an expansion region will not be added.
//
// The purpose of an expansion regions is to render the existing content of the destination
// image of this command to the temporary transfer multisample image, so that regions of that
// temporary transfer image can then be overwritten with content from the source image of this
// command, prior to resolving it back to the destination image of this command.
//
// As such, the source of this expansion stage is the destination image of this command,
// and the destination of this expansion stage is a temp image that has the same shape
// as the source image of this command.
template <size_t N>
void MVKCmdResolveImage<N>::addExpansionRegion(const VkImageResolve& resolveRegion) {
uint32_t mipLvl = resolveRegion.dstSubresource.mipLevel;
VkExtent3D srcImgExt = _srcImage->getExtent3D(mipLvl);
VkExtent3D dstImgExt = _dstImage->getExtent3D(mipLvl);
// No need to add an expansion region if the entire content of
// the source image is being resolved to the destination image.
if (mvkVkExtent3DsAreEqual(srcImgExt, resolveRegion.extent)) { return; }
// The source of this temporary content move is the full extent of the DESTINATION
// image of the resolve command, and the destination of this temporary content move
// is the full extent of the SOURCE image of the resolve command.
VkImageBlit expRgn = {
.srcSubresource = resolveRegion.dstSubresource,
.srcOffsets[0] = { 0, 0, 0 },
.srcOffsets[1] = { int32_t(dstImgExt.width), int32_t(dstImgExt.height), int32_t(dstImgExt.depth) },
.dstSubresource = resolveRegion.dstSubresource,
.dstOffsets[0] = { 0, 0, 0 },
.dstOffsets[1] = { int32_t(srcImgExt.width), int32_t(srcImgExt.height), int32_t(srcImgExt.depth) },
};
_expansionRegions.push_back(expRgn);
}
// Adds a VkImageCopy region, constructed from the resolve region,
// to the internal collection of copy regions.
//
// The purpose of a copy region is to copy regions from the source image of this command to
// the temporary image, prior to the temporary image being resolved back to the destination
// image of this command.
//
// As such, the source of this copy stage is the source image of this command, and the
// destination of this copy stage is the temporary transfer image that has the same shape
// as the source image of this command.
template <size_t N>
void MVKCmdResolveImage<N>::addCopyRegion(const VkImageResolve& resolveRegion) {
VkImageCopy cpyRgn = {
.srcSubresource = resolveRegion.srcSubresource,
.srcOffset = resolveRegion.srcOffset,
.dstSubresource = resolveRegion.srcSubresource,
.dstOffset = resolveRegion.srcOffset,
.extent = resolveRegion.extent,
};
_copyRegions.push_back(cpyRgn);
}
// Adds a resolve slice struct for each destination layer in the resolve region.
template <size_t N>
void MVKCmdResolveImage<N>::addResolveSlices(const VkImageResolve& resolveRegion) {
MVKMetalResolveSlice rslvSlice;
rslvSlice.level = resolveRegion.dstSubresource.mipLevel;
uint32_t baseLayer = resolveRegion.dstSubresource.baseArrayLayer;
uint32_t layCnt = resolveRegion.dstSubresource.layerCount;
for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
rslvSlice.slice = baseLayer + layIdx;
_mtlResolveSlices.push_back(rslvSlice);
}
}
template <size_t N>
void MVKCmdResolveImage<N>::encode(MVKCommandEncoder* cmdEncoder) {
MVKImage* xfrImage = cmdEncoder->getCommandEncodingPool()->getTransferMVKImage(_transferImageData);
id<MTLTexture> xfrMTLTex = xfrImage->getMTLTexture();
id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
if ( !xfrMTLTex || !dstMTLTex ) { return; }
size_t vkIRCnt = _vkImageResolves.size();
VkImageBlit expansionRegions[vkIRCnt];
VkImageCopy copyRegions[vkIRCnt];
// Expand the current content of the destination image to the temporary transfer image.
// Create and execute a temporary BLIT image command.
// To be threadsafe...do NOT acquire and return the command from the pool.
uint32_t expRgnCnt = uint32_t(_expansionRegions.size());
if (expRgnCnt > 0) {
MVKCmdBlitImage<N> expandCmd;
expandCmd.setContent(cmdEncoder->_cmdBuffer,
(VkImage)_dstImage, _dstLayout, (VkImage)xfrImage, _dstLayout,
expRgnCnt, _expansionRegions.data(),
VK_FILTER_LINEAR, kMVKCommandUseResolveExpandImage);
expandCmd.encode(cmdEncoder);
}
uint32_t layerCnt = 0;
for (VkImageResolve& vkIR : _vkImageResolves) { layerCnt += vkIR.dstSubresource.layerCount; }
MVKMetalResolveSlice mtlResolveSlices[layerCnt];
// Copy the resolve regions of the source image to the temporary transfer image.
// Create and execute a temporary copy image command.
// To be threadsafe...do NOT acquire and return the command from the pool.
uint32_t cpyRgnCnt = uint32_t(_copyRegions.size());
if (cpyRgnCnt > 0) {
MVKCmdCopyImage<N> copyCmd;
copyCmd.setContent(cmdEncoder->_cmdBuffer,
uint32_t expCnt = 0;
uint32_t copyCnt = 0;
uint32_t sliceCnt = 0;
for (VkImageResolve& vkIR : _vkImageResolves) {
uint32_t mipLvl = vkIR.dstSubresource.mipLevel;
VkExtent3D srcImgExt = _srcImage->getExtent3D(mipLvl);
VkExtent3D dstImgExt = _dstImage->getExtent3D(mipLvl);
// If the region does not cover the entire content of the source level, expand the
// destination content in the region to the temporary image. The purpose of this
// expansion is to render the existing content of the destination image to the
// temporary transfer multisample image, so that regions of that temporary transfer
// image can then be overwritten with content from the source image, prior to
// resolving it back to the destination image. The source of this temporary content
// move is the full extent of the DESTINATION image of the resolve command, and the
// destination of this temporary content move is the full extent of the SOURCE image.
if ( !mvkVkExtent3DsAreEqual(srcImgExt, vkIR.extent) ) {
VkImageBlit& expRgn = expansionRegions[expCnt++];
expRgn.srcSubresource = vkIR.dstSubresource;
expRgn.srcOffsets[0] = { 0, 0, 0 };
expRgn.srcOffsets[1] = { int32_t(dstImgExt.width), int32_t(dstImgExt.height), int32_t(dstImgExt.depth) };
expRgn.dstSubresource = vkIR.dstSubresource;
expRgn.dstOffsets[0] = { 0, 0, 0 };
expRgn.dstOffsets[1] = { int32_t(srcImgExt.width), int32_t(srcImgExt.height), int32_t(srcImgExt.depth) };
}
// Copy the region from the source image to the temporary multisample image,
// prior to the temporary image being resolved back to the destination image.
// The source of this copy stage is the source image, and the destination of
// this copy stage is the temporary transfer image.
VkImageCopy& cpyRgn = copyRegions[copyCnt++];
cpyRgn.srcSubresource = vkIR.srcSubresource;
cpyRgn.srcOffset = vkIR.srcOffset;
cpyRgn.dstSubresource = vkIR.srcSubresource;
cpyRgn.dstOffset = vkIR.srcOffset;
cpyRgn.extent = vkIR.extent;
// Adds a resolve slice struct for each destination layer in the resolve region.
uint32_t baseLayer = vkIR.dstSubresource.baseArrayLayer;
uint32_t layCnt = vkIR.dstSubresource.layerCount;
for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
MVKMetalResolveSlice& rslvSlice = mtlResolveSlices[sliceCnt++];
rslvSlice.level = vkIR.dstSubresource.mipLevel;
rslvSlice.slice = baseLayer + layIdx;
}
}
id<MTLTexture> srcMTLTex;
if (expCnt == 0) {
// Expansion and copying is not required. Each mip level of the source image
// is being resolved entirely. Resolve directly from the source image.
srcMTLTex = _srcImage->getMTLTexture();
} else {
// Expansion and copying is required. Acquire a temporary transfer image, expand
// the destination image into it, copy from the source image to the temporary image,
// and then resolve from the temporary image to the destination image.
MVKImageDescriptorData xferImageData;
_dstImage->getTransferDescriptorData(xferImageData);
xferImageData.samples = _srcImage->getSampleCount();
MVKImage* xfrImage = cmdEncoder->getCommandEncodingPool()->getTransferMVKImage(xferImageData);
// Expand the current content of the destination image to the temporary transfer image.
MVKCmdBlitImage<N> expCmd;
expCmd.setContent(cmdEncoder->_cmdBuffer,
(VkImage)_dstImage, _dstLayout, (VkImage)xfrImage, _dstLayout,
expCnt, expansionRegions, VK_FILTER_LINEAR, kMVKCommandUseResolveExpandImage);
expCmd.encode(cmdEncoder);
// Copy the resolve regions of the source image to the temporary transfer image.
MVKCmdCopyImage<N> copyCmd;
copyCmd.setContent(cmdEncoder->_cmdBuffer,
(VkImage)_srcImage, _srcLayout, (VkImage)xfrImage, _dstLayout,
cpyRgnCnt, _copyRegions.data(), kMVKCommandUseResolveCopyImage);
copyCmd.encode(cmdEncoder);
}
copyCnt, copyRegions, kMVKCommandUseResolveCopyImage);
copyCmd.encode(cmdEncoder);
cmdEncoder->endCurrentMetalEncoding();
srcMTLTex = xfrImage->getMTLTexture();
}
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = _mtlRenderPassDescriptor.colorAttachments[0];
mtlColorAttDesc.texture = xfrMTLTex;
mtlColorAttDesc.resolveTexture = dstMTLTex;
cmdEncoder->endCurrentMetalEncoding();
for (auto& rslvSlice : _mtlResolveSlices) {
MTLRenderPassDescriptor* mtlRPD = [MTLRenderPassDescriptor renderPassDescriptor];
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = mtlRPD.colorAttachments[0];
mtlColorAttDesc.loadAction = MTLLoadActionLoad;
mtlColorAttDesc.storeAction = MTLStoreActionMultisampleResolve;
mtlColorAttDesc.texture = srcMTLTex;
mtlColorAttDesc.resolveTexture = _dstImage->getMTLTexture();
// Update the render pass descriptor for the texture level and slice, and create a render encoder.
mtlColorAttDesc.level = rslvSlice.level;
mtlColorAttDesc.slice = rslvSlice.slice;
mtlColorAttDesc.resolveLevel = rslvSlice.level;
mtlColorAttDesc.resolveSlice = rslvSlice.slice;
id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: _mtlRenderPassDescriptor];
// For each resolve slice, update the render pass descriptor for
// the texture level and slice and create a render encoder.
for (uint32_t sIdx = 0; sIdx < sliceCnt; sIdx++) {
MVKMetalResolveSlice& rslvSlice = mtlResolveSlices[sIdx];
mtlColorAttDesc.level = rslvSlice.level;
mtlColorAttDesc.slice = rslvSlice.slice;
mtlColorAttDesc.resolveLevel = rslvSlice.level;
mtlColorAttDesc.resolveSlice = rslvSlice.slice;
id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPD];
setLabelIfNotNil(mtlRendEnc, mvkMTLRenderCommandEncoderLabel(kMVKCommandUseResolveImage));
[mtlRendEnc pushDebugGroup: @"vkCmdResolveImage"];
[mtlRendEnc popDebugGroup];
[mtlRendEnc endEncoding];
}
}
template <size_t N>
MVKCmdResolveImage<N>::MVKCmdResolveImage() {
initMTLRenderPassDescriptor();
}
// Create and configure the render pass descriptor
template <size_t N>
void MVKCmdResolveImage<N>::initMTLRenderPassDescriptor() {
_mtlRenderPassDescriptor = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; // retained
MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = _mtlRenderPassDescriptor.colorAttachments[0];
mtlColorAttDesc.loadAction = MTLLoadActionLoad;
mtlColorAttDesc.storeAction = MTLStoreActionMultisampleResolve;
}
template <size_t N>
MVKCmdResolveImage<N>::~MVKCmdResolveImage() {
[_mtlRenderPassDescriptor release];
[mtlRendEnc pushDebugGroup: @"vkCmdResolveImage"];
[mtlRendEnc popDebugGroup];
[mtlRendEnc endEncoding];
}
}
template class MVKCmdResolveImage<1>;