diff --git a/Common/GPU/Vulkan/VulkanImage.cpp b/Common/GPU/Vulkan/VulkanImage.cpp index 8efcb69834..bbdc87a3e4 100644 --- a/Common/GPU/Vulkan/VulkanImage.cpp +++ b/Common/GPU/Vulkan/VulkanImage.cpp @@ -142,8 +142,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i return true; } -// TODO: Batch these. -void VulkanTexture::UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) { +void VulkanTexture::CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) { VkBufferImageCopy copy_region{}; copy_region.bufferOffset = offset; copy_region.bufferRowLength = (uint32_t)rowLength; @@ -157,7 +156,21 @@ void VulkanTexture::UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mi copy_region.imageSubresource.baseArrayLayer = 0; copy_region.imageSubresource.layerCount = 1; - vkCmdCopyBufferToImage(cmd, buffer, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + if (!copyBatch->buffer) { + copyBatch->buffer = buffer; + } else if (copyBatch->buffer != buffer) { + // Need to flush the batch if this image isn't from the same buffer as the previous ones. + FinishCopyBatch(cmd, copyBatch); + copyBatch->buffer = buffer; + } + copyBatch->copies.push_back(copy_region); +} + +void VulkanTexture::FinishCopyBatch(VkCommandBuffer cmd, TextureCopyBatch *copyBatch) { + if (!copyBatch->copies.empty()) { + vkCmdCopyBufferToImage(cmd, copyBatch->buffer, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)copyBatch->copies.size(), copyBatch->copies.data()); + copyBatch->copies.clear(); + } } void VulkanTexture::ClearMip(VkCommandBuffer cmd, int mip, uint32_t value) { diff --git a/Common/GPU/Vulkan/VulkanImage.h b/Common/GPU/Vulkan/VulkanImage.h index c088fdca5b..580e663713 100644 --- a/Common/GPU/Vulkan/VulkanImage.h +++ b/Common/GPU/Vulkan/VulkanImage.h @@ -7,6 +7,13 @@ class VulkanDeviceAllocator; VK_DEFINE_HANDLE(VmaAllocation); +struct TextureCopyBatch { + std::vector copies; + VkBuffer buffer = VK_NULL_HANDLE; + void reserve(size_t mips) { copies.reserve(mips); } + bool empty() const { return copies.empty(); } +}; + // Wrapper around what you need to use a texture. // ALWAYS use an allocator when calling CreateDirect. class VulkanTexture { @@ -23,7 +30,9 @@ public: void ClearMip(VkCommandBuffer cmd, int mip, uint32_t value); // Can also be used to copy individual levels of a 3D texture. - void UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels + // If possible, will just add to the batch instead of submitting a copy. + void CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels + void FinishCopyBatch(VkCommandBuffer cmd, TextureCopyBatch *copyBatch); void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute); void EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index ce76c90af2..008fcd0159 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -682,7 +682,9 @@ VulkanTexture *VKContext::GetNullTexture() { data[y*w + x] = 0; // black } } - nullTexture_->UploadMip(cmdInit, 0, w, h, 0, bindBuf, bindOffset, w); + TextureCopyBatch batch; + nullTexture_->CopyBufferToMipLevel(cmdInit, &batch, 0, w, h, 0, bindBuf, bindOffset, w); + nullTexture_->FinishCopyBatch(cmdInit, &batch); nullTexture_->EndCreate(cmdInit, false, VK_PIPELINE_STAGE_TRANSFER_BIT); } return nullTexture_; @@ -784,7 +786,9 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur } else { offset = push->PushAligned((const void *)desc.initData[i], size, 16, &buf); } - vkTex_->UploadMip(cmd, i, w, h, 0, buf, offset, w); + TextureCopyBatch batch; + vkTex_->CopyBufferToMipLevel(cmd, &batch, i, w, h, 0, buf, offset, w); + vkTex_->FinishCopyBatch(cmd, &batch); w = (w + 1) / 2; h = (h + 1) / 2; d = (d + 1) / 2; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index e18fd7c5d5..20db0ddbe5 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -545,6 +545,10 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { VulkanPushBuffer *pushBuffer = drawEngine_->GetPushBufferForTextureData(); + // Batch the copies. + TextureCopyBatch copyBatch; + copyBatch.reserve(levels); + for (int i = 0; i < levels; i++) { int mipUnscaledWidth = gstate.getTextureWidth(i); int mipUnscaledHeight = gstate.getTextureHeight(i); @@ -595,15 +599,12 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // TODO: Fill with some pattern? } replacementTimeThisFrame_ += time_now_d() - replaceStart; - VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, - "Copy Upload (replaced): %dx%d", mipWidth, mipHeight); - entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, rowLength); - VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); + entry->vkTex->CopyBufferToMipLevel(cmdInit, ©Batch, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, rowLength); } else { if (plan.depth != 1) { // 3D texturing. loadLevel(uploadSize, i, byteStride, plan.scaleFactor); - entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, pixelStride); + entry->vkTex->CopyBufferToMipLevel(cmdInit, ©Batch, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, pixelStride); } else if (computeUpload) { int srcBpp = VkFormatBytesPerPixel(dstFmt); int srcStride = mipUnscaledWidth * srcBpp; @@ -625,10 +626,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { vulkan->Delete().QueueDeleteImageView(view); } else { loadLevel(uploadSize, i == 0 ? plan.baseLevelSrc : i, byteStride, plan.scaleFactor); - VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, - "Copy Upload: %dx%d", mipWidth, mipHeight); - entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, pixelStride); - VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); + entry->vkTex->CopyBufferToMipLevel(cmdInit, ©Batch, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, pixelStride); } // Format might be wrong in lowMemoryMode_, so don't save. if (plan.saveTexture && !lowMemoryMode_) { @@ -650,6 +648,13 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { } } + if (!copyBatch.empty()) { + VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, "Copy Upload"); + // Submit the whole batch of mip uploads. + entry->vkTex->FinishCopyBatch(cmdInit, ©Batch); + VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); + } + VkImageLayout layout = computeUpload ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; VkPipelineStageFlags prevStage = computeUpload ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT;