fix(vulkan): clear startup validation errors on native triangle
Two Vulkan validation errors fired on startup of every native (Vulkan) example, reported in #5: 1. vkCreateDevice enabledLayerCount != 0. Device layers are deprecated and ignored since Vulkan 1.0; passing them is a spec violation (VUID-VkDeviceCreateInfo-enabledLayerCount-12384). The device-layer enumeration/match block in Device::Initialize is removed and enabledLayerCount is pinned to 0 — layers are enabled at the instance only. 2. vkQueueSubmit layout transition on a presentable image that "has not been acquired". StartInit() and RecreateSwapchainAndImages() eagerly transitioned every swapchain image UNDEFINED -> PRESENT_SRC_KHR before any vkAcquireNextImageKHR, which the spec forbids (a presentable image may only be touched after acquire). Those pre-transitions are removed. Each image's first layout transition now happens lazily in Render(), after acquire, from UNDEFINED; subsequent frames transition from PRESENT_SRC_KHR. A per-image `imageInitialised` flag (reset in CreateSwapchain) selects the correct oldLayout. Verified under sway (headless, GPU renderer) + VK_LAYER_KHRONOS_validation: the original code reproduces both errors on HelloUI; the fixed build emits zero validation messages across initial render and swapchain recreation. Resolves #5 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6a54c3c4ca
commit
cac433ee09
3 changed files with 41 additions and 94 deletions
|
|
@ -451,56 +451,13 @@ void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) {
|
|||
}
|
||||
|
||||
void Window::RecreateSwapchainAndImages() {
|
||||
// CreateSwapchain leaves the new swapchain images in
|
||||
// VK_IMAGE_LAYOUT_UNDEFINED and resets imageInitialised. The images are
|
||||
// NOT transitioned here: a presentable image may only be touched after
|
||||
// vkAcquireNextImageKHR returns it, so Render() performs each image's
|
||||
// first layout transition lazily (from UNDEFINED) once it has been
|
||||
// acquired. Pre-transitioning unacquired images is a validation error.
|
||||
CreateSwapchain();
|
||||
|
||||
// CreateSwapchain leaves new swapchain images in VK_IMAGE_LAYOUT_UNDEFINED.
|
||||
// Render() barriers from PRESENT_SRC_KHR, so transition them now to
|
||||
// match. Mirrors the StartInit logic, on a one-shot command buffer.
|
||||
{
|
||||
VkCommandBufferAllocateInfo cba{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = Device::commandPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
Device::CheckVkResult(vkAllocateCommandBuffers(Device::device, &cba, &cmd));
|
||||
|
||||
VkCommandBufferBeginInfo cbi{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
Device::CheckVkResult(vkBeginCommandBuffer(cmd, &cbi));
|
||||
|
||||
VkImageSubresourceRange range{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
};
|
||||
for (std::uint32_t i = 0; i < numFrames; i++) {
|
||||
image_layout_transition(cmd,
|
||||
images[i],
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0, 0,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
range);
|
||||
}
|
||||
|
||||
Device::CheckVkResult(vkEndCommandBuffer(cmd));
|
||||
|
||||
VkSubmitInfo si{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
};
|
||||
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &si, VK_NULL_HANDLE));
|
||||
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||
vkFreeCommandBuffers(Device::device, Device::commandPool, 1, &cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::SetTitle(const std::string_view title) {
|
||||
|
|
@ -724,11 +681,20 @@ void Window::Render() {
|
|||
range.baseArrayLayer = 0;
|
||||
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
||||
|
||||
// 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
|
||||
// PRESENT_SRC_KHR. Transitioning from the wrong oldLayout is a validation
|
||||
// error, so pick based on whether this image has been rendered before.
|
||||
// The image was just acquired above, so touching it here is legal.
|
||||
const bool firstUse = !imageInitialised[currentBuffer];
|
||||
imageInitialised[currentBuffer] = true;
|
||||
|
||||
VkImageMemoryBarrier image_memory_barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.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,
|
||||
|
|
@ -980,6 +946,11 @@ void Window::CreateSwapchain()
|
|||
// Get the swap chain images
|
||||
Device::CheckVkResult(vkGetSwapchainImagesKHR(Device::device, swapChain, &imageCount, images));
|
||||
|
||||
// Brand-new swapchain images are in VK_IMAGE_LAYOUT_UNDEFINED; none have
|
||||
// been rendered/presented yet. Render() consults this to pick the correct
|
||||
// oldLayout for each image's first post-acquire transition.
|
||||
imageInitialised.fill(false);
|
||||
|
||||
for (std::uint8_t i = 0; i < numFrames; i++) {
|
||||
imageViews[i] = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
|
|
@ -1009,26 +980,13 @@ VkCommandBuffer Window::StartInit() {
|
|||
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
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;
|
||||
|
||||
for(std::uint32_t i = 0; i < numFrames; i++) {
|
||||
image_layout_transition(drawCmdBuffers[currentBuffer],
|
||||
images[i],
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0,
|
||||
0,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
range
|
||||
);
|
||||
}
|
||||
|
||||
// The swapchain images are deliberately NOT transitioned here. They are
|
||||
// presentable images and may only be touched after vkAcquireNextImageKHR
|
||||
// returns them; transitioning an unacquired presentable image is a
|
||||
// validation error. Render() instead transitions each image from
|
||||
// VK_IMAGE_LAYOUT_UNDEFINED on its first acquired use (see
|
||||
// imageInitialised). This command buffer is for the caller's one-time
|
||||
// setup work (uploads, acceleration-structure builds, etc.).
|
||||
return drawCmdBuffers[currentBuffer];
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue