diff --git a/src/d3d11/d3d11_context.cpp b/src/d3d11/d3d11_context.cpp index 6b4181306..2cc7756df 100644 --- a/src/d3d11/d3d11_context.cpp +++ b/src/d3d11/d3d11_context.cpp @@ -316,20 +316,31 @@ namespace dxvk { auto dstBuffer = static_cast(pDstResource)->GetBufferSlice(); auto srcBuffer = static_cast(pSrcResource)->GetBufferSlice(); + VkDeviceSize dstOffset = DstX; VkDeviceSize srcOffset = 0; - VkDeviceSize srcLength = srcBuffer.length(); + VkDeviceSize regLength = srcBuffer.length(); + + if (dstOffset >= dstBuffer.length()) + return; if (pSrcBox != nullptr) { - if (pSrcBox->left > pSrcBox->right) + if (pSrcBox->left >= pSrcBox->right) return; // no-op, but legal srcOffset = pSrcBox->left; - srcLength = pSrcBox->right - pSrcBox->left; + regLength = pSrcBox->right - pSrcBox->left; + + if (srcOffset >= srcBuffer.length()) + return; } + // Clamp copy region to prevent out-of-bounds access + regLength = std::min(regLength, srcBuffer.length() - srcOffset); + regLength = std::min(regLength, dstBuffer.length() - dstOffset); + EmitCs([ - cDstSlice = dstBuffer.subSlice(DstX, srcLength), - cSrcSlice = srcBuffer.subSlice(srcOffset, srcLength) + cDstSlice = dstBuffer.subSlice(dstOffset, regLength), + cSrcSlice = srcBuffer.subSlice(srcOffset, regLength) ] (DxvkContext* ctx) { ctx->copyBuffer( cDstSlice.buffer(), @@ -351,13 +362,35 @@ namespace dxvk { const VkImageSubresource dstSubresource = dstTextureInfo->GetSubresourceFromIndex(dstFormatInfo->aspectMask, DstSubresource); const VkImageSubresource srcSubresource = srcTextureInfo->GetSubresourceFromIndex(srcFormatInfo->aspectMask, SrcSubresource); - VkOffset3D srcOffset = { 0, 0, 0 }; - VkOffset3D dstOffset = { - static_cast(DstX), - static_cast(DstY), - static_cast(DstZ) }; + // Copies are only supported on size-compatible formats + if (dstFormatInfo->elementSize != srcFormatInfo->elementSize) { + Logger::err(str::format( + "D3D11: CopySubresourceRegion: Incompatible texel size" + "\n Dst texel size: ", dstFormatInfo->elementSize, + "\n Src texel size: ", srcFormatInfo->elementSize)); + return; + } - VkExtent3D extent = srcImage->mipLevelExtent(srcSubresource.mipLevel); + // Copies are only supported if the sample count matches + if (dstImage->info().sampleCount != srcImage->info().sampleCount) { + Logger::err(str::format( + "D3D11: CopySubresourceRegion: Incompatible sample count", + "\n Dst sample count: ", dstImage->info().sampleCount, + "\n Src sample count: ", srcImage->info().sampleCount)); + return; + } + + VkOffset3D srcOffset = { 0, 0, 0 }; + VkOffset3D dstOffset = { int32_t(DstX), int32_t(DstY), int32_t(DstZ) }; + + VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel); + VkExtent3D dstExtent = dstImage->mipLevelExtent(dstSubresource.mipLevel); + VkExtent3D regExtent = srcExtent; + + if (uint32_t(dstOffset.x) >= dstExtent.width + || uint32_t(dstOffset.y) >= dstExtent.height + || uint32_t(dstOffset.z) >= dstExtent.depth) + return; if (pSrcBox != nullptr) { if (pSrcBox->left >= pSrcBox->right @@ -369,9 +402,14 @@ namespace dxvk { srcOffset.y = pSrcBox->top; srcOffset.z = pSrcBox->front; - extent.width = pSrcBox->right - pSrcBox->left; - extent.height = pSrcBox->bottom - pSrcBox->top; - extent.depth = pSrcBox->back - pSrcBox->front; + regExtent.width = pSrcBox->right - pSrcBox->left; + regExtent.height = pSrcBox->bottom - pSrcBox->top; + regExtent.depth = pSrcBox->back - pSrcBox->front; + + if (uint32_t(srcOffset.x) >= srcExtent.width + || uint32_t(srcOffset.y) >= srcExtent.height + || uint32_t(srcOffset.z) >= srcExtent.depth) + return; } VkImageSubresourceLayers dstLayers = { @@ -387,11 +425,36 @@ namespace dxvk { // Copying multiple slices does not // seem to be supported in D3D11 if (copy2Dto3D || copy3Dto2D) { - extent.depth = 1; + regExtent.depth = 1; dstLayers.layerCount = 1; srcLayers.layerCount = 1; } + // Don't perform the copy if the offsets aren't aligned + if (!util::isBlockAligned(srcOffset, srcFormatInfo->blockSize) + || !util::isBlockAligned(dstOffset, dstFormatInfo->blockSize)) { + Logger::err("D3D11: CopySubresourceRegion: Unaligned block offset"); + return; + } + + // Clamp the image region in order to avoid out-of-bounds access + VkExtent3D regBlockCount = util::computeBlockCount(regExtent, srcFormatInfo->blockSize); + VkExtent3D dstBlockCount = util::computeMaxBlockCount(dstOffset, dstExtent, dstFormatInfo->blockSize); + VkExtent3D srcBlockCount = util::computeMaxBlockCount(srcOffset, srcExtent, srcFormatInfo->blockSize); + + regBlockCount = util::minExtent3D(regBlockCount, dstBlockCount); + regBlockCount = util::minExtent3D(regBlockCount, srcBlockCount); + + regExtent = util::minExtent3D(regExtent, util::computeBlockExtent(regBlockCount, srcFormatInfo->blockSize)); + + // Don't perform the copy if the image extent is not aligned and + // if it does not touch the image border for unaligned dimensons + if (!util::isBlockAligned(srcOffset, regExtent, srcFormatInfo->blockSize, srcExtent) + || !util::isBlockAligned(dstOffset, regExtent, dstFormatInfo->blockSize, dstExtent)) { + Logger::err("D3D11: CopySubresourceRegion: Unaligned block size"); + return; + } + EmitCs([ cDstImage = dstImage, cSrcImage = srcImage, @@ -399,7 +462,7 @@ namespace dxvk { cSrcLayers = srcLayers, cDstOffset = dstOffset, cSrcOffset = srcOffset, - cExtent = extent + cExtent = regExtent ] (DxvkContext* ctx) { ctx->copyImage( cDstImage, cDstLayers, cDstOffset, @@ -470,12 +533,36 @@ namespace dxvk { const DxvkFormatInfo* dstFormatInfo = imageFormatInfo(dstImage->info().format); const DxvkFormatInfo* srcFormatInfo = imageFormatInfo(srcImage->info().format); + + // Copies are only supported on size-compatible formats + if (dstFormatInfo->elementSize != srcFormatInfo->elementSize) { + Logger::err(str::format( + "D3D11: CopyResource: Incompatible texel size" + "\n Dst texel size: ", dstFormatInfo->elementSize, + "\n Src texel size: ", srcFormatInfo->elementSize)); + return; + } + + // Layer count, mip level count, and sample count must match + if (srcImage->info().numLayers != dstImage->info().numLayers + || srcImage->info().mipLevels != dstImage->info().mipLevels + || srcImage->info().sampleCount != dstImage->info().sampleCount) { + Logger::err(str::format( + "D3D11: CopyResource: Incompatible images" + "\n Dst: (", dstImage->info().numLayers, + ",", dstImage->info().mipLevels, + ",", dstImage->info().sampleCount, ")", + "\n Src: (", srcImage->info().numLayers, + ",", srcImage->info().mipLevels, + ",", srcImage->info().sampleCount, ")")); + return; + } for (uint32_t i = 0; i < srcImage->info().mipLevels; i++) { + VkImageSubresourceLayers dstLayers = { dstFormatInfo->aspectMask, i, 0, dstImage->info().numLayers }; + VkImageSubresourceLayers srcLayers = { srcFormatInfo->aspectMask, i, 0, srcImage->info().numLayers }; + VkExtent3D extent = srcImage->mipLevelExtent(i); - - const VkImageSubresourceLayers dstLayers = { dstFormatInfo->aspectMask, i, 0, dstImage->info().numLayers }; - const VkImageSubresourceLayers srcLayers = { srcFormatInfo->aspectMask, i, 0, srcImage->info().numLayers }; EmitCs([ cDstImage = dstImage, diff --git a/src/dxvk/dxvk_util.h b/src/dxvk/dxvk_util.h index 9eac8d494..04b71b56e 100644 --- a/src/dxvk/dxvk_util.h +++ b/src/dxvk/dxvk_util.h @@ -39,20 +39,115 @@ namespace dxvk::util { VkDeviceSize pitchPerRow, VkDeviceSize pitchPerLayer); + /** + * \brief Computes minimum extent + * + * \param [in] a First value + * \param [in] b Second value + * \returns Component-wise \c min + */ + inline VkExtent3D minExtent3D(VkExtent3D a, VkExtent3D b) { + return VkExtent3D { + std::min(a.width, b.width), + std::min(a.height, b.height), + std::min(a.depth, b.depth) }; + } + + /** + * \brief Checks whether an offset is block-aligned + * + * An offset is considered block-aligned if it is + * a multiple of the block size. Only non-negative + * offset values are valid. + * \param [in] offset The offset to check + * \param [in] blockSize Block size + * \returns \c true if \c offset is aligned + */ + inline bool isBlockAligned(VkOffset3D offset, VkExtent3D blockSize) { + return (offset.x % blockSize.width == 0) + && (offset.y % blockSize.height == 0) + && (offset.z % blockSize.depth == 0); + } + + /** + * \brief Checks whether an extent is block-aligned + * + * A block-aligned extent can be used for image copy + * operations that involve block-compressed images. + * \param [in] offset The base offset + * \param [in] extent The extent to check + * \param [in] blockSize Compressed block size + * \param [in] imageSize Image size + * \returns \c true if all components of \c extent + * are aligned or touch the image border. + */ + inline bool isBlockAligned(VkOffset3D offset, VkExtent3D extent, VkExtent3D blockSize, VkExtent3D imageSize) { + return ((extent.width % blockSize.width == 0) || (uint32_t(offset.x + extent.width) == imageSize.width)) + && ((extent.height % blockSize.height == 0) || (uint32_t(offset.y + extent.height) == imageSize.height)) + && ((extent.depth % blockSize.depth == 0) || (uint32_t(offset.z + extent.depth) == imageSize.depth)); + } + + /** + * \brief Computes block offset for compressed images + * + * Convenience function to compute the block position + * within a compressed image based on the block size. + * \param [in] offset The offset + * \param [in] blockSize Size of a pixel block + * \returns The block offset + */ + inline VkOffset3D computeBlockOffset(VkOffset3D offset, VkExtent3D blockSize) { + return VkOffset3D { + offset.x / int32_t(blockSize.width), + offset.y / int32_t(blockSize.height), + offset.z / int32_t(blockSize.depth) }; + } + /** * \brief Computes block count for compressed images * * Convenience function to compute the size, in * blocks, of compressed images subresources. - * \param [in] imageSize The image size - * \param [in] blockSize Size per pixel block + * \param [in] extent The image size + * \param [in] blockSize Size of a pixel block * \returns Number of blocks in the image */ - inline VkExtent3D computeBlockCount(VkExtent3D imageSize, VkExtent3D blockSize) { + inline VkExtent3D computeBlockCount(VkExtent3D extent, VkExtent3D blockSize) { return VkExtent3D { - (imageSize.width + blockSize.width - 1) / blockSize.width, - (imageSize.height + blockSize.height - 1) / blockSize.height, - (imageSize.depth + blockSize.depth - 1) / blockSize.depth }; + (extent.width + blockSize.width - 1) / blockSize.width, + (extent.height + blockSize.height - 1) / blockSize.height, + (extent.depth + blockSize.depth - 1) / blockSize.depth }; + } + + /** + * \brief Computes block count for compressed images + * + * Given an aligned offset, this computes + * Convenience function to compute the size, in + * blocks, of compressed images subresources. + * \param [in] extent The image size + * \param [in] blockSize Size of a pixel block + * \returns Number of blocks in the image + */ + inline VkExtent3D computeMaxBlockCount(VkOffset3D offset, VkExtent3D extent, VkExtent3D blockSize) { + return VkExtent3D { + (extent.width + blockSize.width - offset.x - 1) / blockSize.width, + (extent.height + blockSize.height - offset.y - 1) / blockSize.height, + (extent.depth + blockSize.depth - offset.z - 1) / blockSize.depth }; + } + + /** + * \brief Computes block extent for compressed images + * + * \param [in] blockCount The number of blocks + * \param [in] blockSize Size of a pixel block + * \returns Extent of the given blocks + */ + inline VkExtent3D computeBlockExtent(VkExtent3D blockCount, VkExtent3D blockSize) { + return VkExtent3D { + blockCount.width * blockSize.width, + blockCount.height * blockSize.height, + blockCount.depth * blockSize.depth }; } /**