vkCmdCopyImage() support copying between compressed and uncompressed formats.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 6ecb7c0..22a63de 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -31,7 +31,8 @@
- Fix pipeline cache lookups.
- Fix race condition between swapchain image destruction and presentation completion callback.
- Set Metal texture usage to allow texture copy via view.
-- `vkCmdCopyImage()` validate that formats are compatible for copying.
+- `vkCmdCopyImage()` support copying between compressed and uncompressed formats
+ and validate that formats are compatible for copying.
- `vkCmdBufferImageCopy()` fix crash when setting bytes per image in non-arrayed images.
- Document that the functions in `vk_mvk_moltenvk.h` cannot be used with objects
retrieved through the *Vulkan SDK Loader and Layers* framework.
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
index 53b29d6..9acfe8a 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
@@ -63,7 +63,10 @@
protected:
void addMetalCopyRegions(const VkImageCopy* pRegion);
+ void addTempBufferCopyRegions(const VkImageCopy* pRegion);
bool canCopyFormats();
+ bool shouldUseTextureView();
+ bool shouldUseTempBuffer();
MVKImage* _srcImage;
VkImageLayout _srcLayout;
@@ -72,7 +75,10 @@
MTLPixelFormat _srcMTLPixFmt;
MTLPixelFormat _dstMTLPixFmt;
std::vector<MVKMetalCopyTextureRegion> _mtlTexCopyRegions;
- MVKCommandUse _commandUse = kMVKCommandUseNone;
+ std::vector<VkBufferImageCopy> _srcTmpBuffImgCopies;
+ std::vector<VkBufferImageCopy> _dstTmpBuffImgCopies;
+ size_t _tmpBuffSize;
+ MVKCommandUse _commandUse;
};
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index 4973d70..1d75469 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -50,28 +50,53 @@
_commandUse = commandUse;
- // Deterine the total number of texture layers being affected
- uint32_t layerCnt = 0;
- for (uint32_t i = 0; i < regionCount; i++) {
- layerCnt += pRegions[i].srcSubresource.layerCount;
- }
+ _mtlTexCopyRegions.clear(); // Clear for reuse
+ _srcTmpBuffImgCopies.clear(); // Clear for reuse
+ _dstTmpBuffImgCopies.clear(); // Clear for reuse
+ _tmpBuffSize = 0;
- // Add image regions
- _mtlTexCopyRegions.clear(); // Clear for reuse
- _mtlTexCopyRegions.reserve(layerCnt);
- for (uint32_t i = 0; i < regionCount; i++) {
- addMetalCopyRegions(&pRegions[i]);
+ if (shouldUseTempBuffer()) {
+ // Convert the image-image copies to image-buffer copies
+ _srcTmpBuffImgCopies.reserve(regionCount);
+ _dstTmpBuffImgCopies.reserve(regionCount);
+ for (uint32_t i = 0; i < regionCount; i++) {
+ addTempBufferCopyRegions(&pRegions[i]);
+ }
+ } else {
+ // Deterine the total number of texture layers being affected and add image regions
+ uint32_t layerCnt = 0;
+ for (uint32_t i = 0; i < regionCount; i++) {
+ layerCnt += pRegions[i].srcSubresource.layerCount;
+ }
+ _mtlTexCopyRegions.reserve(layerCnt);
+ for (uint32_t i = 0; i < regionCount; i++) {
+ addMetalCopyRegions(&pRegions[i]);
+ }
}
// Validate
if ( !canCopyFormats() ) {
- setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying between compressed images of different formats, or between compressed and non-compressed formats."));
+ setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Cannot copy between incompatible formats, such as formats of different pixel sizes."));
}
if ((_srcImage->getMTLTextureType() == MTLTextureType3D) != (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying to or from slices of a 3D texture."));
}
}
+bool MVKCmdCopyImage::canCopyFormats() {
+ return mvkMTLPixelFormatBytesPerBlock(_srcMTLPixFmt) == mvkMTLPixelFormatBytesPerBlock(_srcMTLPixFmt);
+}
+
+bool MVKCmdCopyImage::shouldUseTextureView() {
+ return (_srcMTLPixFmt != _dstMTLPixFmt &&
+ mvkFormatTypeFromMTLPixelFormat(_srcMTLPixFmt) != kMVKFormatCompressed &&
+ mvkFormatTypeFromMTLPixelFormat(_dstMTLPixFmt) != kMVKFormatCompressed);
+}
+
+bool MVKCmdCopyImage::shouldUseTempBuffer() {
+ return (_srcMTLPixFmt != _dstMTLPixFmt && !shouldUseTextureView());
+}
+
// Adds a Metal copy region structure for each layer in the specified copy region.
void MVKCmdCopyImage::addMetalCopyRegions(const VkImageCopy* pRegion) {
@@ -93,35 +118,90 @@
}
}
-bool MVKCmdCopyImage::canCopyFormats() {
- return (_srcMTLPixFmt == _dstMTLPixFmt ||
- (mvkFormatTypeFromMTLPixelFormat(_srcMTLPixFmt) != kMVKFormatCompressed &&
- mvkFormatTypeFromMTLPixelFormat(_dstMTLPixFmt) != kMVKFormatCompressed));
+// Add an image-buffer copy and buffer-image copy for the image-image copy
+void MVKCmdCopyImage::addTempBufferCopyRegions(const VkImageCopy* pRegion) {
+ VkBufferImageCopy buffImgCpy;
+
+ // Add copy from source image to temp buffer
+ buffImgCpy.bufferOffset = _tmpBuffSize;
+ buffImgCpy.bufferRowLength = 0;
+ buffImgCpy.bufferImageHeight = 0;
+ buffImgCpy.imageSubresource = pRegion->srcSubresource;
+ buffImgCpy.imageOffset = pRegion->srcOffset;
+ buffImgCpy.imageExtent = pRegion->extent;
+ _srcTmpBuffImgCopies.push_back(buffImgCpy);
+
+ // Add copy from temp buffer to destination image
+ buffImgCpy.bufferOffset = _tmpBuffSize;
+ buffImgCpy.bufferRowLength = 0;
+ buffImgCpy.bufferImageHeight = 0;
+ buffImgCpy.imageSubresource = pRegion->dstSubresource;
+ buffImgCpy.imageOffset = pRegion->dstOffset;
+ buffImgCpy.imageExtent = pRegion->extent;
+ _dstTmpBuffImgCopies.push_back(buffImgCpy);
+
+ NSUInteger bytesPerRow = mvkMTLPixelFormatBytesPerRow(_srcMTLPixFmt, pRegion->extent.width);
+ NSUInteger bytesPerRegion = mvkMTLPixelFormatBytesPerLayer(_srcMTLPixFmt, bytesPerRow, pRegion->extent.height);
+ _tmpBuffSize += bytesPerRegion;
}
void MVKCmdCopyImage::encode(MVKCommandEncoder* cmdEncoder) {
- id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture();
- id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
- if ( !srcMTLTex || !dstMTLTex ) { return; }
+ id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture();
+ id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
+ if ( !srcMTLTex || !dstMTLTex ) { return; }
- // If the pixel formats don't match, use a texture view on source
- if (_srcMTLPixFmt != _dstMTLPixFmt) {
- srcMTLTex = [[srcMTLTex newTextureViewWithPixelFormat: _dstMTLPixFmt] autorelease];
- }
+ // If the pixel formats are different but mappable, use a texture view on the source texture
+ if (shouldUseTextureView()) {
+ srcMTLTex = [[srcMTLTex newTextureViewWithPixelFormat: _dstMTLPixFmt] autorelease];
+ }
- id<MTLBlitCommandEncoder> mtlBlitEnc = cmdEncoder->getMTLBlitEncoder(_commandUse);
+ id<MTLBlitCommandEncoder> mtlBlitEnc = cmdEncoder->getMTLBlitEncoder(_commandUse);
- for (auto& cpyRgn : _mtlTexCopyRegions) {
- [mtlBlitEnc copyFromTexture: srcMTLTex
- sourceSlice: cpyRgn.srcSlice
- sourceLevel: cpyRgn.srcLevel
- sourceOrigin: cpyRgn.srcOrigin
- sourceSize: cpyRgn.srcSize
- toTexture: dstMTLTex
- destinationSlice: cpyRgn.dstSlice
- destinationLevel: cpyRgn.dstLevel
- destinationOrigin: cpyRgn.dstOrigin];
- }
+ // If copies can be performed using direct texture-texture copying, do so
+ for (auto& cpyRgn : _mtlTexCopyRegions) {
+ [mtlBlitEnc copyFromTexture: srcMTLTex
+ sourceSlice: cpyRgn.srcSlice
+ sourceLevel: cpyRgn.srcLevel
+ sourceOrigin: cpyRgn.srcOrigin
+ sourceSize: cpyRgn.srcSize
+ toTexture: dstMTLTex
+ destinationSlice: cpyRgn.dstSlice
+ destinationLevel: cpyRgn.dstLevel
+ destinationOrigin: cpyRgn.dstOrigin];
+ }
+
+ // 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.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+ MVKBuffer* tempBuff = getCommandEncodingPool()->getTransferMVKBuffer(tempBuffData);
+
+ MVKCmdBufferImageCopy cpyCmd(&getCommandPool()->_cmdBufferImageCopyPool);
+
+ // 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((VkBuffer) tempBuff,
+ (VkImage) _srcImage,
+ _srcLayout,
+ (uint32_t)_srcTmpBuffImgCopies.size(),
+ _srcTmpBuffImgCopies.data(),
+ 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((VkBuffer) tempBuff,
+ (VkImage) _dstImage,
+ _dstLayout,
+ (uint32_t)_dstTmpBuffImgCopies.size(),
+ _dstTmpBuffImgCopies.data(),
+ true);
+ cpyCmd.encode(cmdEncoder);
+ }
}