| /* |
| * 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, ©_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, ®ion); |
| |
| 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, ®ion); |
| 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, ©_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, ©_region); |
| } |