Merge pull request 'perf(window): hoist invariant per-frame Vulkan info structs to members' (#78) from claude/issue-43 into master

This commit is contained in:
catbot 2026-06-16 17:27:48 +02:00
commit fa35d86021
2 changed files with 68 additions and 47 deletions

View file

@ -430,6 +430,58 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
submitInfo.pSignalSemaphores = &semaphores.renderComplete; submitInfo.pSignalSemaphores = &semaphores.renderComplete;
submitInfo.pNext = VK_NULL_HANDLE; submitInfo.pNext = VK_NULL_HANDLE;
// Per-frame info structs: everything that never varies between frames is
// set here once. Render() only patches the barriers' image/oldLayout.
cmdBufInfo = {};
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
const VkImageSubresourceRange range {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
// Transition into VK_IMAGE_LAYOUT_GENERAL before recording passes. image
// and oldLayout are patched per frame (oldLayout depends on first use).
acquireBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = VK_NULL_HANDLE,
.subresourceRange = range,
};
// Transition back to PRESENT_SRC_KHR after the passes. Only image varies.
presentBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = VK_NULL_HANDLE,
.subresourceRange = range,
};
// pImageIndices/pSwapchains point at members, so they follow value changes
// (currentBuffer each frame, swapChain across recreation) on their own.
// The render-complete wait semaphore is fixed for the window's lifetime.
presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &currentBuffer;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &semaphores.renderComplete;
lastMousePos = {0,0}; lastMousePos = {0,0};
mouseDelta = {0,0}; mouseDelta = {0,0};
currentMousePos = {0,0}; currentMousePos = {0,0};
@ -689,18 +741,8 @@ void Window::Render() {
submitInfo.commandBufferCount = 1; submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VkCommandBufferBeginInfo cmdBufInfo {};
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
Device::CheckVkResult(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo)); Device::CheckVkResult(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
VkImageSubresourceRange range{};
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.baseMipLevel = 0;
range.levelCount = VK_REMAINING_MIP_LEVELS;
range.baseArrayLayer = 0;
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
// On an image's first use after (re)creating the swapchain it is still in // On an image's first use after (re)creating the swapchain it is still in
// VK_IMAGE_LAYOUT_UNDEFINED; every subsequent frame leaves it in // VK_IMAGE_LAYOUT_UNDEFINED; every subsequent frame leaves it in
// PRESENT_SRC_KHR. Transitioning from the wrong oldLayout is a validation // PRESENT_SRC_KHR. Transitioning from the wrong oldLayout is a validation
@ -709,20 +751,11 @@ void Window::Render() {
const bool firstUse = !imageInitialised[currentBuffer]; const bool firstUse = !imageInitialised[currentBuffer];
imageInitialised[currentBuffer] = true; imageInitialised[currentBuffer] = true;
VkImageMemoryBarrier image_memory_barrier { acquireBarrier.oldLayout = firstUse ? VK_IMAGE_LAYOUT_UNDEFINED
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
.srcAccessMask = 0, acquireBarrier.image = images[currentBuffer];
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = firstUse ? VK_IMAGE_LAYOUT_UNDEFINED
: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = images[currentBuffer],
.subresourceRange = range
};
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &acquireBarrier);
// Synthesise key-repeat events before listeners run, so the focused // Synthesise key-repeat events before listeners run, so the focused
// widget's OnTextInput / OnKeyDown sees them in the same frame. // widget's OnTextInput / OnKeyDown sees them in the same frame.
@ -787,35 +820,13 @@ void Window::Render() {
} }
} }
VkImageMemoryBarrier image_memory_barrier2 { presentBarrier.image = images[currentBuffer];
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = images[currentBuffer],
.subresourceRange = range
};
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier2); vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer])); Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &submitInfo, VK_NULL_HANDLE)); Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &submitInfo, VK_NULL_HANDLE));
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &currentBuffer;
// Check if a wait semaphore has been specified to wait for before presenting the image
if (semaphores.renderComplete != VK_NULL_HANDLE)
{
presentInfo.pWaitSemaphores = &semaphores.renderComplete;
presentInfo.waitSemaphoreCount = 1;
}
VkResult result = vkQueuePresentKHR(Device::queue, &presentInfo); VkResult result = vkQueuePresentKHR(Device::queue, &presentInfo);
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) { if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) {

View file

@ -266,6 +266,16 @@ export namespace Crafter {
std::thread thread; std::thread thread;
VkCommandBuffer drawCmdBuffers[numFrames]; VkCommandBuffer drawCmdBuffers[numFrames];
VkSubmitInfo submitInfo; VkSubmitInfo submitInfo;
// Per-frame Vulkan info structs whose contents are almost entirely
// invariant. Initialised once in the constructor; Render() patches only
// the few fields that change each frame (the two barriers' image and
// the acquire barrier's oldLayout). presentInfo.pImageIndices and
// pSwapchains point at the currentBuffer/swapChain members, so they
// track value changes (including swapchain recreation) automatically.
VkCommandBufferBeginInfo cmdBufInfo;
VkImageMemoryBarrier acquireBarrier;
VkImageMemoryBarrier presentBarrier;
VkPresentInfoKHR presentInfo;
Semaphores semaphores; Semaphores semaphores;
std::uint32_t currentBuffer = 0; std::uint32_t currentBuffer = 0;
VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;