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
|
|
@ -721,30 +721,11 @@ void Device::Initialize() {
|
||||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||||
deviceCreateInfo.pNext = &physical_features2;
|
deviceCreateInfo.pNext = &physical_features2;
|
||||||
|
|
||||||
uint32_t deviceLayerCount;
|
// Device layers are deprecated and have been ignored since Vulkan 1.0;
|
||||||
CheckVkResult(vkEnumerateDeviceLayerProperties(physDevice, &deviceLayerCount, NULL));
|
// enabling them is a validation error. Layers are enabled at instance
|
||||||
|
// creation only, so leave enabledLayerCount at 0.
|
||||||
std::vector<VkLayerProperties> deviceLayerProperties(deviceLayerCount);
|
deviceCreateInfo.enabledLayerCount = 0;
|
||||||
CheckVkResult(vkEnumerateDeviceLayerProperties(physDevice, &deviceLayerCount, deviceLayerProperties.data()));
|
deviceCreateInfo.ppEnabledLayerNames = nullptr;
|
||||||
|
|
||||||
size_t foundDeviceLayers = 0;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < deviceLayerCount; i++)
|
|
||||||
{
|
|
||||||
for (size_t j = 0; j < sizeof(layerNames) / sizeof(const char*); j++)
|
|
||||||
{
|
|
||||||
if (std::strcmp(deviceLayerProperties[i].layerName, layerNames[j]) == 0)
|
|
||||||
{
|
|
||||||
foundDeviceLayers++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundDeviceLayers >= sizeof(layerNames) / sizeof(const char*))
|
|
||||||
{
|
|
||||||
deviceCreateInfo.enabledLayerCount = sizeof(layerNames) / sizeof(const char*);
|
|
||||||
deviceCreateInfo.ppEnabledLayerNames = layerNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckVkResult(vkCreateDevice(physDevice, &deviceCreateInfo, NULL, &device));
|
CheckVkResult(vkCreateDevice(physDevice, &deviceCreateInfo, NULL, &device));
|
||||||
vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
|
vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
|
||||||
|
|
|
||||||
|
|
@ -451,56 +451,13 @@ void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::RecreateSwapchainAndImages() {
|
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();
|
||||||
|
|
||||||
// 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) {
|
void Window::SetTitle(const std::string_view title) {
|
||||||
|
|
@ -724,11 +681,20 @@ void Window::Render() {
|
||||||
range.baseArrayLayer = 0;
|
range.baseArrayLayer = 0;
|
||||||
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
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 {
|
VkImageMemoryBarrier image_memory_barrier {
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.srcAccessMask = 0,
|
.srcAccessMask = 0,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
.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,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
|
@ -980,6 +946,11 @@ void Window::CreateSwapchain()
|
||||||
// Get the swap chain images
|
// Get the swap chain images
|
||||||
Device::CheckVkResult(vkGetSwapchainImagesKHR(Device::device, swapChain, &imageCount, 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++) {
|
for (std::uint8_t i = 0; i < numFrames; i++) {
|
||||||
imageViews[i] = {
|
imageViews[i] = {
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
|
|
@ -1009,26 +980,13 @@ VkCommandBuffer Window::StartInit() {
|
||||||
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
Device::CheckVkResult(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
|
Device::CheckVkResult(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
|
||||||
|
|
||||||
VkImageSubresourceRange range{};
|
// The swapchain images are deliberately NOT transitioned here. They are
|
||||||
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
// presentable images and may only be touched after vkAcquireNextImageKHR
|
||||||
range.baseMipLevel = 0;
|
// returns them; transitioning an unacquired presentable image is a
|
||||||
range.levelCount = VK_REMAINING_MIP_LEVELS;
|
// validation error. Render() instead transitions each image from
|
||||||
range.baseArrayLayer = 0;
|
// VK_IMAGE_LAYOUT_UNDEFINED on its first acquired use (see
|
||||||
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
// imageInitialised). This command buffer is for the caller's one-time
|
||||||
|
// setup work (uploads, acceleration-structure builds, etc.).
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return drawCmdBuffers[currentBuffer];
|
return drawCmdBuffers[currentBuffer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,14 @@ export namespace Crafter {
|
||||||
VkColorSpaceKHR colorSpace;
|
VkColorSpaceKHR colorSpace;
|
||||||
VkImage images[numFrames];
|
VkImage images[numFrames];
|
||||||
VkImageViewCreateInfo imageViews[numFrames];
|
VkImageViewCreateInfo imageViews[numFrames];
|
||||||
|
// Tracks whether each swapchain image has been rendered (and thus
|
||||||
|
// left in PRESENT_SRC_KHR) at least once. Freshly created swapchain
|
||||||
|
// images start in VK_IMAGE_LAYOUT_UNDEFINED, so the first per-frame
|
||||||
|
// barrier must transition from UNDEFINED, not PRESENT_SRC_KHR.
|
||||||
|
// Reset in CreateSwapchain(). A presentable image may only be touched
|
||||||
|
// after it has been acquired, so this initial transition happens
|
||||||
|
// lazily in Render() (post-acquire) rather than up front.
|
||||||
|
std::array<bool, numFrames> imageInitialised{};
|
||||||
std::thread thread;
|
std::thread thread;
|
||||||
VkCommandBuffer drawCmdBuffers[numFrames];
|
VkCommandBuffer drawCmdBuffers[numFrames];
|
||||||
VkSubmitInfo submitInfo;
|
VkSubmitInfo submitInfo;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue