blob: 92efd13ea691b77dc057c585e382f9611a1b02e9 [file] [log] [blame]
/*
* Copyright (c) 2024-2025 Valve Corporation
* Copyright (c) 2024-2025 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include "../framework/layer_validation_tests.h"
#include "../framework/pipeline_helper.h"
#include "../framework/descriptor_helper.h"
#include "../framework/render_pass_helper.h"
class PositiveImageLayout : public ImageTest {};
TEST_F(PositiveImageLayout, BarriersAndImageUsage) {
TEST_DESCRIPTION("Ensure barriers' new and old VkImageLayout are compatible with their images' VkImageUsageFlags");
RETURN_IF_SKIP(Init());
auto depth_format = FindSupportedDepthStencilFormat(Gpu());
InitRenderTarget();
VkImageMemoryBarrier img_barrier = vku::InitStructHelper();
img_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
img_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
img_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
img_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
img_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
img_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
{
vkt::Image img_color(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
vkt::Image img_ds1(*m_device, 128, 128, depth_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkt::Image img_ds2(*m_device, 128, 128, depth_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkt::Image img_xfer_src(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
vkt::Image img_xfer_dst(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT);
vkt::Image img_sampled(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
vkt::Image img_input(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT);
const struct {
vkt::Image &image_obj;
VkImageLayout old_layout;
VkImageLayout new_layout;
} buffer_layouts[] = {
// clang-format off
{img_color, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_ds1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_ds2, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_sampled, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_input, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_xfer_src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
{img_xfer_dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL},
// clang-format on
};
const uint32_t layout_count = sizeof(buffer_layouts) / sizeof(buffer_layouts[0]);
m_command_buffer.Begin();
for (uint32_t i = 0; i < layout_count; ++i) {
img_barrier.image = buffer_layouts[i].image_obj;
const VkImageUsageFlags usage = buffer_layouts[i].image_obj.Usage();
img_barrier.subresourceRange.aspectMask = (usage == VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)
: VK_IMAGE_ASPECT_COLOR_BIT;
img_barrier.oldLayout = buffer_layouts[i].old_layout;
img_barrier.newLayout = buffer_layouts[i].new_layout;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr,
0, nullptr, 1, &img_barrier);
img_barrier.oldLayout = buffer_layouts[i].new_layout;
img_barrier.newLayout = buffer_layouts[i].old_layout;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr,
0, nullptr, 1, &img_barrier);
}
m_command_buffer.End();
img_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
}
}
TEST_F(PositiveImageLayout, ImagelessTracking) {
TEST_DESCRIPTION("Test layout tracking on imageless framebuffers");
AddSurfaceExtension();
AddRequiredExtensions(VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME);
SetTargetApiVersion(VK_API_VERSION_1_2);
RETURN_IF_SKIP(InitFramework());
VkPhysicalDeviceImagelessFramebufferFeaturesKHR physicalDeviceImagelessFramebufferFeatures = vku::InitStructHelper();
physicalDeviceImagelessFramebufferFeatures.imagelessFramebuffer = VK_TRUE;
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2 = vku::InitStructHelper(&physicalDeviceImagelessFramebufferFeatures);
uint32_t physical_device_group_count = 0;
vk::EnumeratePhysicalDeviceGroups(instance(), &physical_device_group_count, nullptr);
if (physical_device_group_count == 0) {
GTEST_SKIP() << "physical_device_group_count is 0";
}
std::vector<VkPhysicalDeviceGroupProperties> physical_device_group(physical_device_group_count,
{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES});
vk::EnumeratePhysicalDeviceGroups(instance(), &physical_device_group_count, physical_device_group.data());
VkDeviceGroupDeviceCreateInfo create_device_pnext = vku::InitStructHelper();
create_device_pnext.physicalDeviceCount = physical_device_group[0].physicalDeviceCount;
create_device_pnext.pPhysicalDevices = physical_device_group[0].physicalDevices;
create_device_pnext.pNext = &physicalDeviceFeatures2;
RETURN_IF_SKIP(InitState(nullptr, &create_device_pnext));
RETURN_IF_SKIP(InitSwapchain(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT));
uint32_t attachmentWidth = m_surface_capabilities.minImageExtent.width;
uint32_t attachmentHeight = m_surface_capabilities.minImageExtent.height;
VkFormat attachmentFormat = m_surface_formats[0].format;
RenderPassSingleSubpass rp(*this);
rp.AddAttachmentDescription(attachmentFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
rp.AddAttachmentReference({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
rp.AddColorAttachment(0);
rp.CreateRenderPass();
// Create an image to use in an imageless framebuffer. Bind swapchain memory to it.
VkImageSwapchainCreateInfoKHR image_swapchain_create_info = vku::InitStructHelper();
image_swapchain_create_info.swapchain = m_swapchain;
auto image_ci = vkt::Image::ImageCreateInfo2D(attachmentWidth, attachmentHeight, 1, 1, attachmentFormat,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
image_ci.pNext = &image_swapchain_create_info;
vkt::Image image(*m_device, image_ci, vkt::no_mem);
VkBindImageMemoryDeviceGroupInfo bind_devicegroup_info = vku::InitStructHelper();
bind_devicegroup_info.deviceIndexCount = physical_device_group[0].physicalDeviceCount;
std::array<uint32_t, 8> deviceIndices = {{0}};
bind_devicegroup_info.pDeviceIndices = deviceIndices.data();
bind_devicegroup_info.splitInstanceBindRegionCount = 0;
bind_devicegroup_info.pSplitInstanceBindRegions = nullptr;
VkBindImageMemorySwapchainInfoKHR bind_swapchain_info = vku::InitStructHelper(&bind_devicegroup_info);
bind_swapchain_info.swapchain = m_swapchain;
bind_swapchain_info.imageIndex = 0;
VkBindImageMemoryInfo bind_info = vku::InitStructHelper(&bind_swapchain_info);
bind_info.image = image;
bind_info.memory = VK_NULL_HANDLE;
bind_info.memoryOffset = 0;
vk::BindImageMemory2(device(), 1, &bind_info);
const std::vector<VkImage> swapchain_images = m_swapchain.GetImages();
vkt::Semaphore image_acquired(*m_device);
const uint32_t current_buffer = m_swapchain.AcquireNextImage(image_acquired, kWaitTimeout);
vkt::ImageView imageView = image.CreateView();
VkFramebufferAttachmentImageInfo framebufferAttachmentImageInfo = {VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO_KHR,
nullptr,
0,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
attachmentWidth,
attachmentHeight,
1,
1,
&attachmentFormat};
VkFramebufferAttachmentsCreateInfo framebufferAttachmentsCreateInfo = vku::InitStructHelper();
framebufferAttachmentsCreateInfo.attachmentImageInfoCount = 1;
framebufferAttachmentsCreateInfo.pAttachmentImageInfos = &framebufferAttachmentImageInfo;
VkFramebufferCreateInfo framebufferCreateInfo = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
&framebufferAttachmentsCreateInfo,
VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT,
rp,
1,
reinterpret_cast<const VkImageView *>(1),
attachmentWidth,
attachmentHeight,
1};
vkt::Framebuffer framebuffer(*m_device, framebufferCreateInfo);
VkRenderPassAttachmentBeginInfo renderPassAttachmentBeginInfo = {VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO_KHR,
nullptr, 1, &imageView.handle()};
VkRenderPassBeginInfo renderPassBeginInfo =
vku::InitStruct<VkRenderPassBeginInfo>(&renderPassAttachmentBeginInfo, rp.Handle(), framebuffer.handle(),
VkRect2D{{0, 0}, {attachmentWidth, attachmentHeight}}, 0u, nullptr);
// RenderPass should change the image layout of both the swapchain image and the aliased image to PRESENT_SRC_KHR
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(renderPassBeginInfo);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
m_default_queue->Present(m_swapchain, current_buffer, image_acquired);
m_default_queue->Wait();
}
TEST_F(PositiveImageLayout, Subresource) {
RETURN_IF_SKIP(Init());
auto image_ci = vkt::Image::ImageCreateInfo2D(64, 64, 7, 6, VK_FORMAT_R8_UINT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
vkt::Image image(*m_device, image_ci);
m_command_buffer.Begin();
const VkImageSubresourceRange subresource_range = image.SubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
auto barrier = image.ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, subresource_range);
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.subresourceRange.baseMipLevel = 1;
barrier.subresourceRange.levelCount = 1;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.subresourceRange.baseMipLevel = 2;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, DescriptorSubresource) {
AddRequiredExtensions(VK_KHR_MAINTENANCE_2_EXTENSION_NAME);
RETURN_IF_SKIP(Init());
InitRenderTarget();
OneOffDescriptorSet descriptor_set(m_device,
{
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr},
});
const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
// Create image, view, and sampler
const VkFormat format = VK_FORMAT_B8G8R8A8_UNORM;
auto usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
auto image_ci = vkt::Image::ImageCreateInfo2D(128, 128, 1, 5, format, usage);
vkt::Image image(*m_device, image_ci, vkt::set_layout);
VkImageSubresourceRange view_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 3, 1};
VkImageSubresourceRange first_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkImageSubresourceRange full_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 5};
VkImageViewCreateInfo image_view_create_info = vku::InitStructHelper();
image_view_create_info.image = image;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = format;
image_view_create_info.subresourceRange = view_range;
vkt::ImageView view(*m_device, image_view_create_info);
vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo());
descriptor_set.WriteDescriptorImageInfo(0, view, sampler);
descriptor_set.UpdateDescriptorSets();
// Create PSO to be used for draw-time errors below
VkShaderObj fs(*m_device, kFragmentSamplerGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_[1] = fs.GetStageCreateInfo();
pipe.gp_ci_.layout = pipeline_layout;
pipe.CreateGraphicsPipeline();
vkt::CommandBuffer cmd_buf(*m_device, m_command_pool);
enum TestType {
kInternal, // Image layout mismatch is *within* a given command buffer
kExternal // Image layout mismatch is with the current state of the image, found at QueueSubmit
};
std::array<TestType, 2> test_list = {{kInternal, kExternal}};
for (TestType test_type : test_list) {
auto init_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
VkImageMemoryBarrier image_barrier = vku::InitStructHelper();
cmd_buf.Begin();
image_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_barrier.image = image;
image_barrier.subresourceRange = full_range;
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_barrier.newLayout = init_layout;
vk::CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &image_barrier);
image_barrier.subresourceRange = first_range;
image_barrier.oldLayout = init_layout;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vk::CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &image_barrier);
image_barrier.subresourceRange = view_range;
image_barrier.oldLayout = init_layout;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vk::CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &image_barrier);
if (test_type == kExternal) {
// The image layout is external to the command buffer we are recording to test. Submit to push to instance scope.
cmd_buf.End();
m_default_queue->SubmitAndWait(cmd_buf);
cmd_buf.Begin();
}
cmd_buf.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
nullptr);
vk::CmdDraw(cmd_buf, 1, 0, 0, 0);
cmd_buf.EndRenderPass();
cmd_buf.End();
// Submit cmd buffer
m_default_queue->SubmitAndWait(cmd_buf);
}
}
TEST_F(PositiveImageLayout, Descriptor3D2DSubresource) {
TEST_DESCRIPTION("Verify renderpass layout transitions for a 2d ImageView created from a 3d Image.");
SetTargetApiVersion(VK_API_VERSION_1_1);
RETURN_IF_SKIP(Init());
InitRenderTarget();
OneOffDescriptorSet descriptor_set(m_device,
{
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr},
});
const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
const VkFormat format = VK_FORMAT_B8G8R8A8_UNORM;
auto usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
VkImageCreateInfo image_ci_3d = vku::InitStructHelper();
image_ci_3d.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
image_ci_3d.imageType = VK_IMAGE_TYPE_3D;
image_ci_3d.format = format;
image_ci_3d.extent = {128, 128, 8};
image_ci_3d.mipLevels = 1;
image_ci_3d.arrayLayers = 1;
image_ci_3d.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci_3d.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci_3d.usage = usage;
vkt::Image image_3d(*m_device, image_ci_3d, vkt::set_layout);
vkt::Image other_image(*m_device, 128, 128, format, usage);
// The image view is a 2D slice of the 3D image at depth = 4, which we request by
// asking for arrayLayer = 4
VkImageSubresourceRange view_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 4, 1};
// But, the spec says:
// Automatic layout transitions apply to the entire image subresource attached
// to the framebuffer. If the attachment view is a 2D or 2D array view of a
// 3D image, even if the attachment view only refers to a subset of the slices
// of the selected mip level of the 3D image, automatic layout transitions apply
// to the entire subresource referenced which is the entire mip level in this case.
VkImageSubresourceRange full_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkImageViewCreateInfo image_view_create_info = vku::InitStructHelper();
image_view_create_info.image = image_3d;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = format;
image_view_create_info.subresourceRange = view_range;
vkt::ImageView view_2d(*m_device, image_view_create_info);
image_view_create_info.image = other_image;
image_view_create_info.subresourceRange = full_range;
vkt::ImageView other_view(*m_device, image_view_create_info);
std::vector<VkAttachmentDescription> attachments = {
{0, format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
};
std::vector<VkAttachmentReference> color = {
{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
};
VkSubpassDescription subpass = {
0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, (uint32_t)color.size(), color.data(), nullptr, nullptr, 0, nullptr};
std::vector<VkSubpassDependency> deps = {
{VK_SUBPASS_EXTERNAL, 0,
(VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT),
(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
(VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT),
(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT), 0},
{0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT), VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
(VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_MEMORY_READ_BIT), 0},
};
VkRenderPassCreateInfo rpci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
nullptr,
0,
(uint32_t)attachments.size(),
attachments.data(),
1,
&subpass,
(uint32_t)deps.size(),
deps.data()};
vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo());
descriptor_set.WriteDescriptorImageInfo(0, other_view, sampler);
descriptor_set.UpdateDescriptorSets();
vkt::RenderPass rp(*m_device, rpci);
// Create PSO to be used for draw-time errors below
VkShaderObj fs(*m_device, kFragmentSamplerGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_[1] = fs.GetStageCreateInfo();
pipe.gp_ci_.layout = pipeline_layout;
pipe.gp_ci_.renderPass = rp;
pipe.CreateGraphicsPipeline();
vkt::CommandBuffer cmd_buf(*m_device, m_command_pool);
enum TestType {
kInternal, // Image layout mismatch is *within* a given command buffer
kExternal // Image layout mismatch is with the current state of the image, found at QueueSubmit
};
std::array<TestType, 2> test_list = {{kInternal, kExternal}};
for (TestType test_type : test_list) {
VkImageMemoryBarrier image_barrier = vku::InitStructHelper();
vkt::Framebuffer fb(*m_device, rp, 1, &view_2d.handle(), 128, 128);
cmd_buf.Begin();
image_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_barrier.image = image_3d;
image_barrier.subresourceRange = full_range;
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vk::CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &image_barrier);
image_barrier.image = other_image;
vk::CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &image_barrier);
if (test_type == kExternal) {
// The image layout is external to the command buffer we are recording to test. Submit to push to instance scope.
cmd_buf.End();
m_default_queue->SubmitAndWait(cmd_buf);
cmd_buf.Begin();
}
m_renderPassBeginInfo.renderPass = rp;
m_renderPassBeginInfo.framebuffer = fb;
m_renderPassBeginInfo.renderArea = {{0, 0}, {128, 128}};
cmd_buf.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
nullptr);
vk::CmdDraw(cmd_buf, 1, 0, 0, 0);
cmd_buf.EndRenderPass();
cmd_buf.End();
m_default_queue->SubmitAndWait(cmd_buf);
}
}
TEST_F(PositiveImageLayout, ArrayLayers) {
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/1998");
RETURN_IF_SKIP(Init());
RETURN_IF_SKIP(InitRenderTarget());
auto image_ci = vkt::Image::ImageCreateInfo2D(128, 128, 1, 2, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
vkt::Image image(*m_device, image_ci);
// layer 0 now VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
// layer 1 is still VK_IMAGE_LAYOUT_UNDEFINED.
m_command_buffer.Begin();
VkImageMemoryBarrier img_barrier = vku::InitStructHelper();
img_barrier.srcAccessMask = 0;
img_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
img_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
img_barrier.image = image;
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &img_barrier);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
vkt::ImageView image_view = image.CreateView(VK_IMAGE_VIEW_TYPE_2D, 0, 1, 0, 1);
VkShaderObj fs(*m_device, kFragmentSamplerGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_[1] = fs.GetStageCreateInfo();
pipe.dsl_bindings_[0] = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
pipe.CreateGraphicsPipeline();
vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo());
pipe.descriptor_set_->WriteDescriptorImageInfo(0, image_view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
pipe.descriptor_set_->UpdateDescriptorSets();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.pipeline_layout_, 0, 1,
&pipe.descriptor_set_->set_, 0, nullptr);
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, DescriptorArray) {
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/1998");
AddRequiredExtensions(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::descriptorBindingPartiallyBound);
RETURN_IF_SKIP(Init());
RETURN_IF_SKIP(InitRenderTarget());
const char *fs_source = R"glsl(
#version 450
#extension GL_EXT_nonuniform_qualifier : enable
layout(set = 0, binding = 0) uniform UBO { uint index; };
// [0] is bad layout
// [1] is good layout
layout(set = 0, binding = 1) uniform sampler2D tex[2];
layout(location = 0) out vec4 uFragColor;
void main(){
uFragColor = texture(tex[index], vec2(0, 0));
}
)glsl";
VkShaderObj vs(*m_device, kVertexDrawPassthroughGlsl, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT);
OneOffDescriptorIndexingSet descriptor_set(m_device,
{
{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr, 0},
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_ALL, nullptr,
VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT},
});
const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.gp_ci_.layout = pipeline_layout;
pipe.CreateGraphicsPipeline();
vkt::Buffer in_buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, kHostVisibleMemProps);
uint32_t *in_buffer_ptr = (uint32_t *)in_buffer.Memory().Map();
in_buffer_ptr[0] = 1;
vkt::Image bad_image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
vkt::Image good_image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
good_image.SetLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
vkt::ImageView bad_image_view = bad_image.CreateView(VK_IMAGE_ASPECT_COLOR_BIT);
vkt::ImageView good_image_view = good_image.CreateView(VK_IMAGE_ASPECT_COLOR_BIT);
vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo());
descriptor_set.WriteDescriptorBufferInfo(0, in_buffer, 0, VK_WHOLE_SIZE);
descriptor_set.WriteDescriptorImageInfo(1, bad_image_view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0);
descriptor_set.WriteDescriptorImageInfo(1, good_image_view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 1);
descriptor_set.UpdateDescriptorSets();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
nullptr);
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, MultipleLayoutChanges) {
// This test is for manual inspection of the code as of April 2025 and it demos that after multiple
// layout transitions the layout entry can store a dangling pointer to initial layout state which
// is caused by pointer invalidation after container resize. This test does not cause crash because
// the resulting dangling pointer in not used, still this can be a useful regression test.
TEST_DESCRIPTION("Perform multiple layout transitions in a row");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(Init());
// Create image with 4 mip levels
auto image_ci = vkt::Image::ImageCreateInfo2D(128, 128, 4, 1, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
vkt::Image image(*m_device, image_ci);
VkImageMemoryBarrier2 barrier = vku::InitStructHelper();
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.image = image;
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
m_command_buffer.Begin();
// This sequence is tied closely to implementation as of April 2025.
// The first two barriers just populate 2 entries in small_vector<2> which does not cause resizes.
barrier.subresourceRange.baseMipLevel = 0;
m_command_buffer.Barrier(barrier);
barrier.subresourceRange.baseMipLevel = 1;
m_command_buffer.Barrier(barrier);
// The third operation finally allocates dynamic memory and caches a pointer to the heap location.
barrier.subresourceRange.baseMipLevel = 2;
m_command_buffer.Barrier(barrier);
// The forth operation reallocates again so the cached pointer becomes a dangling one.
barrier.subresourceRange.baseMipLevel = 3;
m_command_buffer.Barrier(barrier);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, TimelineSemaphoreOrdering) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10185
TEST_DESCRIPTION("Timeline semaphore specifies the order of command buffer execution so it is different than submission order");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::timelineSemaphore);
RETURN_IF_SKIP(Init());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
vkt::Buffer buffer(*m_device, 32 * 32 * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
const VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
layout_transition.srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
layout_transition.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
layout_transition.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
layout_transition.newLayout = VK_IMAGE_LAYOUT_GENERAL;
layout_transition.image = image;
layout_transition.subresourceRange = subresource_range;
VkClearColorValue clear_color{};
VkBufferImageCopy copy_region{};
copy_region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_region.imageExtent = {32, 32, 1};
m_command_buffer.Begin();
m_command_buffer.Barrier(layout_transition);
vk::CmdClearColorImage(m_command_buffer, image, VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &subresource_range);
m_command_buffer.End();
m_second_command_buffer.Begin();
vk::CmdCopyImageToBuffer(m_second_command_buffer, image, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, &copy_region);
m_second_command_buffer.End();
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveImageLayout, FramebufferAttachmentFrom3dImageSlice) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10330
TEST_DESCRIPTION("Subpass transitions arbitrary slice of 3d image");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::maintenance9);
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(Init());
// Create 3d image with 2 slices
VkImageCreateInfo image_ci = vku::InitStructHelper();
image_ci.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
image_ci.imageType = VK_IMAGE_TYPE_3D;
image_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
image_ci.extent = {32, 32, 2};
image_ci.mipLevels = 1;
image_ci.arrayLayers = 1;
image_ci.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
vkt::Image image_3d(*m_device, image_ci);
// Image view for slice 1
VkImageViewCreateInfo image_view_ci = vku::InitStructHelper();
image_view_ci.image = image_3d;
image_view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_ci.format = image_ci.format;
image_view_ci.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 1, 1};
const vkt::ImageView image_view(*m_device, image_view_ci);
RenderPassSingleSubpass render_pass(*this);
render_pass.AddAttachmentDescription(image_ci.format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
render_pass.AddAttachmentReference({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL});
render_pass.AddColorAttachment(0);
render_pass.CreateRenderPass();
vkt::Framebuffer framebuffer(*m_device, render_pass, 1, &image_view.handle(), 32, 32);
// Transition slice 1
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
layout_transition.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
layout_transition.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
layout_transition.image = image_3d;
layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 1, 1};
VkDependencyInfo dep_info = vku::InitStructHelper();
dep_info.imageMemoryBarrierCount = 1;
dep_info.pImageMemoryBarriers = &layout_transition;
m_command_buffer.Begin();
// Render pass transitions slice 1 to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
// The original issue was that layout transition worked only for slice 0.
m_command_buffer.BeginRenderPass(render_pass, framebuffer, 32, 32);
m_command_buffer.EndRenderPass();
// In case of regression the following transition will report layout mismatch error.
vk::CmdPipelineBarrier2(m_command_buffer, &dep_info);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, TransitionAll3dImageSlices) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10377
TEST_DESCRIPTION("Ensure that VK_REMAINING_ARRAY_LAYERS transitions all slices for 3d image slices");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::maintenance9);
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(Init());
VkImageCreateInfo image_ci = vku::InitStructHelper();
image_ci.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
image_ci.imageType = VK_IMAGE_TYPE_3D;
image_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
image_ci.extent = {4, 4, 2};
image_ci.mipLevels = 1;
image_ci.arrayLayers = 1;
image_ci.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
vkt::Image image(*m_device, image_ci);
VkImageMemoryBarrier2 barrier = vku::InitStructHelper();
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
barrier.image = image;
vkt::Buffer buffer_src(*m_device, 128, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_dst(*m_device, 128, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
VkBufferImageCopy region{};
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.imageExtent = {4, 2, 2};
m_command_buffer.Begin();
barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT;
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
// The test checks that this barrier transitions both slices of 3d image.
// In the original issue only the first slice was transitioned in which case the rest of the test leads to validation error.
m_command_buffer.Barrier(barrier);
vk::CmdCopyBufferToImage(m_command_buffer, buffer_src, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT;
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
m_command_buffer.Barrier(barrier);
vk::CmdCopyImageToBuffer(m_command_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer_dst, 1, &region);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, DepthSliceTransitionCriteriaNotMet) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10453
TEST_DESCRIPTION("Enabled maintenance9 but do not set image flag VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_EXT_IMAGE_2D_VIEW_OF_3D_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::image2DViewOf3D);
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(Init());
VkImageCreateInfo image_ci = vku::InitStructHelper();
image_ci.flags = VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT;
image_ci.imageType = VK_IMAGE_TYPE_3D;
image_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
image_ci.extent = {64, 64, 4};
image_ci.mipLevels = 1;
image_ci.arrayLayers = 1;
image_ci.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci.usage = VK_IMAGE_USAGE_STORAGE_BIT;
vkt::Image image(*m_device, image_ci);
vkt::ImageView image_view = image.CreateView(VK_IMAGE_VIEW_TYPE_2D, 0, 1, 3, 1);
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
layout_transition.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
layout_transition.newLayout = VK_IMAGE_LAYOUT_GENERAL;
layout_transition.image = image;
layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
const char *cs_source = R"glsl(
#version 450 core
layout (rgba8, set = 0, binding = 0) uniform image2D verifyImage;
void main (void) {
imageStore(verifyImage, ivec2(1,1), vec4(0.5f));
}
)glsl";
OneOffDescriptorSet descriptor_set(m_device,
{
{0u, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
});
descriptor_set.WriteDescriptorImageInfo(0u, image_view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_IMAGE_LAYOUT_GENERAL, 0);
descriptor_set.UpdateDescriptorSets();
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&descriptor_set.layout_});
pipe.CreateComputePipeline();
m_command_buffer.Begin();
m_command_buffer.Barrier(layout_transition);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.pipeline_layout_, 0, 1, &descriptor_set.set_,
0, nullptr);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk::CmdDispatch(m_command_buffer, 1, 1, 1);
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, SeparateDepthAspectTransition) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11083
TEST_DESCRIPTION("Test separate depth aspect transition in depth-stencil image");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::dynamicRendering);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::separateDepthStencilLayouts);
RETURN_IF_SKIP(Init());
auto depth_stencil_format = FindSupportedDepthStencilFormat(Gpu());
vkt::Image ds_image(*m_device, 32, 32, depth_stencil_format,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkt::ImageView ds_image_view = ds_image.CreateView(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
// At first transition both depth and stencil
VkImageMemoryBarrier2 transition0 = vku::InitStructHelper();
transition0.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
transition0.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
transition0.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
transition0.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
transition0.image = ds_image;
transition0.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
// Then transition only depth
VkImageMemoryBarrier2 transition1 = vku::InitStructHelper();
transition1.srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
transition1.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
transition1.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
transition1.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
transition1.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
transition1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
transition1.image = ds_image;
transition1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
VkRenderingAttachmentInfo depth_attachment = vku::InitStructHelper();
depth_attachment.imageView = ds_image_view;
depth_attachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering_info = vku::InitStructHelper();
rendering_info.renderArea.extent = {32, 32};
rendering_info.layerCount = 1;
rendering_info.pDepthAttachment = &depth_attachment;
m_command_buffer.Begin();
m_command_buffer.Barrier(transition0);
m_command_buffer.Barrier(transition1);
// In the original issue the final result of two transitions did not match expected layout for rendering
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, SeparateStencilAspectTransition) {
TEST_DESCRIPTION("Separate stencil aspect transition of depth-stencil image");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::separateDepthStencilLayouts);
RETURN_IF_SKIP(Init());
const VkFormat depth_stencil_format = FindSupportedDepthStencilFormat(Gpu());
vkt::Image image(*m_device, 128, 128, depth_stencil_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkt::ImageView image_view = image.CreateView(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
VkImageMemoryBarrier2 transition0 = vku::InitStructHelper();
transition0.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
transition0.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
transition0.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
transition0.newLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
transition0.image = image;
transition0.subresourceRange = {VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
VkImageMemoryBarrier2 transition1 = vku::InitStructHelper();
transition1.srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
transition1.srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
transition1.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
transition1.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR;
transition1.oldLayout = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL;
transition1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
transition1.image = image;
transition1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
m_command_buffer.Begin();
m_command_buffer.Barrier(transition0);
m_command_buffer.Barrier(transition1);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, SeparateDepthStencilAndRenderToStencil) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11074
TEST_DESCRIPTION("Separate stencil aspect transition and rendering to stencil aspect");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::dynamicRendering);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::separateDepthStencilLayouts);
RETURN_IF_SKIP(Init());
const VkFormat depth_stencil_format = FindSupportedDepthStencilFormat(Gpu());
vkt::Image image(*m_device, 128, 128, depth_stencil_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkt::ImageView image_view = image.CreateView(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
VkImageMemoryBarrier2 transition0 = vku::InitStructHelper();
transition0.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
transition0.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
transition0.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
transition0.newLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
transition0.image = image;
transition0.subresourceRange = {VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
VkImageMemoryBarrier2 transition1 = vku::InitStructHelper();
transition1.srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
transition1.srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
transition1.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
transition1.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR;
transition1.oldLayout = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL;
transition1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
transition1.image = image;
transition1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
VkRenderingAttachmentInfo stencil_attachment = vku::InitStructHelper();
stencil_attachment.imageView = image_view;
stencil_attachment.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
stencil_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
stencil_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering_info = vku::InitStructHelper();
rendering_info.renderArea.extent = {128, 128};
rendering_info.layerCount = 1;
rendering_info.pStencilAttachment = &stencil_attachment;
m_command_buffer.Begin();
m_command_buffer.Barrier(transition0);
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
// In the original issue the next transition caused validation error.
// Rendering to stencil attachment incorrectly updated first layout of depth aspect.
// According to the spec image view's aspect must be ignored (which includes both DEPTH
// and STENCIL here) and STENCIL aspect must be used.
m_command_buffer.Barrier(transition1);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, DynamicRenderingColorAttachmentLayoutSubmitTime) {
TEST_DESCRIPTION("Check dynamic rendering attachment layout at submit time");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::dynamicRendering);
RETURN_IF_SKIP(Init());
vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
vkt::ImageView image_view = image.CreateView();
// Set layout so it matches layout specified by VkRenderingAttachmentInfo
image.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
color_attachment.imageView = image_view;
color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering_info = vku::InitStructHelper();
rendering_info.renderArea.extent = {128, 128};
rendering_info.layerCount = 1;
rendering_info.colorAttachmentCount = 1;
rendering_info.pColorAttachments = &color_attachment;
m_command_buffer.Begin();
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
m_command_buffer.End();
m_default_queue->SubmitAndWait(m_command_buffer);
}
TEST_F(PositiveImageLayout, DynamicRenderingFragmentShadingRate) {
TEST_DESCRIPTION("Test dynamic rendering FSR attachment layout tracking");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::dynamicRendering);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::attachmentFragmentShadingRate);
RETURN_IF_SKIP(Init());
VkPhysicalDeviceFragmentShadingRatePropertiesKHR fsr_properties = vku::InitStructHelper();
GetPhysicalDeviceProperties2(fsr_properties);
const VkExtent2D fsr_cell = fsr_properties.minFragmentShadingRateAttachmentTexelSize;
vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
vkt::ImageView image_view = image.CreateView();
vkt::Image fsr_image(*m_device, 128 / fsr_cell.width, 128 / fsr_cell.height, VK_FORMAT_R8_UINT,
VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
vkt::ImageView fsr_image_view = fsr_image.CreateView();
VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
color_attachment.imageView = image_view;
color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingFragmentShadingRateAttachmentInfoKHR fsr_attachment = vku::InitStructHelper();
fsr_attachment.imageView = fsr_image_view;
fsr_attachment.imageLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
fsr_attachment.shadingRateAttachmentTexelSize = fsr_cell;
VkRenderingInfo rendering_info = vku::InitStructHelper(&fsr_attachment);
rendering_info.renderArea.extent = {128, 128};
rendering_info.layerCount = 1;
rendering_info.colorAttachmentCount = 1;
rendering_info.pColorAttachments = &color_attachment;
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
layout_transition.srcAccessMask = VK_ACCESS_2_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
layout_transition.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
layout_transition.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
// The test checks that VkRenderingFragmentShadingRateAttachmentInfoKHR::imageLayout is
// tracked properly and matches transition's oldLayout.
layout_transition.oldLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
layout_transition.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
layout_transition.image = fsr_image;
layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
m_command_buffer.Begin();
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
m_command_buffer.Barrier(layout_transition);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, DynamicRenderingFragmentDensityMap) {
TEST_DESCRIPTION("Test dynamic rendering FDM attachment layout tracking");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::dynamicRendering);
AddRequiredFeature(vkt::Feature::synchronization2);
AddRequiredFeature(vkt::Feature::fragmentDensityMap);
RETURN_IF_SKIP(Init());
VkImageCreateInfo image_ci =
vkt::Image::ImageCreateInfo2D(128, 128, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
image_ci.flags = VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT;
vkt::Image image(*m_device, image_ci);
vkt::ImageView image_view = image.CreateView();
vkt::Image fdm_image(*m_device, 128, 128, VK_FORMAT_R8G8_UNORM,
VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
vkt::ImageView fdm_image_view = fdm_image.CreateView();
VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
color_attachment.imageView = image_view;
color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingFragmentDensityMapAttachmentInfoEXT fdm_attachment = vku::InitStructHelper();
fdm_attachment.imageView = fdm_image_view;
fdm_attachment.imageLayout = VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT;
VkRenderingInfo rendering_info = vku::InitStructHelper(&fdm_attachment);
rendering_info.renderArea.extent = {128, 128};
rendering_info.layerCount = 1;
rendering_info.colorAttachmentCount = 1;
rendering_info.pColorAttachments = &color_attachment;
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_DENSITY_PROCESS_BIT_EXT;
layout_transition.srcAccessMask = VK_ACCESS_2_FRAGMENT_DENSITY_MAP_READ_BIT_EXT;
layout_transition.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
layout_transition.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
// The test checks that VkRenderingFragmentDensityMapAttachmentInfoEXT::imageLayout is
// tracked properly and matches transition's oldLayout.
layout_transition.oldLayout = VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT;
layout_transition.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
layout_transition.image = fdm_image;
layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
m_command_buffer.Begin();
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
m_command_buffer.Barrier(layout_transition);
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, DynamicRendering3DImageLayout) {
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10951
TEST_DESCRIPTION("Use 3d image slice as dynamic rendering attachment and validate its layout");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredFeature(vkt::Feature::dynamicRendering);
RETURN_IF_SKIP(Init());
// 3D image with 4 slices
VkImageCreateInfo image_ci = vku::InitStructHelper();
image_ci.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
image_ci.imageType = VK_IMAGE_TYPE_3D;
image_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
image_ci.extent = {32, 32, 4};
image_ci.mipLevels = 1;
image_ci.arrayLayers = 1;
image_ci.samples = VK_SAMPLE_COUNT_1_BIT;
image_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
image_ci.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
vkt::Image image_3d(*m_device, image_ci);
// Image view for slice 3
vkt::ImageView image_view = image_3d.CreateView(VK_IMAGE_VIEW_TYPE_2D, 0, 1, 2, 1);
VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper();
color_attachment.imageView = image_view;
color_attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering_info = vku::InitStructHelper();
rendering_info.renderArea.extent = {32, 32};
rendering_info.layerCount = 1;
rendering_info.colorAttachmentCount = 1;
rendering_info.pColorAttachments = &color_attachment;
const VkClearColorValue clear_color{};
const VkImageSubresourceRange clear_subresource{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
m_command_buffer.Begin();
// Issue command that mentions image layout, so BeginRendering will have non-empty image layout map
// and will go with current image layout validation. In the original issue that validation caused
// assert due to invalid subresource range was passed to range encoder.
vk::CmdClearColorImage(m_command_buffer, image_3d, VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &clear_subresource);
m_command_buffer.BeginRendering(rendering_info);
m_command_buffer.EndRendering();
m_command_buffer.End();
}
TEST_F(PositiveImageLayout, CopyColorToDepthOnComputeQueue) {
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_10_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::maintenance8);
AddRequiredFeature(vkt::Feature::maintenance10);
RETURN_IF_SKIP(Init());
auto compute_without_graphics_queue_i = m_device->QueueFamily(VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT);
if (!compute_without_graphics_queue_i.has_value()) {
GTEST_SKIP() << "Need a queue that supports compute but not graphics";
}
const bool ds_supports_copy_on_compute_queue = FormatFeatures2AreSupported(
Gpu(), VK_FORMAT_D16_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_2_DEPTH_COPY_ON_COMPUTE_QUEUE_BIT_KHR);
if (!ds_supports_copy_on_compute_queue) {
GTEST_SKIP() << "Test requires format features to support depth copy on compute queue";
}
vkt::CommandPool pool(*m_device, *compute_without_graphics_queue_i);
vkt::CommandBuffer cb(*m_device, pool);
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_R16_UINT;
image_create_info.extent = {32, 32, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 4;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_create_info.flags = 0;
vkt::Image src_image(*m_device, image_create_info, vkt::set_layout);
image_create_info.format = VK_FORMAT_D16_UNORM;
image_create_info.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
vkt::Image depth_image(*m_device, image_create_info, vkt::set_layout);
cb.Begin();
VkImageCopy copy_region{};
copy_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
copy_region.srcOffset = {0, 0, 0};
copy_region.dstOffset = {0, 0, 0};
copy_region.extent = {1, 1, 1};
vk::CmdCopyImage(cb, src_image, VK_IMAGE_LAYOUT_GENERAL, depth_image, VK_IMAGE_LAYOUT_GENERAL, 1, &copy_region);
}
TEST_F(PositiveImageLayout, CopyColorToDepthOnTransferQueue) {
SetTargetApiVersion(VK_API_VERSION_1_3);
AddRequiredExtensions(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_10_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::maintenance8);
AddRequiredFeature(vkt::Feature::maintenance10);
RETURN_IF_SKIP(Init());
auto compute_without_graphics_queue_i =
m_device->QueueFamily(VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT);
if (!compute_without_graphics_queue_i.has_value()) {
GTEST_SKIP() << "Need a queue that supports transfer but not graphics";
}
const bool ds_supports_copy_on_transfer_queue = FormatFeatures2AreSupported(
Gpu(), VK_FORMAT_D16_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_2_DEPTH_COPY_ON_TRANSFER_QUEUE_BIT_KHR);
if (!ds_supports_copy_on_transfer_queue) {
GTEST_SKIP() << "Test requires format features to support depth copy on transfer queue";
}
vkt::CommandPool pool(*m_device, *compute_without_graphics_queue_i);
vkt::CommandBuffer cb(*m_device, pool);
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_R16_UINT;
image_create_info.extent = {32, 32, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 4;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_create_info.flags = 0;
vkt::Image src_image(*m_device, image_create_info, vkt::set_layout);
image_create_info.format = VK_FORMAT_D16_UNORM;
image_create_info.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
vkt::Image depth_image(*m_device, image_create_info, vkt::set_layout);
cb.Begin();
VkImageCopy copy_region{};
copy_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
copy_region.srcOffset = {0, 0, 0};
copy_region.dstOffset = {0, 0, 0};
copy_region.extent = {1, 1, 1};
vk::CmdCopyImage(cb, src_image, VK_IMAGE_LAYOUT_GENERAL, depth_image, VK_IMAGE_LAYOUT_GENERAL, 1, &copy_region);
}