tests: GH422 Addition of Buffer Validation Tests
Add classes to support vertex buffer creation and binding
to the pipe and command buffer. Create VertexBufferInvalid
Test to verify:
1) INVALID_BUFFER
2) DOUBLE_DELETE
3) INVALID_BUFFER_MEMORY_OFFSET
4) INVALID_TEXEL_BUFFER_OFFSET
5) INVALID_STORAGE_BUFFER_OFFSET
6) INVALID_UNIFORM_BUFFER_OFFSET
7) INVALID_MEM_OBJ
Change-Id: Ife8e74f0670611a31690bc4cb8182bc09d155cfa
diff --git a/tests/layer_validation_tests.cpp b/tests/layer_validation_tests.cpp
index 030e578..340de76 100644
--- a/tests/layer_validation_tests.cpp
+++ b/tests/layer_validation_tests.cpp
@@ -252,7 +252,8 @@
m_commandBuffer->DrawIndexed(indexCount, instanceCount, firstIndex,
vertexOffset, firstInstance);
}
- void QueueCommandBuffer() { m_commandBuffer->QueueCommandBuffer(); }
+ void QueueCommandBuffer(bool checkSuccess = true) {
+ m_commandBuffer->QueueCommandBuffer(checkSuccess); }
void QueueCommandBuffer(const VkFence &fence) {
m_commandBuffer->QueueCommandBuffer(fence);
}
@@ -557,6 +558,268 @@
}
};
+class VkBufferTest {
+public:
+ enum eTestEnFlags {
+ eDoubleDelete,
+ eInvalidDeviceOffset,
+ eInvalidMemoryOffset,
+ eBindNullBuffer,
+ eFreeInvalidHandle,
+ };
+
+ enum eTestConditions {
+ eOffsetAlignment = 1
+ };
+
+ static bool GetTestConditionValid(VkDeviceObj *aVulkanDevice,
+ eTestEnFlags aTestFlag,
+ VkBufferUsageFlags aBufferUsage = 0) {
+ if (eInvalidDeviceOffset != aTestFlag &&
+ eInvalidMemoryOffset != aTestFlag) {
+ return true;
+ }
+ VkDeviceSize offset_limit = 0;
+ if (eInvalidMemoryOffset == aTestFlag) {
+ VkBuffer vulkanBuffer;
+ VkBufferCreateInfo buffer_create_info = {};
+ buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ buffer_create_info.size = 32;
+ buffer_create_info.usage = aBufferUsage;
+
+ vkCreateBuffer(aVulkanDevice->device(), &buffer_create_info, nullptr,
+ &vulkanBuffer);
+ VkMemoryRequirements memory_reqs = {0};
+
+ vkGetBufferMemoryRequirements(aVulkanDevice->device(),
+ vulkanBuffer, &memory_reqs);
+ vkDestroyBuffer(aVulkanDevice->device(), vulkanBuffer, nullptr);
+ offset_limit = memory_reqs.alignment;
+ }
+ else if ((VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
+ VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT) & aBufferUsage) {
+ offset_limit =
+ aVulkanDevice->props.limits.minTexelBufferOffsetAlignment;
+ } else if (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT & aBufferUsage) {
+ offset_limit =
+ aVulkanDevice->props.limits.minUniformBufferOffsetAlignment;
+ } else if (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT & aBufferUsage) {
+ offset_limit =
+ aVulkanDevice->props.limits.minStorageBufferOffsetAlignment;
+ }
+ if (eOffsetAlignment < offset_limit) {
+ return true;
+ }
+ return false;
+ }
+
+ // A constructor which performs validation tests within construction.
+ VkBufferTest(VkDeviceObj *aVulkanDevice,
+ VkBufferUsageFlags aBufferUsage,
+ eTestEnFlags aTestFlag)
+ : AllocateCurrent(false), BoundCurrent(false),
+ CreateCurrent(false), VulkanDevice(aVulkanDevice->device()) {
+
+ if (eBindNullBuffer == aTestFlag) {
+ VulkanMemory = 0;
+ vkBindBufferMemory(VulkanDevice, VulkanBuffer, VulkanMemory, 0);
+ } else {
+ VkBufferCreateInfo buffer_create_info = {};
+ buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ buffer_create_info.size = 32;
+ buffer_create_info.usage = aBufferUsage;
+
+ vkCreateBuffer(VulkanDevice, &buffer_create_info, nullptr,
+ &VulkanBuffer);
+
+ CreateCurrent = true;
+
+ VkMemoryRequirements memory_requirements;
+ vkGetBufferMemoryRequirements(VulkanDevice, VulkanBuffer,
+ &memory_requirements);
+
+ VkMemoryAllocateInfo memory_allocate_info = {};
+ memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ memory_allocate_info.allocationSize = memory_requirements.size;
+ bool pass = aVulkanDevice->phy().
+ set_memory_type(memory_requirements.memoryTypeBits,
+ &memory_allocate_info,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+ if (!pass) {
+ vkDestroyBuffer(VulkanDevice, VulkanBuffer, nullptr);
+ return;
+ }
+
+ vkAllocateMemory(VulkanDevice, &memory_allocate_info, NULL,
+ &VulkanMemory);
+ AllocateCurrent = true;
+ // NB: 1 is intentionally an invalid offset value
+ const bool offset_en = eInvalidDeviceOffset == aTestFlag ||
+ eInvalidMemoryOffset == aTestFlag;
+ vkBindBufferMemory(VulkanDevice, VulkanBuffer, VulkanMemory,
+ offset_en ? eOffsetAlignment : 0);
+ BoundCurrent = true;
+
+ InvalidDeleteEn = (eFreeInvalidHandle == aTestFlag);
+ }
+ }
+
+ ~VkBufferTest() {
+ if (CreateCurrent) {
+ vkDestroyBuffer(VulkanDevice, VulkanBuffer, nullptr);
+ }
+ if (AllocateCurrent) {
+ if (InvalidDeleteEn) {
+ union {
+ VkDeviceMemory device_memory;
+ unsigned long long index_access;
+ } bad_index;
+
+ bad_index.device_memory = VulkanMemory;
+ bad_index.index_access++;
+
+ vkFreeMemory(VulkanDevice,
+ bad_index.device_memory,
+ nullptr);
+ }
+ vkFreeMemory(VulkanDevice, VulkanMemory, nullptr);
+ }
+ }
+
+ bool GetBufferCurrent() {
+ return AllocateCurrent && BoundCurrent && CreateCurrent;
+ }
+
+ const VkBuffer &GetBuffer() {
+ return VulkanBuffer;
+ }
+
+ void TestDoubleDestroy() {
+ // Destroy the buffer but leave the flag set, which will cause
+ // the buffer to be destroyed again in the destructor.
+ vkDestroyBuffer(VulkanDevice, VulkanBuffer, nullptr);
+ }
+
+protected:
+ bool AllocateCurrent;
+ bool BoundCurrent;
+ bool CreateCurrent;
+ bool InvalidDeleteEn;
+
+ VkBuffer VulkanBuffer;
+ VkDevice VulkanDevice;
+ VkDeviceMemory VulkanMemory;
+
+};
+
+class VkVerticesObj {
+public:
+ VkVerticesObj(VkDeviceObj *aVulkanDevice, unsigned aAttributeCount,
+ unsigned aBindingCount, unsigned aByteStride,
+ VkDeviceSize aVertexCount, const float *aVerticies)
+ : BoundCurrent(false),
+ AttributeCount(aAttributeCount),
+ BindingCount(aBindingCount),
+ BindId(BindIdGenerator),
+ PipelineVertexInputStateCreateInfo(),
+ VulkanMemoryBuffer(aVulkanDevice, 1,
+ static_cast<int>(aByteStride * aVertexCount),
+ reinterpret_cast<const void *>(aVerticies),
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
+ BindIdGenerator++; // NB: This can wrap w/misuse
+
+ VertexInputAttributeDescription =
+ new VkVertexInputAttributeDescription[AttributeCount];
+ VertexInputBindingDescription =
+ new VkVertexInputBindingDescription[BindingCount];
+
+ PipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions =
+ VertexInputAttributeDescription;
+ PipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount =
+ AttributeCount;
+ PipelineVertexInputStateCreateInfo.pVertexBindingDescriptions =
+ VertexInputBindingDescription;
+ PipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount =
+ BindingCount;
+ PipelineVertexInputStateCreateInfo.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+ unsigned i = 0;
+ do {
+ VertexInputAttributeDescription[i].binding = BindId;
+ VertexInputAttributeDescription[i].location = i;
+ VertexInputAttributeDescription[i].format =
+ VK_FORMAT_R32G32B32_SFLOAT;
+ VertexInputAttributeDescription[i].offset =
+ sizeof(float) * aByteStride;
+ i++;
+ } while (AttributeCount < i);
+
+ i = 0;
+ do {
+ VertexInputBindingDescription[i].binding = BindId;
+ VertexInputBindingDescription[i].stride = aByteStride;
+ VertexInputBindingDescription[i].inputRate =
+ VK_VERTEX_INPUT_RATE_VERTEX;
+ i++;
+ } while (BindingCount < i);
+ }
+
+ ~VkVerticesObj() {
+ if (VertexInputAttributeDescription) {
+ delete[] VertexInputAttributeDescription;
+ }
+ if (VertexInputBindingDescription) {
+ delete[] VertexInputBindingDescription;
+ }
+ }
+
+ bool AddVertexInputToPipe(VkPipelineObj &aPipelineObj) {
+ aPipelineObj.AddVertexInputAttribs(VertexInputAttributeDescription,
+ AttributeCount);
+ aPipelineObj.AddVertexInputBindings(VertexInputBindingDescription,
+ BindingCount);
+ return true;
+ }
+
+ void BindVertexBuffers(VkCommandBuffer aCommandBuffer,
+ unsigned aOffsetCount = 0,
+ VkDeviceSize *aOffsetList = nullptr) {
+ VkDeviceSize *offsetList;
+ unsigned offsetCount;
+
+ if (aOffsetCount) {
+ offsetList = aOffsetList;
+ offsetCount = aOffsetCount;
+ } else {
+ offsetList = new VkDeviceSize[1]();
+ offsetCount = 1;
+ }
+
+ vkCmdBindVertexBuffers(aCommandBuffer, BindId, offsetCount,
+ &VulkanMemoryBuffer.handle(), offsetList);
+ BoundCurrent = true;
+
+ if (!aOffsetCount) {
+ delete [] offsetList;
+ }
+ }
+
+protected:
+ static uint32_t BindIdGenerator;
+
+ bool BoundCurrent;
+ unsigned AttributeCount;
+ unsigned BindingCount;
+ uint32_t BindId;
+
+ VkPipelineVertexInputStateCreateInfo PipelineVertexInputStateCreateInfo;
+ VkVertexInputAttributeDescription *VertexInputAttributeDescription;
+ VkVertexInputBindingDescription *VertexInputBindingDescription;
+ VkConstantBufferObj VulkanMemoryBuffer;
+};
+
+uint32_t VkVerticesObj::BindIdGenerator;
// ********************************************************************************************************************
// ********************************************************************************************************************
// ********************************************************************************************************************
@@ -10862,6 +11125,180 @@
vkDestroyDescriptorSetLayout(m_device->device(), ds_layout, NULL);
vkDestroyDescriptorPool(m_device->device(), ds_pool, NULL);
}
+
+TEST_F(VkLayerTest, VertexBufferInvalid) {
+ TEST_DESCRIPTION("Submit a command buffer using deleted vertex buffer, "
+ "delete a buffer twice, use an invalid offset for each "
+ "buffer type, and attempt to bind a null buffer");
+
+ const char *deleted_buffer_in_command_buffer = "Cannot submit cmd buffer "
+ "using deleted buffer ";
+ const char *double_destroy_message = "Cannot free buffer 0x";
+ const char *invalid_offset_message = "vkBindBufferMemory(): "
+ "memoryOffset is 0x";
+ const char *invalid_storage_buffer_offset_message = "vkBindBufferMemory(): "
+ "storage memoryOffset "
+ "is 0x";
+ const char *invalid_texel_buffer_offset_message = "vkBindBufferMemory(): "
+ "texel memoryOffset "
+ "is 0x";
+ const char *invalid_uniform_buffer_offset_message = "vkBindBufferMemory(): "
+ "uniform memoryOffset "
+ "is 0x";
+ const char *bind_null_buffer_message = "In vkBindBufferMemory, attempting"
+ " to Bind Obj(0x";
+ const char *free_invalid_buffer_message = "Request to delete memory "
+ "object 0x";
+
+ ASSERT_NO_FATAL_FAILURE(InitState());
+ ASSERT_NO_FATAL_FAILURE(InitViewport());
+ ASSERT_NO_FATAL_FAILURE(InitRenderTarget());
+
+ VkPipelineMultisampleStateCreateInfo pipe_ms_state_ci = {};
+ pipe_ms_state_ci.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ pipe_ms_state_ci.pNext = NULL;
+ pipe_ms_state_ci.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+ pipe_ms_state_ci.sampleShadingEnable = 0;
+ pipe_ms_state_ci.minSampleShading = 1.0;
+ pipe_ms_state_ci.pSampleMask = nullptr;
+
+ VkPipelineLayoutCreateInfo pipeline_layout_ci = {};
+ pipeline_layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ VkPipelineLayout pipeline_layout;
+
+ VkResult err = vkCreatePipelineLayout(m_device->device(),
+ &pipeline_layout_ci, nullptr,
+ &pipeline_layout);
+ ASSERT_VK_SUCCESS(err);
+
+ VkShaderObj vs(m_device, bindStateVertShaderText,
+ VK_SHADER_STAGE_VERTEX_BIT, this);
+ VkShaderObj fs(m_device, bindStateFragShaderText,
+ VK_SHADER_STAGE_FRAGMENT_BIT,
+ this);
+ VkPipelineObj pipe(m_device);
+ pipe.AddShader(&vs);
+ pipe.AddShader(&fs);
+ pipe.AddColorAttachment();
+ pipe.SetMSAA(&pipe_ms_state_ci);
+ pipe.SetViewport(m_viewports);
+ pipe.SetScissor(m_scissors);
+ pipe.CreateVKPipeline(pipeline_layout, renderPass());
+
+ BeginCommandBuffer();
+ vkCmdBindPipeline(m_commandBuffer->GetBufferHandle(),
+ VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.handle());
+
+ {
+ // Create and bind a vertex buffer in a reduced scope, which will cause
+ // it to be deleted upon leaving this scope
+ const float vbo_data[3] = {1.f, 0.f, 1.f};
+ VkVerticesObj draw_verticies(m_device, 1, 1, sizeof(vbo_data),
+ 3, vbo_data);
+ draw_verticies.BindVertexBuffers(m_commandBuffer->handle());
+ draw_verticies.AddVertexInputToPipe(pipe);
+ }
+
+ Draw(1, 0, 0, 0);
+
+ EndCommandBuffer();
+
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ deleted_buffer_in_command_buffer);
+ QueueCommandBuffer(false);
+ m_errorMonitor->VerifyFound();
+
+ {
+ // Create and bind a vertex buffer in a reduced scope, and delete it
+ // twice, the second through the destructor
+ VkBufferTest buffer_test(m_device, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ VkBufferTest::eDoubleDelete);
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ double_destroy_message);
+ buffer_test.TestDoubleDestroy();
+ }
+ m_errorMonitor->VerifyFound();
+
+ if (VkBufferTest::
+ GetTestConditionValid(m_device,
+ VkBufferTest::eInvalidMemoryOffset)) {
+ // Create and bind a memory buffer with an invalid offset.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ invalid_offset_message);
+ VkBufferTest buffer_test(m_device,
+ VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT,
+ VkBufferTest::eInvalidMemoryOffset);
+ (void) buffer_test;
+ m_errorMonitor->VerifyFound();
+ }
+
+ if (VkBufferTest::
+ GetTestConditionValid(m_device,
+ VkBufferTest::eInvalidDeviceOffset,
+ VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) {
+ // Create and bind a memory buffer with an invalid offset again,
+ // but look for a texel buffer message.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ invalid_texel_buffer_offset_message);
+ VkBufferTest buffer_test(m_device,
+ VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT,
+ VkBufferTest::eInvalidDeviceOffset);
+ (void) buffer_test;
+ m_errorMonitor->VerifyFound();
+ }
+
+ if (VkBufferTest::
+ GetTestConditionValid(m_device,
+ VkBufferTest::eInvalidDeviceOffset,
+ VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)) {
+ // Create and bind a memory buffer with an invalid offset again, but
+ // look for a uniform buffer message.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ invalid_uniform_buffer_offset_message);
+ VkBufferTest buffer_test(m_device, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+ VkBufferTest::eInvalidDeviceOffset);
+ (void) buffer_test;
+ m_errorMonitor->VerifyFound();
+ }
+
+ if (VkBufferTest::
+ GetTestConditionValid(m_device,
+ VkBufferTest::eInvalidDeviceOffset,
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)) {
+ // Create and bind a memory buffer with an invalid offset again, but
+ // look for a storage buffer message.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ invalid_storage_buffer_offset_message);
+ VkBufferTest buffer_test(m_device, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ VkBufferTest::eInvalidDeviceOffset);
+ (void) buffer_test;
+ m_errorMonitor->VerifyFound();
+ }
+
+ {
+ // Attempt to bind a null buffer.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ bind_null_buffer_message);
+ VkBufferTest buffer_test(m_device, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ VkBufferTest::eBindNullBuffer);
+ (void) buffer_test;
+ m_errorMonitor->VerifyFound();
+ }
+
+ {
+ // Attempt to use an invalid handle to delete a buffer.
+ m_errorMonitor->SetDesiredFailureMsg(VK_DEBUG_REPORT_ERROR_BIT_EXT,
+ free_invalid_buffer_message);
+ VkBufferTest buffer_test(m_device, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ VkBufferTest::eFreeInvalidHandle);
+ (void) buffer_test;
+ }
+ m_errorMonitor->VerifyFound();
+
+ vkDestroyPipelineLayout(m_device->device(), pipeline_layout, NULL);
+}
+
// INVALID_IMAGE_LAYOUT tests (one other case is hit by MapMemWithoutHostVisibleBit and not here)
TEST_F(VkLayerTest, InvalidImageLayout) {
TEST_DESCRIPTION("Hit all possible validation checks associated with the "