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/CMakeLists.txt b/CMakeLists.txt
index 6850639..45e0f33 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2353,6 +2353,7 @@
if(BUILD_TESTS AND BUILD_VULKAN)
set(UNITTESTS_LIST
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/VulkanUnitTests/Device.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/VulkanUnitTests/Driver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/VulkanUnitTests/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/VulkanUnitTests/unittests.cpp
@@ -2363,6 +2364,7 @@
${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googletest/include/
${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googlemock/include/
${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googletest/
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/SPIRV-Tools/include
${CMAKE_CURRENT_SOURCE_DIR}/include/
)
@@ -2373,5 +2375,5 @@
COMPILE_DEFINITIONS "STANDALONE"
)
- target_link_libraries(vk-unittests ${OS_LIBS})
+ target_link_libraries(vk-unittests ${OS_LIBS} SPIRV-Tools)
endif()
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;
+}