tests: Add a unit test for a simple memcpy compute shader Bug: b/126871859 Change-Id: I0b3db6c033419a2ad54453d470960330d4f337cc Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/25909 Presubmit-Ready: Ben Clayton <bclayton@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Tested-by: Ben Clayton <bclayton@google.com> Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/tests/VulkanUnitTests/Device.cpp b/tests/VulkanUnitTests/Device.cpp new file mode 100644 index 0000000..1fcc8b8 --- /dev/null +++ b/tests/VulkanUnitTests/Device.cpp
@@ -0,0 +1,397 @@ +// Copyright 2019 The SwiftShader Authors. 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Device.hpp" +#include "Driver.hpp" + +Device::Device() + : driver(nullptr), + device(nullptr), + physicalDevice(nullptr), + queueFamilyIndex(0) {} + +Device::Device( + Driver const *driver, VkDevice device, VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex) + : driver(driver), + device(device), + physicalDevice(physicalDevice), + queueFamilyIndex(queueFamilyIndex) {} + +bool Device::IsValid() const { return device != nullptr; } + +VkResult Device::CreateComputeDevice( + Driver const *driver, VkInstance instance, Device *out) +{ + VkResult result; + + // Gather all physical devices + std::vector<VkPhysicalDevice> physicalDevices; + result = GetPhysicalDevices(driver, instance, physicalDevices); + if (result != VK_SUCCESS) + { + return result; + } + + // Inspect each physical device's queue families for compute support. + for (auto physicalDevice : physicalDevices) + { + int queueFamilyIndex = GetComputeQueueFamilyIndex(driver, physicalDevice); + if (queueFamilyIndex < 0) + { + continue; + } + + const float queuePrioritory = 1.0f; + const VkDeviceQueueCreateInfo deviceQueueCreateInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + (uint32_t)queueFamilyIndex, // queueFamilyIndex + 1, // queueCount + &queuePrioritory, // pQueuePriorities + }; + + const VkDeviceCreateInfo deviceCreateInfo = { + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + 1, // queueCreateInfoCount + &deviceQueueCreateInfo, // pQueueCreateInfos + 0, // enabledLayerCount + nullptr, // ppEnabledLayerNames + 0, // enabledExtensionCount + nullptr, // ppEnabledExtensionNames + nullptr, // pEnabledFeatures + }; + + VkDevice device; + result = driver->vkCreateDevice(physicalDevice, &deviceCreateInfo, 0, &device); + if (result != VK_SUCCESS) + { + return result; + } + + *out = Device(driver, device, physicalDevice, static_cast<uint32_t>(queueFamilyIndex)); + return VK_SUCCESS; + } + + return VK_SUCCESS; +} + +int Device::GetComputeQueueFamilyIndex( + Driver const *driver, VkPhysicalDevice device) +{ + auto properties = GetPhysicalDeviceQueueFamilyProperties(driver, device); + for (uint32_t i = 0; i < properties.size(); i++) + { + if ((properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0) + { + return static_cast<int>(i); + } + } + return -1; +} + +std::vector<VkQueueFamilyProperties> + Device::GetPhysicalDeviceQueueFamilyProperties( + Driver const *driver, VkPhysicalDevice device) +{ + std::vector<VkQueueFamilyProperties> out; + uint32_t count = 0; + driver->vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr); + out.resize(count); + driver->vkGetPhysicalDeviceQueueFamilyProperties(device, &count, out.data()); + return out; +} + +VkResult Device::GetPhysicalDevices( + const Driver* driver, VkInstance instance, + std::vector<VkPhysicalDevice>& out) +{ + uint32_t count = 0; + VkResult result = driver->vkEnumeratePhysicalDevices(instance, &count, 0); + if (result != VK_SUCCESS) + { + return result; + } + out.resize(count); + return driver->vkEnumeratePhysicalDevices(instance, &count, out.data()); +} + +VkResult Device::CreateStorageBuffer( + VkDeviceMemory memory, VkDeviceSize size, + VkDeviceSize offset, VkBuffer* out) const +{ + const VkBufferCreateInfo info = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + size, // size + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, // usage + VK_SHARING_MODE_EXCLUSIVE, // sharingMode + 0, // queueFamilyIndexCount + nullptr, // pQueueFamilyIndices + }; + + VkBuffer buffer; + VkResult result = driver->vkCreateBuffer(device, &info, 0, &buffer); + if (result != VK_SUCCESS) + { + return result; + } + + result = driver->vkBindBufferMemory(device, buffer, memory, offset); + if (result != VK_SUCCESS) + { + return result; + } + + *out = buffer; + return VK_SUCCESS; +} + +VkResult Device::CreateShaderModule( + const std::vector<uint32_t>& spirv, VkShaderModule* out) const +{ + VkShaderModuleCreateInfo info = { + VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + spirv.size() * 4, // codeSize + spirv.data(), // pCode + }; + return driver->vkCreateShaderModule(device, &info, 0, out); +} + +VkResult Device::CreateDescriptorSetLayout( + const std::vector<VkDescriptorSetLayoutBinding>& bindings, + VkDescriptorSetLayout* out) const +{ + VkDescriptorSetLayoutCreateInfo info = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + (uint32_t)bindings.size(), // bindingCount + bindings.data(), // pBindings + }; + + return driver->vkCreateDescriptorSetLayout(device, &info, 0, out); +} + +VkResult Device::CreatePipelineLayout( + VkDescriptorSetLayout layout, VkPipelineLayout* out) const +{ + VkPipelineLayoutCreateInfo info = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + 1, // setLayoutCount + &layout, // pSetLayouts + 0, // pushConstantRangeCount + nullptr, // pPushConstantRanges + }; + + return driver->vkCreatePipelineLayout(device, &info, 0, out); +} + +VkResult Device::CreateComputePipeline( + VkShaderModule module, VkPipelineLayout pipelineLayout, + VkPipeline* out) const +{ + VkComputePipelineCreateInfo info = { + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + { + // stage + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + VK_SHADER_STAGE_COMPUTE_BIT, // stage + module, // module + "main", // pName + nullptr, // pSpecializationInfo + }, + pipelineLayout, // layout + 0, // basePipelineHandle + 0, // basePipelineIndex + }; + + return driver->vkCreateComputePipelines(device, 0, 1, &info, 0, out); +} + +VkResult Device::CreateStorageBufferDescriptorPool(uint32_t descriptorCount, + VkDescriptorPool* out) const +{ + VkDescriptorPoolSize size = { + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // type + descriptorCount, // descriptorCount + }; + + VkDescriptorPoolCreateInfo info = { + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + 1, // maxSets + 1, // poolSizeCount + &size, // pPoolSizes + }; + + return driver->vkCreateDescriptorPool(device, &info, 0, out); +} + +VkResult Device::AllocateDescriptorSet( + VkDescriptorPool pool, VkDescriptorSetLayout layout, + VkDescriptorSet* out) const +{ + VkDescriptorSetAllocateInfo info = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, // sType + nullptr, // pNext + pool, // descriptorPool + 1, // descriptorSetCount + &layout, // pSetLayouts + }; + + return driver->vkAllocateDescriptorSets(device, &info, out); +} + +void Device::UpdateStorageBufferDescriptorSets( + VkDescriptorSet descriptorSet, + const std::vector<VkDescriptorBufferInfo>& bufferInfos) const +{ + std::vector<VkWriteDescriptorSet> writes; + writes.reserve(bufferInfos.size()); + for (uint32_t i = 0; i < bufferInfos.size(); i++) + { + writes.push_back(VkWriteDescriptorSet{ + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, // sType + nullptr, // pNext + descriptorSet, // dstSet + i, // dstBinding + 0, // dstArrayElement + 1, // descriptorCount + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType + nullptr, // pImageInfo + &bufferInfos[i], // pBufferInfo + nullptr, // pTexelBufferView + }); + } + + driver->vkUpdateDescriptorSets(device, writes.size(), writes.data(), 0, nullptr); +} + +VkResult Device::AllocateMemory(size_t size, VkMemoryPropertyFlags flags, VkDeviceMemory* out) const +{ + VkPhysicalDeviceMemoryProperties properties; + driver->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &properties); + + for(uint32_t type = 0; type < properties.memoryTypeCount; type++) + { + if ((flags & properties.memoryTypes[type].propertyFlags) == 0) + { + continue; // Type mismatch + } + + if (size > properties.memoryHeaps[properties.memoryTypes[type].heapIndex].size) + { + continue; // Too small. + } + + const VkMemoryAllocateInfo info = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType + nullptr, // pNext + size, // allocationSize + type, // memoryTypeIndex + }; + + return driver->vkAllocateMemory(device, &info, 0, out); + } + + return VK_ERROR_OUT_OF_DEVICE_MEMORY; // TODO: Change to something not made up? +} + +VkResult Device::MapMemory(VkDeviceMemory memory, VkDeviceSize offset, + VkDeviceSize size, VkMemoryMapFlags flags, void **ppData) const +{ + return driver->vkMapMemory(device, memory, offset, size, flags, ppData); +} + +void Device::UnmapMemory(VkDeviceMemory memory) const +{ + driver->vkUnmapMemory(device, memory); +} + +VkResult Device::CreateCommandPool(VkCommandPool* out) const +{ + VkCommandPoolCreateInfo info = { + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + queueFamilyIndex, // queueFamilyIndex + }; + return driver->vkCreateCommandPool(device, &info, 0, out); +} + +VkResult Device::AllocateCommandBuffer( + VkCommandPool pool, VkCommandBuffer* out) const +{ + VkCommandBufferAllocateInfo info = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType + nullptr, // pNext + pool, // commandPool + VK_COMMAND_BUFFER_LEVEL_PRIMARY, // level + 1, // commandBufferCount + }; + return driver->vkAllocateCommandBuffers(device, &info, out); +} + +VkResult Device::BeginCommandBuffer( + VkCommandBufferUsageFlagBits usage, VkCommandBuffer commandBuffer) const +{ + VkCommandBufferBeginInfo info = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, // sType + nullptr, // pNext + usage, // flags + nullptr, // pInheritanceInfo + }; + + return driver->vkBeginCommandBuffer(commandBuffer, &info); +} + +VkResult Device::QueueSubmitAndWait(VkCommandBuffer commandBuffer) const +{ + VkQueue queue; + driver->vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue); + + VkSubmitInfo info = { + VK_STRUCTURE_TYPE_SUBMIT_INFO, // sType + nullptr, // pNext + 0, // waitSemaphoreCount + nullptr, // pWaitSemaphores + nullptr, // pWaitDstStageMask + 1, // commandBufferCount + &commandBuffer, // pCommandBuffers + 0, // signalSemaphoreCount + nullptr, // pSignalSemaphores + }; + + VkResult result = driver->vkQueueSubmit(queue, 1, &info, 0); + if (result != VK_SUCCESS) + { + return result; + } + + return driver->vkQueueWaitIdle(queue); +}
diff --git a/tests/VulkanUnitTests/Device.hpp b/tests/VulkanUnitTests/Device.hpp new file mode 100644 index 0000000..a235f61 --- /dev/null +++ b/tests/VulkanUnitTests/Device.hpp
@@ -0,0 +1,128 @@ +// Copyright 2019 The SwiftShader Authors. 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <vulkan/vulkan_core.h> + +#include <vector> + +class Driver; + +// Device provides a wrapper around a VkDevice with a number of helper functions +// for common test operations. +class Device +{ +public: + Device(); + + // CreateComputeDevice enumerates the physical devices, looking for a device + // that supports compute. + // If a compatible physical device is found, then a device is created and + // assigned to out. + // If a compatible physical device is not found, VK_SUCCESS will still be + // returned (as there was no Vulkan error), but calling Device::IsValid() + // on this device will return false. + static VkResult CreateComputeDevice( + Driver const *driver, VkInstance instance, Device *out); + + // IsValid returns true if the Device is initialized and can be used. + bool IsValid() const; + + // CreateBuffer creates a new buffer with the + // VK_BUFFER_USAGE_STORAGE_BUFFER_BIT usage, and + // VK_SHARING_MODE_EXCLUSIVE sharing mode. + VkResult CreateStorageBuffer(VkDeviceMemory memory, VkDeviceSize size, + VkDeviceSize offset, VkBuffer *out) const; + + // CreateShaderModule creates a new shader module with the given SPIR-V + // code. + VkResult CreateShaderModule(const std::vector<uint32_t> &spirv, + VkShaderModule *out) const; + + // CreateDescriptorSetLayout creates a new descriptor set layout with the + // given bindings. + VkResult CreateDescriptorSetLayout( + const std::vector<VkDescriptorSetLayoutBinding> &bindings, + VkDescriptorSetLayout *out) const; + + // CreatePipelineLayout creates a new single set descriptor set layout. + VkResult CreatePipelineLayout(VkDescriptorSetLayout layout, + VkPipelineLayout *out) const; + + // CreateComputePipeline creates a new compute pipeline with the entry point + // "main". + VkResult CreateComputePipeline(VkShaderModule module, + VkPipelineLayout pipelineLayout, + VkPipeline *out) const; + + // CreateStorageBufferDescriptorPool creates a new descriptor pool that can + // hold descriptorCount storage buffers. + VkResult CreateStorageBufferDescriptorPool(uint32_t descriptorCount, + VkDescriptorPool *out) const; + + // AllocateDescriptorSet allocates a single descriptor set with the given + // layout from pool. + VkResult AllocateDescriptorSet(VkDescriptorPool pool, + VkDescriptorSetLayout layout, + VkDescriptorSet *out) const; + + // UpdateStorageBufferDescriptorSets updates the storage buffers in + // descriptorSet with the given list of VkDescriptorBufferInfos. + void UpdateStorageBufferDescriptorSets(VkDescriptorSet descriptorSet, + const std::vector<VkDescriptorBufferInfo> &bufferInfos) const; + + // AllocateMemory allocates size bytes from a memory heap that has all the + // given flag bits set. + // If memory could not be allocated from any heap then + // VK_ERROR_OUT_OF_DEVICE_MEMORY is returned. + VkResult AllocateMemory(size_t size, VkMemoryPropertyFlags flags, VkDeviceMemory* out) const; + + // MapMemory wraps vkMapMemory, supplying the first VkDevice parameter. + VkResult MapMemory(VkDeviceMemory memory, VkDeviceSize offset, + VkDeviceSize size, VkMemoryMapFlags flags, void **ppData) const; + + // UnmapMemory wraps vkUnmapMemory, supplying the first VkDevice parameter. + void UnmapMemory(VkDeviceMemory memory) const; + + // CreateCommandPool creates a new command pool. + VkResult CreateCommandPool(VkCommandPool* out) const; + + // AllocateCommandBuffer creates a new command buffer with a primary level. + VkResult AllocateCommandBuffer(VkCommandPool pool, VkCommandBuffer* out) const; + + // BeginCommandBuffer begins writing to commandBuffer. + VkResult BeginCommandBuffer(VkCommandBufferUsageFlagBits usage, VkCommandBuffer commandBuffer) const; + + // QueueSubmitAndWait submits the given command buffer and waits for it to + // complete. + VkResult QueueSubmitAndWait(VkCommandBuffer commandBuffer) const; + +private: + Device(Driver const *driver, VkDevice device, VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex); + + static VkResult GetPhysicalDevices( + Driver const *driver, VkInstance instance, + std::vector<VkPhysicalDevice> &out); + + static int GetComputeQueueFamilyIndex( + Driver const *driver, VkPhysicalDevice device); + + static std::vector<VkQueueFamilyProperties> + GetPhysicalDeviceQueueFamilyProperties( + Driver const *driver, VkPhysicalDevice device); + + Driver const *driver; + VkDevice device; + VkPhysicalDevice physicalDevice; + uint32_t queueFamilyIndex; +};
diff --git a/tests/VulkanUnitTests/VulkanUnitTests.vcxproj b/tests/VulkanUnitTests/VulkanUnitTests.vcxproj index ebef195..460c99a 100644 --- a/tests/VulkanUnitTests/VulkanUnitTests.vcxproj +++ b/tests/VulkanUnitTests/VulkanUnitTests.vcxproj
@@ -98,6 +98,7 @@ </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\..\third_party\googletest\googletest\src\gtest-all.cc" /> + <ClCompile Include="Device.cpp" /> <ClCompile Include="Driver.cpp" /> <ClCompile Include="main.cpp" /> <ClCompile Include="unittests.cpp" /> @@ -108,6 +109,7 @@ </ProjectReference> </ItemGroup> <ItemGroup> + <ClInclude Include="Device.hpp" /> <ClInclude Include="Driver.hpp" /> <ClInclude Include="VkGlobalFuncs.hpp" /> <ClInclude Include="VkInstanceFuncs.hpp" />
diff --git a/tests/VulkanUnitTests/unittests.cpp b/tests/VulkanUnitTests/unittests.cpp index be2d924..78eb489 100644 --- a/tests/VulkanUnitTests/unittests.cpp +++ b/tests/VulkanUnitTests/unittests.cpp
@@ -16,10 +16,14 @@ // the dEQP test suite. Also used as a smoke test. #include "Driver.hpp" +#include "Device.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "spirv-tools/libspirv.hpp" + +#include <sstream> #include <cstring> class SwiftShaderVulkanTest : public testing::Test @@ -92,3 +96,280 @@ EXPECT_EQ(strncmp(physicalDeviceProperties.deviceName, "SwiftShader Device", VK_MAX_PHYSICAL_DEVICE_NAME_SIZE), 0); } + +std::vector<uint32_t> compileSpirv(const char* assembly) +{ + spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0); + + core.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& p, const char* m) { + FAIL() << p.line << ":" << p.column << ": " << m; + }); + + std::vector<uint32_t> spirv; + EXPECT_TRUE(core.Assemble(assembly, &spirv)); + EXPECT_TRUE(core.Validate(spirv)); + + // Warn if the disassembly does not match the source assembly. + // We do this as debugging tests in the debugger is often made much harder + // if the SSA names (%X) in the debugger do not match the source. + std::string disassembled; + core.Disassemble(spirv, &disassembled, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + if (disassembled != assembly) + { + printf("-- WARNING: Disassembly does not match assembly: ---\n\n"); + + auto splitLines = [](const std::string& str) -> std::vector<std::string> + { + std::stringstream ss(str); + std::vector<std::string> out; + std::string line; + while (std::getline(ss, line, '\n')) { out.push_back(line); } + return out; + }; + + auto srcLines = splitLines(std::string(assembly)); + auto disLines = splitLines(disassembled); + + for (size_t line = 0; line < srcLines.size() && line < disLines.size(); line++) + { + auto srcLine = (line < srcLines.size()) ? srcLines[line] : "<missing>"; + auto disLine = (line < disLines.size()) ? disLines[line] : "<missing>"; + if (srcLine != disLine) + { + printf("%zu: '%s' != '%s'\n", line, srcLine.c_str(), disLine.c_str()); + } + } + printf("\n\n---\n"); + } + + return spirv; +} + +#define VK_ASSERT(x) ASSERT_EQ(x, VK_SUCCESS) + +struct ComputeParams +{ + int localSizeX; + int localSizeY; + int localSizeZ; +}; + +class SwiftShaderVulkanComputeTest : public testing::TestWithParam<ComputeParams> {}; + +INSTANTIATE_TEST_CASE_P(ComputeParams, SwiftShaderVulkanComputeTest, testing::Values( + ComputeParams{1, 1, 1}, + ComputeParams{2, 1, 1}, + ComputeParams{4, 1, 1}, + ComputeParams{8, 1, 1}, + ComputeParams{16, 1, 1}, + ComputeParams{32, 1, 1} +)); + +TEST_P(SwiftShaderVulkanComputeTest, Memcpy) +{ + Driver driver; + ASSERT_TRUE(driver.loadSwiftShader()); + + auto params = GetParam(); + + std::stringstream src; + src << + "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint GLCompute %1 \"main\" %2\n" + "OpExecutionMode %1 LocalSize " << + params.localSizeX << " " << + params.localSizeY << " " << + params.localSizeZ << "\n" << + "OpDecorate %3 ArrayStride 4\n" + "OpMemberDecorate %4 0 Offset 0\n" + "OpDecorate %4 BufferBlock\n" + "OpDecorate %5 DescriptorSet 0\n" + "OpDecorate %5 Binding 1\n" + "OpDecorate %2 BuiltIn GlobalInvocationId\n" + "OpDecorate %6 ArrayStride 4\n" + "OpMemberDecorate %7 0 Offset 0\n" + "OpDecorate %7 BufferBlock\n" + "OpDecorate %8 DescriptorSet 0\n" + "OpDecorate %8 Binding 0\n" + "%9 = OpTypeVoid\n" + "%10 = OpTypeFunction %9\n" + "%11 = OpTypeInt 32 1\n" + "%3 = OpTypeRuntimeArray %11\n" + "%4 = OpTypeStruct %3\n" + "%12 = OpTypePointer Uniform %4\n" + "%5 = OpVariable %12 Uniform\n" + "%13 = OpConstant %11 0\n" + "%14 = OpTypeInt 32 0\n" + "%15 = OpTypeVector %14 3\n" + "%16 = OpTypePointer Input %15\n" + "%2 = OpVariable %16 Input\n" + "%17 = OpConstant %14 0\n" + "%18 = OpTypePointer Input %14\n" + "%6 = OpTypeRuntimeArray %11\n" + "%7 = OpTypeStruct %6\n" + "%19 = OpTypePointer Uniform %7\n" + "%8 = OpVariable %19 Uniform\n" + "%20 = OpTypePointer Uniform %11\n" + "%21 = OpConstant %11 1\n" + "%1 = OpFunction %9 None %10\n" + "%22 = OpLabel\n" + "%23 = OpAccessChain %18 %2 %17\n" + "%24 = OpLoad %14 %23\n" + "%25 = OpAccessChain %20 %8 %13 %24\n" + "%26 = OpLoad %11 %25\n" + "%27 = OpAccessChain %20 %5 %13 %24\n" + "OpStore %27 %26\n" + "OpReturn\n" + "OpFunctionEnd\n"; + + auto code = compileSpirv(src.str().c_str()); + + const VkInstanceCreateInfo createInfo = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + nullptr, // pApplicationInfo + 0, // enabledLayerCount + nullptr, // ppEnabledLayerNames + 0, // enabledExtensionCount + nullptr, // ppEnabledExtensionNames + }; + + VkInstance instance = VK_NULL_HANDLE; + VK_ASSERT(driver.vkCreateInstance(&createInfo, nullptr, &instance)); + + ASSERT_TRUE(driver.resolve(instance)); + + Device device; + VK_ASSERT(Device::CreateComputeDevice(&driver, instance, &device)); + ASSERT_TRUE(device.IsValid()); + + constexpr int NUM_ELEMENTS = 256; + + struct Buffers + { + uint32_t magic0; + uint32_t in[NUM_ELEMENTS]; + uint32_t magic1; + uint32_t out[NUM_ELEMENTS]; + uint32_t magic2; + }; + + constexpr uint32_t magic0 = 0x01234567; + constexpr uint32_t magic1 = 0x89abcdef; + constexpr uint32_t magic2 = 0xfedcba99; + + VkDeviceMemory memory; + VK_ASSERT(device.AllocateMemory(sizeof(Buffers), + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &memory)); + + Buffers* buffers; + VK_ASSERT(device.MapMemory(memory, 0, sizeof(Buffers), 0, (void**)&buffers)); + + memset(buffers, 0, sizeof(Buffers)); + + buffers->magic0 = magic0; + buffers->magic1 = magic1; + buffers->magic2 = magic2; + + for(int i = 0; i < NUM_ELEMENTS; i++) + { + buffers->in[i] = (uint32_t)i; + } + + device.UnmapMemory(memory); + buffers = nullptr; + + VkBuffer bufferIn; + VK_ASSERT(device.CreateStorageBuffer(memory, sizeof(Buffers::in), offsetof(Buffers, in), &bufferIn)); + + VkBuffer bufferOut; + VK_ASSERT(device.CreateStorageBuffer(memory, sizeof(Buffers::out), offsetof(Buffers, out), &bufferOut)); + + VkShaderModule shaderModule; + VK_ASSERT(device.CreateShaderModule(code, &shaderModule)); + + std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings = + { + { + 0, // binding + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType + 1, // descriptorCount + VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags + 0, // pImmutableSamplers + }, + { + 1, // binding + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType + 1, // descriptorCount + VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags + 0, // pImmutableSamplers + } + }; + + VkDescriptorSetLayout descriptorSetLayout; + VK_ASSERT(device.CreateDescriptorSetLayout(descriptorSetLayoutBindings, &descriptorSetLayout)); + + VkPipelineLayout pipelineLayout; + VK_ASSERT(device.CreatePipelineLayout(descriptorSetLayout, &pipelineLayout)); + + VkPipeline pipeline; + VK_ASSERT(device.CreateComputePipeline(shaderModule, pipelineLayout, &pipeline)); + + VkDescriptorPool descriptorPool; + VK_ASSERT(device.CreateStorageBufferDescriptorPool(2, &descriptorPool)); + + VkDescriptorSet descriptorSet; + VK_ASSERT(device.AllocateDescriptorSet(descriptorPool, descriptorSetLayout, &descriptorSet)); + + std::vector<VkDescriptorBufferInfo> descriptorBufferInfos = + { + { + bufferIn, // buffer + 0, // offset + VK_WHOLE_SIZE, // range + }, + { + bufferOut, // buffer + 0, // offset + VK_WHOLE_SIZE, // range + } + }; + device.UpdateStorageBufferDescriptorSets(descriptorSet, descriptorBufferInfos); + + VkCommandPool commandPool; + VK_ASSERT(device.CreateCommandPool(&commandPool)); + + VkCommandBuffer commandBuffer; + VK_ASSERT(device.AllocateCommandBuffer(commandPool, &commandBuffer)); + + VK_ASSERT(device.BeginCommandBuffer(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, commandBuffer)); + + driver.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + + driver.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, + 0, nullptr); + + driver.vkCmdDispatch(commandBuffer, NUM_ELEMENTS / params.localSizeX, 1, 1); + + VK_ASSERT(driver.vkEndCommandBuffer(commandBuffer)); + + VK_ASSERT(device.QueueSubmitAndWait(commandBuffer)); + + VK_ASSERT(device.MapMemory(memory, 0, sizeof(Buffers), 0, (void**)&buffers)); + + for (int i = 0; i < NUM_ELEMENTS; ++i) + { + EXPECT_EQ(buffers->in[i], buffers->out[i]) << "Unexpected output at " << i; + } + + // Check for writes outside of bounds. + EXPECT_EQ(buffers->magic0, magic0); + EXPECT_EQ(buffers->magic1, magic1); + EXPECT_EQ(buffers->magic2, magic2); + + device.UnmapMemory(memory); + buffers = nullptr; +}