From b5abfe15898777c22eeb5f65b059f3dedd87b74e Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Fri, 21 Feb 2025 22:41:22 +0100 Subject: [PATCH] adding pipeline unit tests --- Includes/Pulse.h | 2 + README.md | 49 +++++ Sources/Backends/Vulkan/VulkanComputePass.c | 40 +++- .../Backends/Vulkan/VulkanComputePipeline.c | 3 +- Sources/Backends/Vulkan/VulkanDescriptor.c | 8 + Sources/PulseBackend.c | 6 +- Tests/Vulkan/Buffer.c | 70 ++++++ Tests/Vulkan/Pipeline.c | 208 ++++++++++++++++++ Tests/Vulkan/Shaders/BufferCopy.nzsl | 26 +++ Tests/Vulkan/Shaders/ReadOnlyBindings.nzsl | 25 +++ Tests/Vulkan/Shaders/ReadWriteBindings.nzsl | 27 +++ Tests/Vulkan/Shaders/Simple.nzsl | 13 ++ Tests/Vulkan/Shaders/WriteOnlyBindings.nzsl | 25 +++ Tests/Vulkan/main.c | 2 + 14 files changed, 497 insertions(+), 7 deletions(-) create mode 100644 Tests/Vulkan/Shaders/BufferCopy.nzsl create mode 100644 Tests/Vulkan/Shaders/ReadOnlyBindings.nzsl create mode 100644 Tests/Vulkan/Shaders/ReadWriteBindings.nzsl create mode 100644 Tests/Vulkan/Shaders/Simple.nzsl create mode 100644 Tests/Vulkan/Shaders/WriteOnlyBindings.nzsl diff --git a/Includes/Pulse.h b/Includes/Pulse.h index 940adcf..552dfe6 100644 --- a/Includes/Pulse.h +++ b/Includes/Pulse.h @@ -110,6 +110,8 @@ typedef enum PulseErrorType PULSE_ERROR_MAP_FAILED, PULSE_ERROR_INVALID_DEVICE, PULSE_ERROR_INVALID_REGION, + PULSE_ERROR_INVALID_BUFFER_USAGE, + PULSE_ERROR_INVALID_IMAGE_USAGE, } PulseErrorType; typedef enum PulseImageType diff --git a/README.md b/README.md index 0e27457..a796ff8 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,52 @@ [![Windows build](https://github.com/ft-grmhd/Pulse/actions/workflows/windows-build.yml/badge.svg)](https://github.com/ft-grmhd/Pulse/actions/workflows/windows-build.yml) Pulse is a low level GPGPU library designed for highly intensive general GPU computations with high control over the hardware. It is built on top of Vulkan and a Metal support is in discussion. + +```cpp +#include + +int main(void) +{ + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + + const uint8_t shader_bytecode[] = { + #include "shader.spv.h" + }; + + PulseComputePipelineCreateInfo info = { 0 }; + info.code_size = sizeof(shader_bytecode); + info.code = shader_bytecode; + info.entrypoint = "main"; + info.format = PULSE_SHADER_FORMAT_SPIRV_BIT; + info.num_readwrite_storage_buffers = 1; + PulseComputePipeline pipeline = PulseCreateComputePipeline(device, &info); + + PulseBufferCreateInfo buffer_create_info = { 0 }; + buffer_create_info.size = 1024; + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_WRITE; + PulseBuffer buffer = PulseCreateBuffer(device, &buffer_create_info); + + PulseFence fence = PulseCreateFence(device); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + + PulseComputePass pass = PulseBeginComputePass(cmd); + PulseBindStorageBuffers(pass, 0, &buffer, 1); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + PulseSubmitCommandList(device, cmd, fence); + PulseWaitForFences(device, &fence, 1, true); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + PulseDestroyComputePipeline(device, pipeline); + + PulseDestroyBuffer(device, buffer); + + PulseDestroyDevice(device); + PulseUnloadBackend(backend); + return 0; +} +``` diff --git a/Sources/Backends/Vulkan/VulkanComputePass.c b/Sources/Backends/Vulkan/VulkanComputePass.c index d7222a1..676b02e 100644 --- a/Sources/Backends/Vulkan/VulkanComputePass.c +++ b/Sources/Backends/Vulkan/VulkanComputePass.c @@ -37,16 +37,32 @@ void VulkanDestroyComputePass(PulseDevice device, PulseComputePass pass) void VulkanBindStorageBuffers(PulseComputePass pass, uint32_t starting_slot, const PulseBuffer* buffers, uint32_t num_buffers) { PulseBufferUsageFlags usage = buffers[0]->usage; - PulseBuffer* array = ((usage & PULSE_BUFFER_USAGE_STORAGE_WRITE) != 0) ? pass->readwrite_storage_buffers : pass->readonly_storage_buffers; + bool is_readwrite = (usage & PULSE_BUFFER_USAGE_STORAGE_WRITE) != 0; + PulseBuffer* array = is_readwrite ? pass->readwrite_storage_buffers : pass->readonly_storage_buffers; VulkanComputePass* vulkan_pass = VULKAN_RETRIEVE_DRIVER_DATA_AS(pass, VulkanComputePass*); for(uint32_t i = 0; i < num_buffers; i++) { + if(is_readwrite && (buffers[i]->usage & PULSE_BUFFER_USAGE_STORAGE_WRITE) == 0) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(pass->cmd->device->backend)) + PulseLogError(pass->cmd->device->backend, "cannot bind a read only buffer with read-write buffers"); + PulseSetInternalError(PULSE_ERROR_INVALID_BUFFER_USAGE); + return; + } + else if(!is_readwrite && (buffers[i]->usage & PULSE_BUFFER_USAGE_STORAGE_WRITE) != 0) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(pass->cmd->device->backend)) + PulseLogError(pass->cmd->device->backend, "cannot bind a read-write buffer with read only buffers"); + PulseSetInternalError(PULSE_ERROR_INVALID_BUFFER_USAGE); + return; + } + if(array[starting_slot + i] == buffers[i]) continue; array[starting_slot + i] = buffers[i]; - - if((usage & PULSE_BUFFER_USAGE_STORAGE_WRITE) != 0) + + if(is_readwrite) vulkan_pass->should_recreate_write_descriptor_sets = true; else vulkan_pass->should_recreate_read_only_descriptor_sets = true; @@ -60,11 +76,27 @@ void VulkanBindUniformData(PulseComputePass pass, uint32_t slot, const void* dat void VulkanBindStorageImages(PulseComputePass pass, uint32_t starting_slot, const PulseImage* images, uint32_t num_images) { PulseImageUsageFlags usage = images[0]->usage; - PulseImage* array = ((usage & PULSE_IMAGE_USAGE_STORAGE_WRITE) != 0) ? pass->readwrite_images : pass->readonly_images; + bool is_readwrite = (usage & PULSE_IMAGE_USAGE_STORAGE_WRITE) != 0; + PulseImage* array = is_readwrite ? pass->readwrite_images : pass->readonly_images; VulkanComputePass* vulkan_pass = VULKAN_RETRIEVE_DRIVER_DATA_AS(pass, VulkanComputePass*); for(uint32_t i = 0; i < num_images; i++) { + if(is_readwrite && (images[i]->usage & PULSE_IMAGE_USAGE_STORAGE_WRITE) == 0) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(pass->cmd->device->backend)) + PulseLogError(pass->cmd->device->backend, "cannot bind a read only image with read-write images"); + PulseSetInternalError(PULSE_ERROR_INVALID_IMAGE_USAGE); + return; + } + else if(!is_readwrite && (images[i]->usage & PULSE_IMAGE_USAGE_STORAGE_WRITE) != 0) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(pass->cmd->device->backend)) + PulseLogError(pass->cmd->device->backend, "cannot bind a read-write image with read only images"); + PulseSetInternalError(PULSE_ERROR_INVALID_IMAGE_USAGE); + return; + } + if(array[starting_slot + i] == images[i]) continue; array[starting_slot + i] = images[i]; diff --git a/Sources/Backends/Vulkan/VulkanComputePipeline.c b/Sources/Backends/Vulkan/VulkanComputePipeline.c index d378c94..d82a429 100644 --- a/Sources/Backends/Vulkan/VulkanComputePipeline.c +++ b/Sources/Backends/Vulkan/VulkanComputePipeline.c @@ -89,8 +89,9 @@ void VulkanDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipel vulkan_device->vkDestroyPipelineLayout(vulkan_device->device, vulkan_pipeline->layout, PULSE_NULLPTR); vulkan_device->vkDestroyPipeline(vulkan_device->device, vulkan_pipeline->pipeline, PULSE_NULLPTR); free(vulkan_pipeline); - free(pipeline); if(PULSE_IS_BACKEND_HIGH_LEVEL_DEBUG(device->backend)) PulseLogInfoFmt(device->backend, "(Vulkan) destroyed compute pipeline %p", pipeline); + + free(pipeline); } diff --git a/Sources/Backends/Vulkan/VulkanDescriptor.c b/Sources/Backends/Vulkan/VulkanDescriptor.c index 52fedda..9f6c48e 100644 --- a/Sources/Backends/Vulkan/VulkanDescriptor.c +++ b/Sources/Backends/Vulkan/VulkanDescriptor.c @@ -275,6 +275,8 @@ void VulkanBindDescriptorSets(PulseComputePass pass) for(uint32_t i = 0; i < pass->current_pipeline->num_readonly_storage_images; i++) { + if(pass->readonly_images[i] == PULSE_NULL_HANDLE) + continue; VkWriteDescriptorSet* write_descriptor_set = &writes[write_count]; write_descriptor_set->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -301,6 +303,8 @@ void VulkanBindDescriptorSets(PulseComputePass pass) for(uint32_t i = 0; i < pass->current_pipeline->num_readonly_storage_buffers; i++) { + if(pass->readonly_storage_buffers[i] == PULSE_NULL_HANDLE) + continue; VkWriteDescriptorSet* write_descriptor_set = &writes[write_count]; write_descriptor_set->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -336,6 +340,8 @@ void VulkanBindDescriptorSets(PulseComputePass pass) for(uint32_t i = 0; i < pass->current_pipeline->num_readwrite_storage_images; i++) { + if(pass->readwrite_images[i] == PULSE_NULL_HANDLE) + continue; VkWriteDescriptorSet* write_descriptor_set = &writes[write_count]; write_descriptor_set->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -362,6 +368,8 @@ void VulkanBindDescriptorSets(PulseComputePass pass) for(uint32_t i = 0; i < pass->current_pipeline->num_readwrite_storage_buffers; i++) { + if(pass->readwrite_storage_buffers[i] == PULSE_NULL_HANDLE) + continue; VkWriteDescriptorSet* write_descriptor_set = &writes[write_count]; write_descriptor_set->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; diff --git a/Sources/PulseBackend.c b/Sources/PulseBackend.c index 1830020..e531c04 100644 --- a/Sources/PulseBackend.c +++ b/Sources/PulseBackend.c @@ -164,8 +164,10 @@ PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error) case PULSE_ERROR_DEVICE_LOST: return "device has been lost"; case PULSE_ERROR_INVALID_INTERNAL_POINTER: return "invalid internal pointer"; case PULSE_ERROR_MAP_FAILED: return "memory mapping failed"; - case PULSE_ERROR_INVALID_DEVICE: return "device is invalid"; - case PULSE_ERROR_INVALID_REGION: return "region is invalid"; + case PULSE_ERROR_INVALID_DEVICE: return "invalid device"; + case PULSE_ERROR_INVALID_REGION: return "invalid region"; + case PULSE_ERROR_INVALID_BUFFER_USAGE: return "invalid buffer usage"; + case PULSE_ERROR_INVALID_IMAGE_USAGE: return "invalid image usage"; default: return "invalid error type"; }; diff --git a/Tests/Vulkan/Buffer.c b/Tests/Vulkan/Buffer.c index 14cd003..e88c1f8 100644 --- a/Tests/Vulkan/Buffer.c +++ b/Tests/Vulkan/Buffer.c @@ -325,6 +325,75 @@ void TestBufferComputeWrite() CleanupPulse(backend); } +void TestBufferComputeCopy() +{ + PulseBackend backend; + SetupPulse(&backend); + PulseDevice device; + SetupDevice(backend, &device); + + const uint8_t shader_bytecode[] = { + #include "Shaders/BufferCopy.spv.h" + }; + + uint32_t data[256]; + memset(data, 0xFF, 256 * sizeof(uint32_t)); + + PulseBufferCreateInfo buffer_create_info = { 0 }; + buffer_create_info.size = 256 * sizeof(int32_t); + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_READ | PULSE_BUFFER_USAGE_TRANSFER_UPLOAD; + PulseBuffer read_buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(read_buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + { + void* ptr; + TEST_ASSERT_NOT_EQUAL_MESSAGE(PulseMapBuffer(read_buffer, &ptr), false, PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_NOT_NULL(ptr); + memcpy(ptr, data, 256 * sizeof(uint32_t)); + PulseUnmapBuffer(read_buffer); + } + + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_WRITE | PULSE_BUFFER_USAGE_TRANSFER_DOWNLOAD; + PulseBuffer write_buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(write_buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePipeline pipeline; + LoadComputePipeline(device, &pipeline, shader_bytecode, sizeof(shader_bytecode), 0, 1, 0, 1, 0); + + PulseFence fence = PulseCreateFence(device); + TEST_ASSERT_NOT_EQUAL_MESSAGE(fence, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + TEST_ASSERT_NOT_EQUAL_MESSAGE(cmd, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePass pass = PulseBeginComputePass(cmd); + TEST_ASSERT_NOT_EQUAL_MESSAGE(pass, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseBindStorageBuffers(pass, 0, &read_buffer, 1); + PulseBindStorageBuffers(pass, 0, &write_buffer, 1); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + TEST_ASSERT_TRUE_MESSAGE(PulseSubmitCommandList(device, cmd, fence), PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE_MESSAGE(PulseWaitForFences(device, &fence, 1, true), PulseVerbaliseErrorType(PulseGetLastErrorType())); + + { + void* ptr; + TEST_ASSERT_NOT_EQUAL_MESSAGE(PulseMapBuffer(write_buffer, &ptr), false, PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL(memcmp(ptr, data, 256 * sizeof(uint32_t)), 0); + PulseUnmapBuffer(write_buffer); + } + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + PulseDestroyBuffer(device, read_buffer); + PulseDestroyBuffer(device, write_buffer); + + CleanupPipeline(device, pipeline); + CleanupDevice(device); + CleanupPulse(backend); +} + void TestBufferDestruction() { PulseBackend backend; @@ -365,5 +434,6 @@ void TestBuffer() RUN_TEST(TestBufferCopy); RUN_TEST(TestBufferCopyImage); RUN_TEST(TestBufferComputeWrite); + RUN_TEST(TestBufferComputeCopy); RUN_TEST(TestBufferDestruction); } diff --git a/Tests/Vulkan/Pipeline.c b/Tests/Vulkan/Pipeline.c index 3773a8f..e5a5d21 100644 --- a/Tests/Vulkan/Pipeline.c +++ b/Tests/Vulkan/Pipeline.c @@ -10,6 +10,211 @@ void TestPipelineSetup() PulseDevice device; SetupDevice(backend, &device); + const uint8_t shader_bytecode[] = { + #include "Shaders/Simple.spv.h" + }; + + PulseComputePipeline pipeline; + LoadComputePipeline(device, &pipeline, shader_bytecode, sizeof(shader_bytecode), 0, 0, 0, 0, 0); + + PulseFence fence = PulseCreateFence(device); + TEST_ASSERT_NOT_EQUAL_MESSAGE(fence, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + TEST_ASSERT_NOT_EQUAL_MESSAGE(cmd, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePass pass = PulseBeginComputePass(cmd); + TEST_ASSERT_NOT_EQUAL_MESSAGE(pass, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + TEST_ASSERT_TRUE_MESSAGE(PulseSubmitCommandList(device, cmd, fence), PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE_MESSAGE(PulseWaitForFences(device, &fence, 1, true), PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + + CleanupPipeline(device, pipeline); + CleanupDevice(device); + CleanupPulse(backend); +} + +void TestPipelineReadOnlyBindings() +{ + PulseBackend backend; + SetupPulse(&backend); + PulseDevice device; + SetupDevice(backend, &device); + + const uint8_t shader_bytecode[] = { + #include "Shaders/ReadOnlyBindings.spv.h" + }; + + PulseBufferCreateInfo buffer_create_info = { 0 }; + buffer_create_info.size = 256 * sizeof(int32_t); + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_READ; + PulseBuffer buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePipeline pipeline; + LoadComputePipeline(device, &pipeline, shader_bytecode, sizeof(shader_bytecode), 1, 1, 0, 0, 0); + + PulseFence fence = PulseCreateFence(device); + TEST_ASSERT_NOT_EQUAL_MESSAGE(fence, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + TEST_ASSERT_NOT_EQUAL_MESSAGE(cmd, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePass pass = PulseBeginComputePass(cmd); + TEST_ASSERT_NOT_EQUAL_MESSAGE(pass, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseBindStorageBuffers(pass, 0, &buffer, 1); + PulseBindStorageImages(pass, 1, &image, 1); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + TEST_ASSERT_TRUE_MESSAGE(PulseSubmitCommandList(device, cmd, fence), PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE_MESSAGE(PulseWaitForFences(device, &fence, 1, true), PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + PulseDestroyBuffer(device, buffer); + PulseDestroyImage(device, image); + + CleanupPipeline(device, pipeline); + CleanupDevice(device); + CleanupPulse(backend); +} + +void TestPipelineWriteOnlyBindings() +{ + PulseBackend backend; + SetupPulse(&backend); + PulseDevice device; + SetupDevice(backend, &device); + + const uint8_t shader_bytecode[] = { + #include "Shaders/ReadOnlyBindings.spv.h" + }; + + PulseBufferCreateInfo buffer_create_info = { 0 }; + buffer_create_info.size = 256 * sizeof(int32_t); + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_WRITE; + PulseBuffer buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_WRITE; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePipeline pipeline; + LoadComputePipeline(device, &pipeline, shader_bytecode, sizeof(shader_bytecode), 0, 0, 1, 1, 0); + + PulseFence fence = PulseCreateFence(device); + TEST_ASSERT_NOT_EQUAL_MESSAGE(fence, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + TEST_ASSERT_NOT_EQUAL_MESSAGE(cmd, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePass pass = PulseBeginComputePass(cmd); + TEST_ASSERT_NOT_EQUAL_MESSAGE(pass, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseBindStorageBuffers(pass, 0, &buffer, 1); + PulseBindStorageImages(pass, 1, &image, 1); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + TEST_ASSERT_TRUE_MESSAGE(PulseSubmitCommandList(device, cmd, fence), PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE_MESSAGE(PulseWaitForFences(device, &fence, 1, true), PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + PulseDestroyBuffer(device, buffer); + PulseDestroyImage(device, image); + + CleanupPipeline(device, pipeline); + CleanupDevice(device); + CleanupPulse(backend); +} + +void TestPipelineReadWriteBindings() +{ + PulseBackend backend; + SetupPulse(&backend); + PulseDevice device; + SetupDevice(backend, &device); + + const uint8_t shader_bytecode[] = { + #include "Shaders/ReadOnlyBindings.spv.h" + }; + + PulseBufferCreateInfo buffer_create_info = { 0 }; + buffer_create_info.size = 256 * sizeof(int32_t); + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_READ; + PulseBuffer read_buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(read_buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + buffer_create_info.usage = PULSE_BUFFER_USAGE_STORAGE_WRITE; + PulseBuffer write_buffer = PulseCreateBuffer(device, &buffer_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(write_buffer, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage read_image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(read_image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_WRITE; + PulseImage write_image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(write_image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePipeline pipeline; + LoadComputePipeline(device, &pipeline, shader_bytecode, sizeof(shader_bytecode), 1, 1, 1, 1, 0); + + PulseFence fence = PulseCreateFence(device); + TEST_ASSERT_NOT_EQUAL_MESSAGE(fence, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + TEST_ASSERT_NOT_EQUAL_MESSAGE(cmd, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseComputePass pass = PulseBeginComputePass(cmd); + TEST_ASSERT_NOT_EQUAL_MESSAGE(pass, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseBindStorageBuffers(pass, 0, &read_buffer, 1); + PulseBindStorageBuffers(pass, 0, &write_buffer, 1); + PulseBindStorageImages(pass, 1, &read_image, 1); + PulseBindStorageImages(pass, 1, &write_image, 1); + PulseBindComputePipeline(pass, pipeline); + PulseDispatchComputations(pass, 32, 32, 1); + PulseEndComputePass(pass); + + TEST_ASSERT_TRUE_MESSAGE(PulseSubmitCommandList(device, cmd, fence), PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE_MESSAGE(PulseWaitForFences(device, &fence, 1, true), PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); + PulseDestroyBuffer(device, read_buffer); + PulseDestroyBuffer(device, write_buffer); + PulseDestroyImage(device, read_image); + PulseDestroyImage(device, write_image); + + CleanupPipeline(device, pipeline); CleanupDevice(device); CleanupPulse(backend); } @@ -17,4 +222,7 @@ void TestPipelineSetup() void TestPipeline() { RUN_TEST(TestPipelineSetup); + RUN_TEST(TestPipelineReadOnlyBindings); + RUN_TEST(TestPipelineWriteOnlyBindings); + RUN_TEST(TestPipelineReadWriteBindings); } diff --git a/Tests/Vulkan/Shaders/BufferCopy.nzsl b/Tests/Vulkan/Shaders/BufferCopy.nzsl new file mode 100644 index 0000000..9c9d75f --- /dev/null +++ b/Tests/Vulkan/Shaders/BufferCopy.nzsl @@ -0,0 +1,26 @@ +[nzsl_version("1.0")] +module; + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[layout(std430)] +struct SSBO +{ + data: dyn_array[u32] +} + +external +{ + [set(0), binding(0)] read_ssbo: storage[SSBO, readonly], + [set(1), binding(0)] write_ssbo: storage[SSBO, writeonly], +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ + write_ssbo.data[input.indices.x * input.indices.y] = read_ssbo.data[input.indices.x * input.indices.y]; +} diff --git a/Tests/Vulkan/Shaders/ReadOnlyBindings.nzsl b/Tests/Vulkan/Shaders/ReadOnlyBindings.nzsl new file mode 100644 index 0000000..2494dee --- /dev/null +++ b/Tests/Vulkan/Shaders/ReadOnlyBindings.nzsl @@ -0,0 +1,25 @@ +[nzsl_version("1.0")] +module; + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[layout(std430)] +struct SSBO +{ + data: dyn_array[u32] +} + +external +{ + [set(0), binding(0)] read_ssbo: storage[SSBO, readonly], + [set(0), binding(1)] read_texture: texture2D[f32, readonly, rgba8], +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ +} diff --git a/Tests/Vulkan/Shaders/ReadWriteBindings.nzsl b/Tests/Vulkan/Shaders/ReadWriteBindings.nzsl new file mode 100644 index 0000000..277e40f --- /dev/null +++ b/Tests/Vulkan/Shaders/ReadWriteBindings.nzsl @@ -0,0 +1,27 @@ +[nzsl_version("1.0")] +module; + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[layout(std430)] +struct SSBO +{ + data: dyn_array[u32] +} + +external +{ + [set(0), binding(0)] read_ssbo: storage[SSBO, readonly], + [set(0), binding(1)] read_texture: texture2D[f32, readonly, rgba8], + [set(1), binding(0)] write_ssbo: storage[SSBO, writeonly], + [set(1), binding(1)] write_texture: texture2D[f32, readonly, rgba8], +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ +} diff --git a/Tests/Vulkan/Shaders/Simple.nzsl b/Tests/Vulkan/Shaders/Simple.nzsl new file mode 100644 index 0000000..99a7a33 --- /dev/null +++ b/Tests/Vulkan/Shaders/Simple.nzsl @@ -0,0 +1,13 @@ +[nzsl_version("1.0")] +module; + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ +} diff --git a/Tests/Vulkan/Shaders/WriteOnlyBindings.nzsl b/Tests/Vulkan/Shaders/WriteOnlyBindings.nzsl new file mode 100644 index 0000000..8b66b01 --- /dev/null +++ b/Tests/Vulkan/Shaders/WriteOnlyBindings.nzsl @@ -0,0 +1,25 @@ +[nzsl_version("1.0")] +module; + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[layout(std430)] +struct SSBO +{ + data: dyn_array[u32] +} + +external +{ + [set(1), binding(0)] write_ssbo: storage[SSBO], + [set(1), binding(1)] write_texture: texture2D[f32, readonly, rgba8], +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ +} diff --git a/Tests/Vulkan/main.c b/Tests/Vulkan/main.c index 83f1594..ed8998d 100644 --- a/Tests/Vulkan/main.c +++ b/Tests/Vulkan/main.c @@ -7,6 +7,7 @@ extern void TestBackend(); extern void TestDevice(); extern void TestBuffer(); extern void TestImage(); +extern void TestPipeline(); int main(void) { @@ -15,5 +16,6 @@ int main(void) TestDevice(); TestBuffer(); TestImage(); + TestPipeline(); return UNITY_END(); }