perf(image): batch final mip-chain layout transition (#70)
The mip-generation upload path issued N+1 layout-transition barriers per chain. The final blit's destination level is never read again, so its dedicated TRANSFER_DST -> TRANSFER_SRC barrier was redundant: the level was transitioned to SRC only to keep the final SRC -> consumer transition uniform. Skip that last per-level barrier and fold the final level into the closing transition, which now batches two VkImageMemoryBarrier entries ([0, mipLevels-1) from TRANSFER_SRC, the last level from TRANSFER_DST) into a single vkCmdPipelineBarrier. One fewer barrier call per chain; the N-1 interleaved per-level barriers stay one-at-a-time since each is read by the next blit. Applied to both the host-upload and GPU-decompress Update paths. Adds the MipChainBarrierBatch regression test driving the pure barrier builder (no GPU device needed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b7500e1fd0
commit
518509aa0e
3 changed files with 202 additions and 4 deletions
|
|
@ -31,6 +31,41 @@ import :Decompress;
|
|||
import :VulkanBuffer;
|
||||
|
||||
export namespace Crafter {
|
||||
// Builds the batched final layout transition for a freshly-generated mip
|
||||
// chain (mipLevels >= 2). After the blit loop, levels [0, mipLevels-1) sit
|
||||
// in TRANSFER_SRC_OPTIMAL — each was read as a blit source — while the last
|
||||
// level is still in TRANSFER_DST_OPTIMAL: it was written by the final blit
|
||||
// and is never read, so it never needed a DST->SRC barrier of its own.
|
||||
// Both groups move to the consumer `layout` in one vkCmdPipelineBarrier
|
||||
// (two VkImageMemoryBarrier entries sharing src=TRANSFER / dst=consumer
|
||||
// stage), shaving one barrier call off the previous N+1-per-chain count.
|
||||
// The interleaved per-level barriers stay one-at-a-time — each is mandated
|
||||
// because the next blit reads the level it transitions. Writes the entries
|
||||
// into `out` and returns the count (always 2).
|
||||
std::uint32_t BuildMipChainFinalBarriers(VkImage image, VkImageLayout layout, std::uint32_t mipLevels, std::array<VkImageMemoryBarrier, 2>& out) {
|
||||
auto fill = [&](VkImageMemoryBarrier& b, VkImageLayout oldLayout, std::uint32_t baseMip, std::uint32_t count, VkAccessFlags srcAccess) {
|
||||
b = {};
|
||||
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
b.oldLayout = oldLayout;
|
||||
b.newLayout = layout;
|
||||
b.image = image;
|
||||
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
b.subresourceRange.baseMipLevel = baseMip;
|
||||
b.subresourceRange.levelCount = count;
|
||||
b.subresourceRange.baseArrayLayer = 0;
|
||||
b.subresourceRange.layerCount = 1;
|
||||
b.srcAccessMask = srcAccess;
|
||||
b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
};
|
||||
// Levels [0, mipLevels-1): blit sources, currently TRANSFER_SRC.
|
||||
fill(out[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, mipLevels - 1u, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
// Final level: the last blit's destination, still TRANSFER_DST.
|
||||
fill(out[1], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels - 1u, 1u, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename PixelType>
|
||||
class ImageVulkan {
|
||||
public:
|
||||
|
|
@ -160,10 +195,17 @@ export namespace Crafter {
|
|||
blit.dstOffsets[1] = { (int32_t)mipWidth, (int32_t)mipHeight, 1 };
|
||||
|
||||
vkCmdBlitImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1);
|
||||
// The final blit's destination is never read again, so it
|
||||
// skips the DST->SRC barrier and is taken straight to the
|
||||
// consumer layout by the batched final transition below.
|
||||
if (i + 1 < mipLevels) {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
std::array<VkImageMemoryBarrier, 2> finalBarriers;
|
||||
std::uint32_t finalCount = BuildMipChainFinalBarriers(image, layout, mipLevels, finalBarriers);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, 0, 0, nullptr, 0, nullptr, finalCount, finalBarriers.data());
|
||||
} else {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
}
|
||||
|
|
@ -297,9 +339,16 @@ export namespace Crafter {
|
|||
blit.dstOffsets[0] = { 0, 0, 0 };
|
||||
blit.dstOffsets[1] = { (int32_t)mipWidth, (int32_t)mipHeight, 1 };
|
||||
vkCmdBlitImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1);
|
||||
// The final blit's destination is never read again, so it
|
||||
// skips the DST->SRC barrier and is taken straight to the
|
||||
// consumer layout by the batched final transition below.
|
||||
if (i + 1 < mipLevels) {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1);
|
||||
}
|
||||
}
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
std::array<VkImageMemoryBarrier, 2> finalBarriers;
|
||||
std::uint32_t finalCount = BuildMipChainFinalBarriers(image, layout, mipLevels, finalBarriers);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, 0, 0, nullptr, 0, nullptr, finalCount, finalBarriers.data());
|
||||
} else {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, consumerStage, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue