| /* |
| * Copyright (c) 2015-2025 The Khronos Group Inc. |
| * Copyright (c) 2015-2025 Valve Corporation |
| * Copyright (c) 2015-2025 LunarG, Inc. |
| * Copyright (c) 2015-2025 Google, Inc. |
| * Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. |
| * |
| * 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 <vulkan/vulkan_core.h> |
| #include "utils/cast_utils.h" |
| #include "../framework/layer_validation_tests.h" |
| #include "../framework/pipeline_helper.h" |
| #include "../framework/render_pass_helper.h" |
| #include "../framework/descriptor_helper.h" |
| #include "../framework/external_memory_sync.h" |
| |
| class NegativeObjectLifetime : public VkLayerTest {}; |
| |
| TEST_F(NegativeObjectLifetime, CmdBufferBufferDestroyed) { |
| TEST_DESCRIPTION("Attempt to draw with a command buffer that is invalid due to a buffer dependency being destroyed."); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| vk::CmdFillBuffer(m_command_buffer, buffer, 0, VK_WHOLE_SIZE, 0); |
| m_command_buffer.End(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| // Destroy buffer dependency prior to submit to cause ERROR |
| buffer.Destroy(); |
| |
| m_default_queue->Submit(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBarrierBufferDestroyed) { |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| VkBufferMemoryBarrier buf_barrier = vku::InitStructHelper(); |
| buf_barrier.buffer = buffer; |
| buf_barrier.offset = 0; |
| buf_barrier.size = VK_WHOLE_SIZE; |
| |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 1, |
| &buf_barrier, 0, NULL); |
| m_command_buffer.End(); |
| |
| m_default_queue->Submit(m_command_buffer); |
| |
| VkDeviceMemory leaked_handled = buffer.Memory().handle(); |
| m_errorMonitor->SetDesiredError("VUID-vkFreeMemory-memory-00677"); |
| buffer.Memory().Destroy(); |
| m_errorMonitor->VerifyFound(); |
| |
| m_default_queue->Wait(); |
| |
| // free because we didn't actually free it in due to the error above |
| vk::FreeMemory(*m_device, leaked_handled, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBarrierImageDestroyed) { |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| VkImageMemoryBarrier img_barrier = vku::InitStructHelper(); |
| img_barrier.image = image; |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| 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->Submit(m_command_buffer); |
| |
| VkDeviceMemory leaked_handled = image.Memory().handle(); |
| m_errorMonitor->SetDesiredError("VUID-vkFreeMemory-memory-00677"); |
| image.Memory().Destroy(); |
| m_errorMonitor->VerifyFound(); |
| |
| m_default_queue->Wait(); |
| |
| // free because we didn't actually free it in due to the error above |
| vk::FreeMemory(*m_device, leaked_handled, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, Sync2CmdBarrierBufferDestroyed) { |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| VkBufferMemoryBarrier2 buf_barrier = vku::InitStructHelper(); |
| buf_barrier.buffer = buffer; |
| buf_barrier.offset = 0; |
| buf_barrier.size = VK_WHOLE_SIZE; |
| buf_barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| buf_barrier.dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| |
| m_command_buffer.BarrierKHR(buf_barrier); |
| |
| buffer.Memory().Destroy(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkEndCommandBuffer-commandBuffer-00059"); |
| vk::EndCommandBuffer(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->SubmitAndWait(m_command_buffer); |
| |
| buffer.Destroy(); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, Sync2CmdBarrierImageDestroyed) { |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| VkImageMemoryBarrier2 img_barrier = vku::InitStructHelper(); |
| img_barrier.image = image; |
| img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| img_barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| img_barrier.dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| |
| m_command_buffer.BarrierKHR(img_barrier); |
| |
| image.Memory().Destroy(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkEndCommandBuffer-commandBuffer-00059"); |
| vk::EndCommandBuffer(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->SubmitAndWait(m_command_buffer); |
| image.Destroy(); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBufferBufferViewDestroyed) { |
| TEST_DESCRIPTION("Delete bufferView bound to cmd buffer, then attempt to submit cmd buffer."); |
| |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| OneOffDescriptorSet descriptor_set(m_device, |
| { |
| {0, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, |
| }); |
| CreatePipelineHelper pipe(*this); |
| VkBufferCreateInfo buffer_create_info = vku::InitStructHelper(); |
| VkBufferViewCreateInfo bvci = vku::InitStructHelper(); |
| VkBufferView view; |
| |
| { |
| buffer_create_info.size = 1024; |
| buffer_create_info.usage = VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; |
| vkt::Buffer buffer(*m_device, buffer_create_info); |
| |
| bvci.buffer = buffer; |
| bvci.format = VK_FORMAT_R32_SFLOAT; |
| bvci.range = VK_WHOLE_SIZE; |
| |
| VkResult err = vk::CreateBufferView(device(), &bvci, NULL, &view); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| descriptor_set.WriteDescriptorBufferView(0, view); |
| descriptor_set.UpdateDescriptorSets(); |
| |
| const char *fsSource = R"glsl( |
| #version 450 |
| layout(set=0, binding=0, r32f) uniform readonly imageBuffer s; |
| layout(location=0) out vec4 x; |
| void main(){ |
| x = imageLoad(s, 0); |
| } |
| )glsl"; |
| VkShaderObj vs(*m_device, kVertexMinimalGlsl, VK_SHADER_STAGE_VERTEX_BIT); |
| VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT); |
| |
| pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()}; |
| pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&descriptor_set.layout_}); |
| err = pipe.CreateGraphicsPipeline(); |
| if (err != VK_SUCCESS) { |
| GTEST_SKIP() << "Unable to compile shader"; |
| } |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| |
| // Bind pipeline to cmd buffer - This causes crash on Mali |
| 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, |
| &descriptor_set.set_, 0, nullptr); |
| } |
| // buffer is released. |
| m_errorMonitor->SetDesiredError("VUID-vkCmdDraw-None-08114"); // buffer |
| vk::CmdDraw(m_command_buffer, 1, 0, 0, 0); |
| m_errorMonitor->VerifyFound(); |
| |
| vk::DestroyBufferView(device(), view, NULL); |
| m_errorMonitor->SetDesiredError("VUID-vkCmdDraw-None-08114"); // bufferView |
| vk::CmdDraw(m_command_buffer, 1, 0, 0, 0); |
| m_errorMonitor->VerifyFound(); |
| |
| vkt::Buffer buffer(*m_device, buffer_create_info); |
| |
| bvci.buffer = buffer; |
| VkResult err = vk::CreateBufferView(device(), &bvci, NULL, &view); |
| ASSERT_EQ(VK_SUCCESS, err); |
| descriptor_set.Clear(); |
| descriptor_set.WriteDescriptorBufferView(0, view); |
| descriptor_set.UpdateDescriptorSets(); |
| |
| vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.pipeline_layout_, 0, 1, &descriptor_set.set_, |
| 0, nullptr); |
| vk::CmdDraw(m_command_buffer, 1, 0, 0, 0); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| |
| // Delete BufferView in order to invalidate cmd buffer |
| vk::DestroyBufferView(device(), view, NULL); |
| // Now attempt submit of cmd buffer |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->Submit(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DescriptorSetStorageBufferDestroyed) { |
| RETURN_IF_SKIP(Init()); |
| |
| OneOffDescriptorSet descriptor_set(m_device, { |
| {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }); |
| const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); |
| |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); |
| descriptor_set.WriteDescriptorBufferInfo(0, buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); |
| descriptor_set.UpdateDescriptorSets(); |
| |
| const char *cs_source = R"glsl( |
| #version 450 |
| layout(set=0, binding=0) buffer SSBO { uint x; }; |
| void main(){ |
| x = 0; |
| } |
| )glsl"; |
| |
| CreateComputePipelineHelper pipe(*this); |
| pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT); |
| pipe.cp_ci_.layout = pipeline_layout; |
| pipe.CreateComputePipeline(); |
| |
| // Destroy the buffer before it's bound to the cmd buffer |
| buffer.Destroy(); |
| |
| m_command_buffer.Begin(); |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); |
| vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0, |
| NULL); |
| m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08114"); |
| vk::CmdDispatch(m_command_buffer, 1, 1, 1); |
| m_errorMonitor->VerifyFound(); |
| m_command_buffer.End(); |
| } |
| |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8918 |
| TEST_F(NegativeObjectLifetime, DISABLED_DescriptorSetMutableBufferDestroyed) { |
| AddRequiredExtensions(VK_EXT_MUTABLE_DESCRIPTOR_TYPE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::mutableDescriptorType); |
| RETURN_IF_SKIP(Init()); |
| |
| VkDescriptorType desc_types[2] = { |
| VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, |
| }; |
| |
| VkMutableDescriptorTypeListEXT type_list = {2, desc_types}; |
| VkMutableDescriptorTypeCreateInfoEXT mdtci = vku::InitStructHelper(); |
| mdtci.mutableDescriptorTypeListCount = 1; |
| mdtci.pMutableDescriptorTypeLists = &type_list; |
| |
| OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_MUTABLE_EXT, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}}, 0, |
| &mdtci); |
| const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); |
| |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); |
| descriptor_set.WriteDescriptorBufferInfo(0, buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); |
| descriptor_set.UpdateDescriptorSets(); |
| |
| const char *cs_source = R"glsl( |
| #version 450 |
| layout(set=0, binding=0) buffer SSBO { uint x; }; |
| void main(){ |
| x = 0; |
| } |
| )glsl"; |
| |
| CreateComputePipelineHelper pipe(*this); |
| pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT); |
| pipe.cp_ci_.layout = pipeline_layout; |
| pipe.CreateComputePipeline(); |
| |
| buffer.Destroy(); // Destroy the buffer before it's bound to the cmd buffer |
| |
| m_command_buffer.Begin(); |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); |
| vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0, |
| NULL); |
| m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08114"); |
| vk::CmdDispatch(m_command_buffer, 1, 1, 1); |
| m_errorMonitor->VerifyFound(); |
| m_command_buffer.End(); |
| } |
| |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8918 |
| TEST_F(NegativeObjectLifetime, DISABLED_DescriptorSetMutableBufferArrayDestroyed) { |
| AddRequiredExtensions(VK_EXT_MUTABLE_DESCRIPTOR_TYPE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::mutableDescriptorType); |
| RETURN_IF_SKIP(Init()); |
| |
| VkDescriptorType desc_types[2] = { |
| VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, |
| }; |
| |
| VkMutableDescriptorTypeListEXT type_list = {2, desc_types}; |
| VkMutableDescriptorTypeCreateInfoEXT mdtci = vku::InitStructHelper(); |
| mdtci.mutableDescriptorTypeListCount = 1; |
| mdtci.pMutableDescriptorTypeLists = &type_list; |
| |
| OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_MUTABLE_EXT, 2, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}}, 0, |
| &mdtci); |
| const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); |
| |
| vkt::Buffer storage_buffer(*m_device, 32, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); // not used |
| vkt::Buffer uniform_buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); // used |
| descriptor_set.WriteDescriptorBufferInfo(0, storage_buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0); |
| descriptor_set.WriteDescriptorBufferInfo(0, uniform_buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); |
| descriptor_set.UpdateDescriptorSets(); |
| |
| const char *cs_source = R"glsl( |
| #version 450 |
| layout(set=0, binding=0) buffer SSBO { uint x; } ssbo[2]; |
| void main(){ |
| ssbo[1].x = 0; |
| } |
| )glsl"; |
| |
| CreateComputePipelineHelper pipe(*this); |
| pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT); |
| pipe.cp_ci_.layout = pipeline_layout; |
| pipe.CreateComputePipeline(); |
| |
| uniform_buffer.Destroy(); // Destroy the buffer before it's bound to the cmd buffer |
| |
| m_command_buffer.Begin(); |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); |
| vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0, |
| NULL); |
| m_errorMonitor->SetDesiredError("VUID-vkCmdDispatch-None-08114"); |
| vk::CmdDispatch(m_command_buffer, 1, 1, 1); |
| m_errorMonitor->VerifyFound(); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBufferImageDestroyed) { |
| TEST_DESCRIPTION("Attempt to draw with a command buffer that is invalid due to an image dependency being destroyed."); |
| RETURN_IF_SKIP(Init()) |
| |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); |
| |
| m_command_buffer.Begin(); |
| VkClearColorValue ccv = {}; |
| VkImageSubresourceRange isr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| vk::CmdClearColorImage(m_command_buffer, image, VK_IMAGE_LAYOUT_GENERAL, &ccv, 1, &isr); |
| m_command_buffer.End(); |
| |
| image.Destroy(); |
| // Destroy image dependency prior to submit to cause ERROR |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->Submit(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBufferFramebufferImageDestroyed) { |
| TEST_DESCRIPTION( |
| "Attempt to draw with a command buffer that is invalid due to a framebuffer image dependency being destroyed."); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| vkt::ImageView image_view = image.CreateView(); |
| vkt::Framebuffer fb(*m_device, m_renderPass, 1, &image_view.handle()); |
| |
| // Just use default renderpass with our framebuffer |
| m_renderPassBeginInfo.framebuffer = fb; |
| m_renderPassBeginInfo.renderArea.extent = {32, 32}; |
| // Create Null cmd buffer for submit |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| |
| // Destroy image attached to framebuffer to invalidate cmd buffer |
| // Now attempt to submit cmd buffer and verify error |
| image.Destroy(); |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->SubmitAndWait(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, FramebufferAttachmentMemoryFreed) { |
| TEST_DESCRIPTION("Attempt to create framebuffer with attachment which memory was freed."); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| vkt::ImageView image_view = image.CreateView(); |
| image.Memory().Destroy(); |
| |
| m_errorMonitor->SetDesiredError("UNASSIGNED-VkFramebufferCreateInfo-BoundResourceFreedMemoryAccess"); |
| vkt::Framebuffer fb(*m_device, m_renderPass, 1, &image_view.handle()); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DescriptorPoolInUseDestroyed) { |
| TEST_DESCRIPTION("Delete a DescriptorPool with a DescriptorSet that is in use."); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| // Create image to update the descriptor with |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); |
| image.SetLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); |
| |
| vkt::ImageView view = image.CreateView(); |
| vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo()); |
| // 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_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()}; |
| pipe.dsl_bindings_ = { |
| {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }; |
| pipe.CreateGraphicsPipeline(); |
| |
| // Update descriptor with image and sampler |
| pipe.descriptor_set_->WriteDescriptorImageInfo(0, 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, NULL); |
| |
| vk::CmdDraw(m_command_buffer, 1, 0, 0, 0); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| // Submit cmd buffer to put pool in-flight |
| m_default_queue->Submit(m_command_buffer); |
| // Destroy pool while in-flight, causing error |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyDescriptorPool-descriptorPool-00303"); |
| vk::DestroyDescriptorPool(device(), pipe.descriptor_set_->pool_, NULL); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| |
| m_errorMonitor->SetUnexpectedError( |
| "If descriptorPool is not VK_NULL_HANDLE, descriptorPool must be a valid VkDescriptorPool handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove DescriptorPool obj"); |
| // TODO : It seems Validation layers think ds_pool was already destroyed, even though it wasn't? |
| } |
| |
| TEST_F(NegativeObjectLifetime, FramebufferInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use framebuffer."); |
| RETURN_IF_SKIP(Init()); |
| VkFormatProperties format_properties; |
| vk::GetPhysicalDeviceFormatProperties(Gpu(), VK_FORMAT_B8G8R8A8_UNORM, &format_properties); |
| |
| InitRenderTarget(); |
| |
| vkt::Image image(*m_device, 256, 256, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| image.SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); |
| vkt::ImageView view = image.CreateView(); |
| |
| VkFramebufferCreateInfo fci = { |
| VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_renderPass, 1, &view.handle(), 256, 256, 1}; |
| VkFramebuffer fb; |
| vk::CreateFramebuffer(device(), &fci, nullptr, &fb); |
| |
| // Just use default renderpass with our framebuffer |
| m_renderPassBeginInfo.framebuffer = fb; |
| // Create Null cmd buffer for submit |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| // Submit cmd buffer to put it in-flight |
| m_default_queue->Submit(m_command_buffer); |
| // Destroy framebuffer while in-flight |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyFramebuffer-framebuffer-00892"); |
| vk::DestroyFramebuffer(device(), fb, NULL); |
| m_errorMonitor->VerifyFound(); |
| // Wait for queue to complete so we can safely destroy everything |
| m_default_queue->Wait(); |
| m_errorMonitor->SetUnexpectedError("If framebuffer is not VK_NULL_HANDLE, framebuffer must be a valid VkFramebuffer handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Framebuffer obj"); |
| vk::DestroyFramebuffer(device(), fb, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, PushDescriptorUniformDestroySignaled) { |
| TEST_DESCRIPTION("Destroy a uniform buffer in use by a push descriptor set"); |
| AddRequiredExtensions(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| VkDescriptorSetLayoutBinding dsl_binding = {2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; |
| const vkt::DescriptorSetLayout ds_layout(*m_device, {dsl_binding}); |
| // Create push descriptor set layout |
| const vkt::DescriptorSetLayout push_ds_layout(*m_device, {dsl_binding}, VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT); |
| |
| // Use helper to create graphics pipeline |
| CreatePipelineHelper helper(*this); |
| helper.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&push_ds_layout, &ds_layout}); |
| helper.CreateGraphicsPipeline(); |
| |
| const uint32_t data_size = sizeof(float) * 3; |
| vkt::Buffer vbo(*m_device, data_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); |
| |
| VkDescriptorBufferInfo buff_info{vbo, 0, data_size}; |
| VkWriteDescriptorSet descriptor_write = vku::InitStructHelper(); |
| descriptor_write.dstBinding = 2; |
| descriptor_write.descriptorCount = 1; |
| descriptor_write.pTexelBufferView = nullptr; |
| descriptor_write.pBufferInfo = &buff_info; |
| descriptor_write.pImageInfo = nullptr; |
| descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
| descriptor_write.dstSet = 0; // Should not cause a validation error |
| |
| m_command_buffer.Begin(); |
| |
| // In Intel GPU, it needs to bind pipeline before push descriptor set. |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, helper); |
| vk::CmdPushDescriptorSetKHR(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, helper.pipeline_layout_, 0, 1, |
| &descriptor_write); |
| m_command_buffer.End(); |
| |
| m_default_queue->Submit(m_command_buffer); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyBuffer-buffer-00922"); |
| vk::DestroyBuffer(*m_device, vbo, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, FramebufferImageInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use image that's child of framebuffer."); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Image image(*m_device, m_width, m_height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| image.SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); |
| vkt::ImageView view = image.CreateView(); |
| |
| RenderPassSingleSubpass rp(*this); |
| rp.AddAttachmentDescription(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); |
| rp.AddAttachmentReference({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); |
| rp.AddColorAttachment(0); |
| rp.CreateRenderPass(); |
| vkt::Framebuffer fb(*m_device, rp, 1, &view.handle(), 256, 256); |
| |
| // Create Null cmd buffer for submit |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(rp, fb); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| // Submit cmd buffer to put framebuffer and children in-flight |
| m_default_queue->Submit(m_command_buffer); |
| // Destroy image attached to framebuffer while in-flight |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyImage-image-01000"); |
| vk::DestroyImage(device(), image, NULL); |
| m_errorMonitor->VerifyFound(); |
| // Wait for queue to complete so we can safely destroy image and other objects |
| m_default_queue->Wait(); |
| m_errorMonitor->SetUnexpectedError("If image is not VK_NULL_HANDLE, image must be a valid VkImage handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Image obj"); |
| fb.Destroy(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, EventInUseDestroyed) { |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| m_command_buffer.Begin(); |
| |
| VkEvent event; |
| VkEventCreateInfo event_create_info = vku::InitStructHelper(); |
| vk::CreateEvent(device(), &event_create_info, nullptr, &event); |
| vk::CmdSetEvent(m_command_buffer, event, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); |
| |
| m_command_buffer.End(); |
| vk::DestroyEvent(device(), event, nullptr); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| m_default_queue->Submit(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, MiscInUseDestroyed) { |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| VkSemaphoreCreateInfo semaphore_create_info = vku::InitStructHelper(); |
| VkSemaphore semaphore; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateSemaphore(device(), &semaphore_create_info, nullptr, &semaphore)); |
| VkFenceCreateInfo fence_create_info = vku::InitStructHelper(); |
| VkFence fence; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateFence(device(), &fence_create_info, nullptr, &fence)); |
| |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); |
| |
| CreatePipelineHelper pipe(*this); |
| pipe.CreateGraphicsPipeline(); |
| |
| pipe.descriptor_set_->WriteDescriptorBufferInfo(0, buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); |
| pipe.descriptor_set_->UpdateDescriptorSets(); |
| |
| VkEvent event; |
| VkEventCreateInfo event_create_info = vku::InitStructHelper(); |
| vk::CreateEvent(device(), &event_create_info, nullptr, &event); |
| |
| m_command_buffer.Begin(); |
| |
| vk::CmdSetEvent(m_command_buffer, event, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); |
| |
| 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, NULL); |
| |
| m_command_buffer.End(); |
| |
| VkSubmitInfo submit_info = vku::InitStructHelper(); |
| submit_info.commandBufferCount = 1; |
| submit_info.pCommandBuffers = &m_command_buffer.handle(); |
| submit_info.signalSemaphoreCount = 1; |
| submit_info.pSignalSemaphores = &semaphore; |
| vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, fence); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyEvent-event-01145"); |
| vk::DestroyEvent(device(), event, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroySemaphore-semaphore-05149"); |
| vk::DestroySemaphore(device(), semaphore, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyFence-fence-01120"); |
| vk::DestroyFence(device(), fence, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| m_default_queue->Wait(); |
| m_errorMonitor->SetUnexpectedError("If semaphore is not VK_NULL_HANDLE, semaphore must be a valid VkSemaphore handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Semaphore obj"); |
| vk::DestroySemaphore(device(), semaphore, nullptr); |
| m_errorMonitor->SetUnexpectedError("If fence is not VK_NULL_HANDLE, fence must be a valid VkFence handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Fence obj"); |
| vk::DestroyFence(device(), fence, nullptr); |
| m_errorMonitor->SetUnexpectedError("If event is not VK_NULL_HANDLE, event must be a valid VkEvent handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Event obj"); |
| vk::DestroyEvent(device(), event, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, PipelineInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use pipeline."); |
| |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| const vkt::PipelineLayout pipeline_layout(*m_device); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyPipeline-pipeline-00765"); |
| // Create PSO to be used for draw-time errors below |
| |
| // Store pipeline handle so we can actually delete it before test finishes |
| VkPipeline delete_this_pipeline; |
| { // Scope pipeline so it will be auto-deleted |
| CreatePipelineHelper pipe(*this); |
| pipe.CreateGraphicsPipeline(); |
| |
| delete_this_pipeline = pipe; |
| |
| m_command_buffer.Begin(); |
| // Bind pipeline to cmd buffer |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); |
| |
| m_command_buffer.End(); |
| |
| // Submit cmd buffer and then pipeline destroyed while in-flight |
| m_default_queue->Submit(m_command_buffer); |
| } // Pipeline deletion triggered here |
| m_errorMonitor->VerifyFound(); |
| // Make sure queue finished and then actually delete pipeline |
| m_default_queue->Wait(); |
| m_errorMonitor->SetUnexpectedError("If pipeline is not VK_NULL_HANDLE, pipeline must be a valid VkPipeline handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Pipeline obj"); |
| vk::DestroyPipeline(*m_device, delete_this_pipeline, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, ImageViewInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use imageView."); |
| |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| VkSamplerCreateInfo sampler_ci = SafeSaneSamplerCreateInfo(); |
| VkSampler sampler; |
| |
| VkResult err; |
| err = vk::CreateSampler(device(), &sampler_ci, NULL, &sampler); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); |
| image.SetLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); |
| |
| vkt::ImageView view = image.CreateView(); |
| |
| // Create PSO to use the sampler |
| VkShaderObj fs(*m_device, kFragmentSamplerGlsl, VK_SHADER_STAGE_FRAGMENT_BIT); |
| |
| CreatePipelineHelper pipe(*this); |
| pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()}; |
| pipe.dsl_bindings_ = { |
| {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }; |
| pipe.CreateGraphicsPipeline(); |
| |
| pipe.descriptor_set_->WriteDescriptorImageInfo(0, view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); |
| pipe.descriptor_set_->UpdateDescriptorSets(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyImageView-imageView-01026"); |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| // Bind pipeline to cmd buffer |
| 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, 1, 0, 0, 0); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| |
| // Submit cmd buffer and then destroy imageView while in-flight |
| m_default_queue->Submit(m_command_buffer); |
| |
| vk::DestroyImageView(device(), view, nullptr); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| // Now we can actually destroy imageView |
| m_errorMonitor->SetUnexpectedError("If imageView is not VK_NULL_HANDLE, imageView must be a valid VkImageView handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove ImageView obj"); |
| vk::DestroySampler(device(), sampler, nullptr); |
| } |
| |
| TEST_F(NegativeObjectLifetime, BufferViewInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use bufferView."); |
| |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| vkt::Buffer buffer(*m_device, 1024, VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT); |
| VkBufferView view; |
| VkBufferViewCreateInfo bvci = vku::InitStructHelper(); |
| bvci.buffer = buffer; |
| bvci.format = VK_FORMAT_R32_SFLOAT; |
| bvci.range = VK_WHOLE_SIZE; |
| |
| VkResult err = vk::CreateBufferView(device(), &bvci, NULL, &view); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| const char *fsSource = R"glsl( |
| #version 450 |
| layout(set=0, binding=0, r32f) uniform readonly imageBuffer s; |
| layout(location=0) out vec4 x; |
| void main(){ |
| x = imageLoad(s, 0); |
| } |
| )glsl"; |
| VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT); |
| |
| CreatePipelineHelper pipe(*this); |
| pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()}; |
| pipe.dsl_bindings_ = { |
| {0, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }; |
| err = pipe.CreateGraphicsPipeline(); |
| if (err != VK_SUCCESS) { |
| GTEST_SKIP() << "Unable to compile shader"; |
| } |
| |
| pipe.descriptor_set_->WriteDescriptorBufferView(0, view, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER); |
| pipe.descriptor_set_->UpdateDescriptorSets(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyBufferView-bufferView-00936"); |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| // Bind pipeline to cmd buffer |
| 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, 1, 0, 0, 0); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| |
| // Submit cmd buffer and then destroy bufferView while in-flight |
| m_default_queue->Submit(m_command_buffer); |
| |
| vk::DestroyBufferView(device(), view, nullptr); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| // Now we can actually destroy bufferView |
| m_errorMonitor->SetUnexpectedError("If bufferView is not VK_NULL_HANDLE, bufferView must be a valid VkBufferView handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove BufferView obj"); |
| vk::DestroyBufferView(device(), view, NULL); |
| } |
| |
| TEST_F(NegativeObjectLifetime, SamplerInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use sampler."); |
| |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| VkSamplerCreateInfo sampler_ci = SafeSaneSamplerCreateInfo(); |
| VkSampler sampler; |
| vk::CreateSampler(device(), &sampler_ci, NULL, &sampler); |
| |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); |
| image.SetLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); |
| |
| vkt::ImageView view = image.CreateView(); |
| |
| // Create PSO to use the sampler |
| VkShaderObj fs(*m_device, kFragmentSamplerGlsl, VK_SHADER_STAGE_FRAGMENT_BIT); |
| |
| CreatePipelineHelper pipe(*this); |
| pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()}; |
| pipe.dsl_bindings_ = { |
| {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }; |
| pipe.CreateGraphicsPipeline(); |
| |
| pipe.descriptor_set_->WriteDescriptorImageInfo(0, view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); |
| pipe.descriptor_set_->UpdateDescriptorSets(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroySampler-sampler-01082"); |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| // Bind pipeline to cmd buffer |
| 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, 1, 0, 0, 0); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| |
| // Submit cmd buffer and then destroy sampler while in-flight |
| m_default_queue->Submit(m_command_buffer); |
| |
| vk::DestroySampler(device(), sampler, nullptr); // Destroyed too soon |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| |
| // Now we can actually destroy sampler |
| m_errorMonitor->SetUnexpectedError("If sampler is not VK_NULL_HANDLE, sampler must be a valid VkSampler handle"); |
| m_errorMonitor->SetUnexpectedError("Unable to remove Sampler obj"); |
| vk::DestroySampler(device(), sampler, NULL); // Destroyed for real |
| } |
| |
| TEST_F(NegativeObjectLifetime, ImmutableSamplerInUseDestroyed) { |
| TEST_DESCRIPTION("Delete in-use immutable sampler."); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo()); |
| |
| const VkDescriptorSetLayoutBinding binding = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, |
| &sampler.handle()}; |
| vkt::DescriptorSetLayout set_layout(*m_device, binding); |
| vkt::PipelineLayout pipeline_layout(*m_device, {&set_layout}); |
| |
| sampler.Destroy(); |
| |
| CreateComputePipelineHelper pipe(*this); |
| pipe.cp_ci_.layout = pipeline_layout; |
| m_errorMonitor->SetDesiredError("VUID-VkComputePipelineCreateInfo-layout-parameter"); |
| pipe.CreateComputePipeline(); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, ImmutableSamplerInUseDestroyed2) { |
| TEST_DESCRIPTION("Delete in-use immutable sampler."); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo()); |
| |
| OneOffDescriptorSet descriptor_set( |
| m_device, { |
| {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT, &sampler.handle()}, |
| }); |
| |
| CreateComputePipelineHelper pipe(*this); |
| pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&descriptor_set.layout_}); |
| pipe.CreateComputePipeline(); |
| |
| sampler.Destroy(); |
| |
| m_command_buffer.Begin(); |
| m_errorMonitor->SetDesiredError("VUID-vkCmdBindPipeline-pipeline-parameter"); |
| vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); |
| m_errorMonitor->VerifyFound(); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, CmdBufferEventDestroyed) { |
| TEST_DESCRIPTION("Attempt to draw with a command buffer that is invalid due to an event dependency being destroyed."); |
| RETURN_IF_SKIP(Init()); |
| |
| VkEvent event; |
| VkEventCreateInfo evci = vku::InitStructHelper(); |
| VkResult result = vk::CreateEvent(device(), &evci, NULL, &event); |
| ASSERT_EQ(VK_SUCCESS, result); |
| |
| m_command_buffer.Begin(); |
| vk::CmdSetEvent(m_command_buffer, event, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); |
| m_command_buffer.End(); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkQueueSubmit-pCommandBuffers-00070"); |
| // Destroy event dependency prior to submit to cause ERROR |
| vk::DestroyEvent(device(), event, NULL); |
| |
| m_default_queue->Submit(m_command_buffer); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, ImportFdSemaphoreInUse) { |
| TEST_DESCRIPTION("Import semaphore when semaphore is in use."); |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_BINARY, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through fd handle"; |
| } |
| |
| // Create semaphore and export its fd handle |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(); |
| export_info.handleTypes = handle_type; |
| VkSemaphoreCreateInfo create_info = vku::InitStructHelper(&export_info); |
| vkt::Semaphore export_semaphore(*m_device, create_info); |
| int fd = -1; |
| ASSERT_EQ(VK_SUCCESS, export_semaphore.ExportHandle(fd, handle_type)); |
| |
| // Create a new semaphore and put it to work |
| vkt::Semaphore semaphore(*m_device); |
| VkSubmitInfo submit_info = vku::InitStructHelper(); |
| submit_info.signalSemaphoreCount = 1; |
| submit_info.pSignalSemaphores = &semaphore.handle(); |
| ASSERT_EQ(VK_SUCCESS, vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, VK_NULL_HANDLE)); |
| |
| // Try to import fd handle while semaphore is still in use |
| m_errorMonitor->SetDesiredError("VUID-vkImportSemaphoreFdKHR-semaphore-01142"); |
| semaphore.ImportHandle(fd, handle_type); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| } |
| |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| TEST_F(NegativeObjectLifetime, ImportWin32SemaphoreInUse) { |
| TEST_DESCRIPTION("Import semaphore when semaphore is in use."); |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_BINARY, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through Win32 handle"; |
| } |
| |
| // Create semaphore and export its Win32 handle |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(); |
| export_info.handleTypes = handle_type; |
| VkSemaphoreCreateInfo create_info = vku::InitStructHelper(&export_info); |
| vkt::Semaphore export_semaphore(*m_device, create_info); |
| HANDLE handle = NULL; |
| ASSERT_EQ(VK_SUCCESS, export_semaphore.ExportHandle(handle, handle_type)); |
| |
| // Create a new semaphore and put it to work |
| vkt::Semaphore semaphore(*m_device); |
| VkSubmitInfo submit_info = vku::InitStructHelper(); |
| submit_info.signalSemaphoreCount = 1; |
| submit_info.pSignalSemaphores = &semaphore.handle(); |
| ASSERT_EQ(VK_SUCCESS, vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, VK_NULL_HANDLE)); |
| |
| // Try to import Win32 handle while semaphore is still in use |
| // Waiting for: https://gitlab.khronos.org/vulkan/vulkan/-/issues/3507 |
| m_errorMonitor->SetDesiredError("VUID_Undefined"); |
| semaphore.ImportHandle(handle, handle_type); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| } |
| #endif |
| |
| TEST_F(NegativeObjectLifetime, LeakAnObject) { |
| TEST_DESCRIPTION("Create a fence and destroy its device without first destroying the fence."); |
| |
| RETURN_IF_SKIP(InitFramework()); |
| if (!IsPlatformMockICD()) { |
| // This test leaks a fence (on purpose) and should not be run on a real driver |
| GTEST_SKIP() << "This test only runs on the mock ICD"; |
| } |
| |
| // Workaround for overzealous layers checking even the guaranteed 0th queue family |
| const auto q_props = vkt::PhysicalDevice(Gpu()).queue_properties_; |
| ASSERT_TRUE(q_props.size() > 0); |
| ASSERT_TRUE(q_props[0].queueCount > 0); |
| |
| const float q_priority[] = {1.0f}; |
| VkDeviceQueueCreateInfo queue_ci = vku::InitStructHelper(); |
| queue_ci.queueFamilyIndex = 0; |
| queue_ci.queueCount = 1; |
| queue_ci.pQueuePriorities = q_priority; |
| |
| VkDeviceCreateInfo device_ci = vku::InitStructHelper(); |
| device_ci.queueCreateInfoCount = 1; |
| device_ci.pQueueCreateInfos = &queue_ci; |
| |
| VkDevice leaky_device; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateDevice(Gpu(), &device_ci, nullptr, &leaky_device)); |
| |
| const VkFenceCreateInfo fence_ci = vku::InitStructHelper(); |
| VkFence leaked_fence; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateFence(leaky_device, &fence_ci, nullptr, &leaked_fence)); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyDevice-device-05137"); |
| vk::DestroyDevice(leaky_device, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| // There's no way we can destroy the fence at this point. Even though DestroyDevice failed, the loader has already removed |
| // references to the device |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyDevice-device-05137"); |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyInstance-instance-00629"); |
| } |
| |
| TEST_F(NegativeObjectLifetime, LeakABuffer) { |
| TEST_DESCRIPTION("Create a fence and destroy its device without first destroying the buffer."); |
| |
| RETURN_IF_SKIP(InitFramework()); |
| if (!IsPlatformMockICD()) { |
| // This test leaks a buffer (on purpose) and should not be run on a real driver |
| GTEST_SKIP() << "This test only runs on the mock ICD"; |
| } |
| |
| // Workaround for overzealous layers checking even the guaranteed 0th queue family |
| const auto q_props = vkt::PhysicalDevice(Gpu()).queue_properties_; |
| ASSERT_TRUE(q_props.size() > 0); |
| ASSERT_TRUE(q_props[0].queueCount > 0); |
| |
| auto features = vkt::PhysicalDevice(Gpu()).Features(); |
| if (!features.sparseBinding) { |
| GTEST_SKIP() << "Test requires unsupported sparseBinding feature"; |
| } |
| |
| const float q_priority[] = {1.0f}; |
| VkDeviceQueueCreateInfo queue_ci = vku::InitStructHelper(); |
| queue_ci.queueFamilyIndex = 0; |
| queue_ci.queueCount = 1; |
| queue_ci.pQueuePriorities = q_priority; |
| |
| VkDeviceCreateInfo device_ci = vku::InitStructHelper(); |
| device_ci.queueCreateInfoCount = 1; |
| device_ci.pQueueCreateInfos = &queue_ci; |
| device_ci.pEnabledFeatures = &features; |
| |
| VkDevice leaky_device; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateDevice(Gpu(), &device_ci, nullptr, &leaky_device)); |
| |
| VkBufferCreateInfo buffer_create_info = vku::InitStructHelper(); |
| buffer_create_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; |
| buffer_create_info.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT; |
| buffer_create_info.size = 1; |
| |
| VkBuffer buffer{}; |
| ASSERT_EQ(VK_SUCCESS, vk::CreateBuffer(leaky_device, &buffer_create_info, nullptr, &buffer)); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkDestroyDevice-device-05137"); |
| vk::DestroyDevice(leaky_device, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| // There's no way we can destroy the buffer at this point. |
| // Even though DestroyDevice failed, the loader has already removed references to the device |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyDevice-device-05137"); |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyInstance-instance-00629"); |
| } |
| |
| TEST_F(NegativeObjectLifetime, LeakImageAndSampler) { |
| RETURN_IF_SKIP(Init()); |
| |
| VkSamplerCreateInfo sampler_ci = SafeSaneSamplerCreateInfo(); |
| VkSampler sampler; |
| vk::CreateSampler(device(), &sampler_ci, nullptr, &sampler); |
| |
| VkImageCreateInfo image_ci = |
| vkt::Image::ImageCreateInfo2D(32, 32, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| VkImage image; |
| vk::CreateImage(device(), &image_ci, nullptr, &image); |
| |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyDevice-device-05137"); |
| m_errorMonitor->SetUnexpectedError("VUID-vkDestroyDevice-device-05137"); |
| } |
| |
| TEST_F(NegativeObjectLifetime, FreeCommandBuffersNull) { |
| TEST_DESCRIPTION("Can pass NULL for vkFreeCommandBuffers"); |
| RETURN_IF_SKIP(Init()); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkFreeCommandBuffers-pCommandBuffers-00048"); |
| vk::FreeCommandBuffers(device(), m_command_pool, 2, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| VkCommandBuffer invalid_cb = CastToHandle<VkCommandBuffer, uintptr_t>(0xbaadbeef); |
| m_errorMonitor->SetDesiredError("VUID-vkFreeCommandBuffers-pCommandBuffers-00048"); |
| vk::FreeCommandBuffers(device(), m_command_pool, 1, &invalid_cb); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, FreeDescriptorSetsNull) { |
| TEST_DESCRIPTION("Can pass NULL for vkFreeDescriptorSets"); |
| RETURN_IF_SKIP(Init()); |
| |
| VkDescriptorPoolSize ds_type_count = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1}; |
| VkDescriptorPoolCreateInfo ds_pool_ci = vku::InitStructHelper(); |
| ds_pool_ci.maxSets = 1; |
| ds_pool_ci.poolSizeCount = 1; |
| ds_pool_ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; |
| ds_pool_ci.pPoolSizes = &ds_type_count; |
| vkt::DescriptorPool ds_pool(*m_device, ds_pool_ci); |
| |
| m_errorMonitor->SetDesiredError("VUID-vkFreeDescriptorSets-pDescriptorSets-00310"); |
| vk::FreeDescriptorSets(device(), ds_pool, 2, nullptr); |
| m_errorMonitor->VerifyFound(); |
| |
| VkDescriptorSet invalid_set = CastToHandle<VkDescriptorSet, uintptr_t>(0xbaadbeef); |
| m_errorMonitor->SetDesiredError("VUID-vkFreeDescriptorSets-pDescriptorSets-00310"); |
| vk::FreeDescriptorSets(device(), ds_pool, 1, &invalid_set); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DescriptorBufferInfoUpdate) { |
| TEST_DESCRIPTION("Destroy a buffer then try to update it in the descriptor set"); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); |
| VkBuffer invalid_buffer = CastToHandle<VkBuffer, uintptr_t>(0xbaadbeef); |
| |
| OneOffDescriptorSet descriptor_set(m_device, { |
| {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| {1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}, |
| }); |
| descriptor_set.WriteDescriptorBufferInfo(0, buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); |
| descriptor_set.WriteDescriptorBufferInfo(1, invalid_buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); |
| |
| buffer.Destroy(); |
| m_errorMonitor->SetDesiredError("VUID-VkDescriptorBufferInfo-buffer-parameter"); // destroyed |
| m_errorMonitor->SetDesiredError("VUID-VkDescriptorBufferInfo-buffer-parameter"); // invalid |
| descriptor_set.UpdateDescriptorSets(); |
| m_errorMonitor->VerifyFound(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DestroyFenceInUseErrorMessage) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8745 |
| TEST_DESCRIPTION("Test that error message identifies a queue where the fence is used"); |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence fence(*m_device); |
| m_default_queue->Submit(vkt::no_cmd, fence); |
| // The issue was that error message reported that fence was used not by the queue but by the fence itself |
| m_errorMonitor->SetDesiredErrorRegex("VUID-vkDestroyFence-fence-01120", "in use by VkQueue"); |
| vk::DestroyFence(*m_device, fence, nullptr); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DestroySemaphoreInUseErrorMessage) { |
| TEST_DESCRIPTION("Test that error message identifies a queue where the semaphore is used"); |
| RETURN_IF_SKIP(Init()); |
| vkt::Semaphore semaphore(*m_device); |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(semaphore)); |
| m_errorMonitor->SetDesiredErrorRegex("VUID-vkDestroySemaphore-semaphore-05149", "in use by VkQueue"); |
| vk::DestroySemaphore(*m_device, semaphore, nullptr); |
| m_errorMonitor->VerifyFound(); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(NegativeObjectLifetime, DestroyedImageInImageView) { |
| RETURN_IF_SKIP(Init()); |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); |
| VkImageViewCreateInfo view_ci = image.BasicViewCreatInfo(VK_IMAGE_ASPECT_COLOR_BIT); |
| image.Memory().Destroy(); |
| m_errorMonitor->SetDesiredError("VUID-VkImageViewCreateInfo-image-01020"); |
| vkt::ImageView view(*m_device, view_ci); |
| m_errorMonitor->VerifyFound(); |
| } |