├── LICENSE ├── Makefile ├── README.md ├── benchmarks ├── Makefile ├── case1_opencl.cpp └── case1_vulkan.cpp ├── images ├── spir.png └── vulkan.png ├── include ├── arguments.h ├── buffer.h ├── commandbuffer.h ├── constants.h ├── device.h ├── devicepool.h ├── program.h └── vc.h ├── lib └── libvulkan.so.1 ├── libvc.pro ├── shaders ├── comp.spv └── computeShader.comp └── src ├── arguments.cpp ├── buffer.cpp ├── commandbuffer.cpp ├── device.cpp ├── devicepool.cpp ├── main.cpp └── program.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Alex Hultman 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | g++ -O2 -s -std=c++11 src/*.cpp -I include -L lib -l:libvulkan.so.1 -o libvc_test 3 | run: 4 | LD_LIBRARY_PATH=lib ./libvc_test 5 | clean: 6 | rm -f libvc_test 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulkan Compute for C++ (experimentation project) 2 | ```libvc``` is a GPGPU engine based on Vulkan. It eats SPIR-V compute shaders and executes them without any graphical context, much like ```OpenCL```. The interface is very abstract and allows you to get to work with your shaders as quickly as possible. With the current state of compute shaders, you should be able to achieve ~OpenCL 1.2 feature parity. 3 | 4 | Compute shaders are constantly being evolved and extended with new versions of OpenGL, GLSL and/or Vulkan. They are here to stay and will only become more competent with time. 5 | 6 | * SSBO's allows for passing mutable linear global memory to your shaders. 7 | * Vulkan command buffers allows for recording commands once and replaying them multiple times, minimizing CPU overhead. 8 | * NVIDIA refuse to embrace OpenCL in favor of their proprietary CUDA alternative - but they do embrace Vulkan and OpenGL. 9 | 10 | ![](images/vulkan.png) ![](images/spir.png) 11 | 12 | ## Overview 13 | The interface is still being designed. It will feature abstractions for devices, memory, buffers, shaders, etc. 14 | 15 | ```c++ 16 | DevicePool devicePool; 17 | for (Device &device : devicePool.getDevices()) { 18 | cout << "Found device: " << device.getName() << " from vendor: 0x" 19 | << hex << device.getVendorId() << dec << endl; 20 | 21 | try { 22 | // Allocate a buffer & clear it 23 | Buffer buffer(device, sizeof(double) * 10240); 24 | buffer.fill(0); 25 | 26 | // Compile the compute shader & prepare to use the buffer as argument 27 | Program program(device, "shaders/comp.spv", {BUFFER}); 28 | Arguments args(program, {buffer}); 29 | 30 | // Create and build the command buffer, making use of the program and arguments 31 | CommandBuffer commands(device, program, args); 32 | for (int i = 0; i < 100000; i++) { 33 | commands.dispatch(10); 34 | commands.barrier(); 35 | } 36 | commands.end(); 37 | 38 | // Time the execution on the GPU 39 | for (int i = 0; i < 5; i++) { 40 | steady_clock::time_point start = steady_clock::now(); 41 | device.submit(commands); 42 | device.wait(); 43 | cout << duration_cast(steady_clock::now() - start).count() << "ms" << endl; 44 | } 45 | 46 | // Download results and show the first scalar 47 | double results[10240]; 48 | buffer.download(results); 49 | cout << "Result: " << results[0] << endl; 50 | 51 | } catch(vc::Error e) { 52 | cout << "vc::Error thrown" << endl; 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | g++ -O2 -s -std=c++11 case1_vulkan.cpp -I ../include -L ../lib -l:libvulkan.so.1 -o case1_vulkan 3 | g++ -O2 -s -std=c++11 case1_opencl.cpp -I ../include -L ../lib -l:libOpenCL.so.1 -o case1_opencl 4 | run: 5 | LD_LIBRARY_PATH=../lib ./case1_vulkan 6 | LD_LIBRARY_PATH=../lib ./case1_opencl 7 | clean: 8 | rm -f case1_vulkan 9 | rm -f case1_opencl 10 | -------------------------------------------------------------------------------- /benchmarks/case1_opencl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | using namespace std; 6 | using namespace chrono; 7 | 8 | #define BUFFER_SIZE 10240 9 | #define INCREMENT_PASSES 100000 10 | #define RUNS 5 11 | 12 | const char *source = "kernel void f(global double *output) { output[get_global_id(0)] += 1.0; }"; 13 | 14 | int main(int argc, char** argv) 15 | { 16 | double data[BUFFER_SIZE] = {}; 17 | 18 | cl_platform_id platforms[5]; 19 | cl_uint numPlatforms = 0; 20 | clGetPlatformIDs(5, platforms, &numPlatforms); 21 | 22 | cl_device_id device_id; 23 | cl_uint numDevices = 0; 24 | for (int i = 0; i < 5; i++) { 25 | if (CL_SUCCESS == clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_GPU, 1, &device_id, &numDevices)) { 26 | char deviceName[512]; 27 | clGetDeviceInfo(device_id, CL_DEVICE_NAME, 512, deviceName, NULL); 28 | cout << "[" << deviceName << "]" << endl; 29 | break; 30 | } 31 | } 32 | 33 | cl_context context = clCreateContext(0, 1, &device_id, nullptr, nullptr, nullptr); 34 | cl_command_queue commands = clCreateCommandQueue(context, device_id, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, nullptr); 35 | cl_program program = clCreateProgramWithSource(context, 1, (const char **) & source, NULL, nullptr); 36 | clBuildProgram(program, 0, NULL, NULL, NULL, NULL); 37 | cl_kernel kernel = clCreateKernel(program, "f", nullptr); 38 | cl_mem output = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(data), nullptr, nullptr); 39 | clEnqueueWriteBuffer(commands, output, CL_TRUE, 0, sizeof(data), data, 0, nullptr, nullptr); 40 | clSetKernelArg(kernel, 0, sizeof(cl_mem), &output); 41 | 42 | size_t global = BUFFER_SIZE; 43 | size_t local = 1024; 44 | 45 | for (int i = 0; i < RUNS; i++) { 46 | steady_clock::time_point start = steady_clock::now(); 47 | for (int i = 0; i < INCREMENT_PASSES; i++) { 48 | clEnqueueNDRangeKernel(commands, kernel, 1, nullptr, &global, &local, 0, nullptr, nullptr); 49 | clEnqueueBarrier(commands); 50 | } 51 | clFinish(commands); 52 | cout << i + 1 << ": " << duration_cast(steady_clock::now() - start).count() << "ms" << endl; 53 | } 54 | 55 | clEnqueueReadBuffer(commands, output, CL_TRUE, 0, sizeof(data), data, 0, nullptr, nullptr); 56 | 57 | if (int(data[0] + 0.5) != INCREMENT_PASSES * RUNS) { 58 | cout << "Mismatching result!" << endl; 59 | return -3; 60 | } 61 | 62 | for (int i = 1; i < BUFFER_SIZE; i++) { 63 | if (data[i] != data[i - 1]) { 64 | cout << "Corruption at " << i << ": " << data[i] << " != " << data[i - 1] << endl; 65 | return -1; 66 | } 67 | } 68 | 69 | cout << "OK" << endl; 70 | 71 | clReleaseMemObject(output); 72 | clReleaseProgram(program); 73 | clReleaseKernel(kernel); 74 | clReleaseCommandQueue(commands); 75 | clReleaseContext(context); 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /benchmarks/case1_vulkan.cpp: -------------------------------------------------------------------------------- 1 | #include "vc.h" 2 | using namespace vc; 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | using namespace chrono; 10 | 11 | #define BUFFER_SIZE 10240 12 | #define INCREMENT_PASSES 100000 13 | #define RUNS 5 14 | 15 | int main() 16 | { 17 | DevicePool devicePool; 18 | for (Device &device : devicePool.getDevices()) { 19 | cout << "[" << device.getName() << "]" << endl; 20 | 21 | try { 22 | Pipeline pipeline = device.pipeline("../shaders/comp.spv", {BUFFER}); 23 | Buffer output = device.buffer(sizeof(double) * BUFFER_SIZE); 24 | 25 | // full buffer with 0 (very explicit) 26 | Buffer mappable = device.mappable(sizeof(double) * BUFFER_SIZE); 27 | double *inputScalars = (double *) mappable.map(); 28 | for (int i = 0; i < BUFFER_SIZE; i++) { 29 | inputScalars[i] = 0; 30 | } 31 | mappable.unmap(); 32 | 33 | CommandBuffer writeCommands = device.commandBuffer(); 34 | writeCommands.begin(); 35 | writeCommands.copyBuffer(mappable, output, BUFFER_SIZE * sizeof(double)); 36 | writeCommands.end(); 37 | device.submit(writeCommands); 38 | device.wait(); 39 | 40 | // create a command buffer with 100k passes 41 | CommandBuffer commandBuffer = device.commandBuffer(); 42 | commandBuffer.begin(); 43 | commandBuffer.bindPipeline(pipeline); 44 | commandBuffer.bindResources(pipeline, {output}); 45 | 46 | steady_clock::time_point start = steady_clock::now(); 47 | for (int i = 0; i < INCREMENT_PASSES; i++) { 48 | commandBuffer.dispatch(10, 1, 1); 49 | commandBuffer.barrier(); 50 | } 51 | commandBuffer.end(); 52 | //cout << "Command buffer creation took " << duration_cast(steady_clock::now() - start).count() << "ms.\n"; 53 | 54 | for (int i = 0; i < RUNS; i++) { 55 | start = steady_clock::now(); 56 | device.submit(commandBuffer); 57 | device.wait(); 58 | cout << i + 1 << ": " << duration_cast(steady_clock::now() - start).count() << "ms" << endl; 59 | } 60 | 61 | // download results to our mappable buffer 62 | CommandBuffer readCommands = device.commandBuffer(); 63 | readCommands.begin(); 64 | readCommands.copyBuffer(output, mappable, BUFFER_SIZE * sizeof(double)); 65 | readCommands.end(); 66 | device.submit(readCommands); 67 | device.wait(); 68 | 69 | // check for correctness 70 | double *outputScalars = (double *) mappable.map(); 71 | 72 | if (int(outputScalars[0] + 0.5) != INCREMENT_PASSES * RUNS) { 73 | cout << "Mismatching result!" << endl; 74 | return -3; 75 | } 76 | 77 | for (int i = 1; i < BUFFER_SIZE; i++) { 78 | if (outputScalars[i] != outputScalars[i - 1]) { 79 | cout << "Corruption at " << i << ": " << outputScalars[i] << " != " << outputScalars[i - 1] << endl; 80 | return -1; 81 | } 82 | } 83 | mappable.unmap(); 84 | 85 | } catch(vc::Error e) { 86 | cout << "vc::Error thrown" << endl; 87 | return -2; 88 | } 89 | } 90 | 91 | cout << "OK" << endl; 92 | return 0; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /images/spir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uNetworking/libvc/347a431008b58840792df65ce7167334bd3f9b94/images/spir.png -------------------------------------------------------------------------------- /images/vulkan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uNetworking/libvc/347a431008b58840792df65ce7167334bd3f9b94/images/vulkan.png -------------------------------------------------------------------------------- /include/arguments.h: -------------------------------------------------------------------------------- 1 | #ifndef ARGUMENTS_H 2 | #define ARGUMENTS_H 3 | 4 | #include "program.h" 5 | #include "buffer.h" 6 | #include 7 | 8 | namespace vc { 9 | 10 | class Arguments : protected Program { 11 | private: 12 | VkDescriptorPool descriptorPool; 13 | VkDescriptorSet descriptorSet; 14 | 15 | public: 16 | Arguments(Program &function, std::vector resources); 17 | void bindTo(VkCommandBuffer commandBuffer); 18 | void destroy(); 19 | }; 20 | 21 | } 22 | 23 | #endif // ARGUMENTS_H 24 | 25 | -------------------------------------------------------------------------------- /include/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | #include "device.h" 5 | #include "commandbuffer.h" 6 | #include 7 | 8 | namespace vc { 9 | 10 | class Buffer : protected Device { 11 | private: 12 | VkDeviceMemory memory; 13 | VkBuffer buffer; 14 | 15 | public: 16 | Buffer(Device &device, size_t byteSize, bool mappable = false); 17 | void fill(uint32_t value); 18 | void enqueueCopy(Buffer src, Buffer dst, size_t byteSize, VkCommandBuffer commandBuffer); 19 | void download(void *hostPtr); 20 | operator VkBuffer(); 21 | void destroy(); 22 | void unmap(); 23 | void *map(); 24 | }; 25 | 26 | } 27 | 28 | #endif // BUFFER_H 29 | 30 | -------------------------------------------------------------------------------- /include/commandbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMANDBUFFER_H 2 | #define COMMANDBUFFER_H 3 | 4 | #include "device.h" 5 | #include 6 | 7 | namespace vc { 8 | 9 | class Program; 10 | class Arguments; 11 | 12 | class CommandBuffer : protected Device { 13 | private: 14 | VkCommandBuffer commandBuffer; 15 | VkCommandPool commandPool; 16 | void sharedConstructor(); 17 | 18 | public: 19 | CommandBuffer(Device &device); 20 | CommandBuffer(Device &device, Program &program, Arguments &arguments); 21 | void destroy(); 22 | operator VkCommandBuffer(); 23 | void begin(); 24 | void barrier(); 25 | void dispatch(int x = 1, int y = 1, int z = 1); 26 | void end(); 27 | }; 28 | 29 | } 30 | 31 | #endif // COMMANDBUFFER_H 32 | 33 | -------------------------------------------------------------------------------- /include/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | #include 5 | 6 | namespace vc { 7 | 8 | enum Error{ 9 | ERROR_INSTANCE, 10 | ERROR_DEVICES, 11 | ERROR_MALLOC, 12 | ERROR_MAP, 13 | ERROR_SHADER, 14 | ERROR_COMMAND 15 | }; 16 | 17 | enum ResourceType { 18 | BUFFER = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER 19 | }; 20 | 21 | } 22 | 23 | #endif // CONSTANTS_H 24 | 25 | -------------------------------------------------------------------------------- /include/device.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_H 2 | #define DEVICE_H 3 | 4 | #include 5 | #include "constants.h" 6 | 7 | namespace vc { 8 | 9 | class CommandBuffer; 10 | 11 | class Device { 12 | protected: 13 | VkPhysicalDevice physicalDevice; 14 | VkPhysicalDeviceProperties physicalDeviceProperties; 15 | VkDevice device; 16 | VkQueue queue; 17 | CommandBuffer *implicitCommandBuffer; 18 | 19 | int memoryTypeMappable = -1, 20 | memoryTypeLocal = -1, 21 | computeQueueFamily = -1; 22 | 23 | public: 24 | Device(VkPhysicalDevice physicalDevice); 25 | void destroy(); 26 | void submit(VkCommandBuffer commandBuffer); 27 | void wait(); 28 | const char *getName(); 29 | uint32_t getVendorId(); 30 | }; 31 | 32 | } 33 | 34 | #endif // DEVICE_H 35 | 36 | -------------------------------------------------------------------------------- /include/devicepool.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICEPOOL_H 2 | #define DEVICEPOOL_H 3 | 4 | #include "device.h" 5 | #include 6 | 7 | namespace vc { 8 | 9 | class DevicePool { 10 | private: 11 | VkInstance instance; 12 | std::vector devices; 13 | 14 | public: 15 | DevicePool(); 16 | std::vector &getDevices(); 17 | VkInstance &getInstance(); 18 | }; 19 | 20 | } 21 | 22 | #endif // DEVICEPOOL_H 23 | 24 | -------------------------------------------------------------------------------- /include/program.h: -------------------------------------------------------------------------------- 1 | #ifndef PROGRAM_H 2 | #define PROGRAM_H 3 | 4 | #include "device.h" 5 | #include 6 | #include 7 | 8 | namespace vc { 9 | 10 | class Program : protected Device { 11 | protected: 12 | VkShaderModule shaderModule; 13 | VkPipelineLayout pipelineLayout; 14 | VkDescriptorSetLayout descriptorSetLayout; 15 | VkPipeline pipeline; 16 | 17 | public: 18 | Program(Device &device, const char *fileName, std::vector resourceTypes); 19 | void bindTo(VkCommandBuffer commandBuffer); 20 | }; 21 | 22 | } 23 | 24 | #endif // PROGRAM_H 25 | 26 | -------------------------------------------------------------------------------- /include/vc.h: -------------------------------------------------------------------------------- 1 | #ifndef VC_H 2 | #define VC_H 3 | 4 | #include "constants.h" 5 | #include "buffer.h" 6 | #include "commandbuffer.h" 7 | #include "device.h" 8 | #include "devicepool.h" 9 | #include "program.h" 10 | #include "arguments.h" 11 | 12 | #endif // VC_H 13 | -------------------------------------------------------------------------------- /lib/libvulkan.so.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uNetworking/libvc/347a431008b58840792df65ce7167334bd3f9b94/lib/libvulkan.so.1 -------------------------------------------------------------------------------- /libvc.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console c++11 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | 6 | SOURCES += src/main.cpp \ 7 | src/commandbuffer.cpp \ 8 | src/device.cpp \ 9 | src/buffer.cpp \ 10 | src/arguments.cpp \ 11 | src/program.cpp \ 12 | src/devicepool.cpp 13 | HEADERS += include/vc.h \ 14 | include/buffer.h \ 15 | include/commandbuffer.h \ 16 | include/devicepool.h \ 17 | include/device.h \ 18 | include/constants.h \ 19 | include/program.h \ 20 | include/arguments.h 21 | 22 | INCLUDEPATH += include 23 | LIBS += -L$$_PRO_FILE_PWD_/lib -l:libvulkan.so.1 24 | QMAKE_CXXFLAGS += -Wno-missing-field-initializers 25 | -------------------------------------------------------------------------------- /shaders/comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uNetworking/libvc/347a431008b58840792df65ce7167334bd3f9b94/shaders/comp.spv -------------------------------------------------------------------------------- /shaders/computeShader.comp: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | layout(local_size_x=1024, local_size_y=1, local_size_z=1) in; 4 | 5 | layout (binding=0) buffer a2 6 | { 7 | double outp[]; 8 | }; 9 | 10 | void main() 11 | { 12 | outp[gl_GlobalInvocationID.x] += 1.0; 13 | } 14 | -------------------------------------------------------------------------------- /src/arguments.cpp: -------------------------------------------------------------------------------- 1 | #include "arguments.h" 2 | 3 | namespace vc { 4 | 5 | Arguments::Arguments(Program &function, std::vector resources) : Program(function) 6 | { 7 | // how many of each type 8 | VkDescriptorPoolSize descriptorPoolSizes[] = { 9 | {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, (uint32_t) resources.size()} 10 | }; 11 | 12 | VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; 13 | descriptorPoolCreateInfo.poolSizeCount = 1; 14 | descriptorPoolCreateInfo.maxSets = 1; 15 | descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes; 16 | if (VK_SUCCESS != vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)) { 17 | throw ERROR_SHADER; 18 | } 19 | 20 | // allocate the descriptor set (according to the function's layout) 21 | VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; 22 | descriptorSetAllocateInfo.descriptorSetCount = 1; // 1? 23 | descriptorSetAllocateInfo.pSetLayouts = &descriptorSetLayout; 24 | descriptorSetAllocateInfo.descriptorPool = descriptorPool; 25 | 26 | if (VK_SUCCESS != vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet)) { 27 | throw ERROR_SHADER; 28 | } 29 | 30 | // bind to this 31 | 32 | // buffers to bind 33 | VkDescriptorBufferInfo *descriptorBufferInfo = new VkDescriptorBufferInfo[resources.size()]; 34 | for (uint32_t i = 0; i < resources.size(); i++) { 35 | descriptorBufferInfo[i].buffer = resources[i]; 36 | descriptorBufferInfo[i].offset = 0; 37 | descriptorBufferInfo[i].range = VK_WHOLE_SIZE; 38 | } 39 | 40 | // bind stuff here 41 | VkWriteDescriptorSet writeDescriptorSet = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; 42 | writeDescriptorSet.dstSet = descriptorSet;//pipeline.getDescriptorSet(); 43 | writeDescriptorSet.dstBinding = 0; 44 | writeDescriptorSet.descriptorCount = resources.size(); 45 | writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; 46 | writeDescriptorSet.pBufferInfo = descriptorBufferInfo; 47 | vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); 48 | delete [] descriptorBufferInfo; 49 | } 50 | 51 | void Arguments::bindTo(VkCommandBuffer commandBuffer) 52 | { 53 | vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); 54 | } 55 | 56 | void Arguments::destroy() 57 | { 58 | vkFreeDescriptorSets(device, descriptorPool, 1, &descriptorSet); 59 | vkDestroyDescriptorPool(device, descriptorPool, nullptr); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | namespace vc { 4 | 5 | Buffer::Buffer(Device &device, size_t byteSize, bool mappable) : Device(device) 6 | { 7 | // create buffer 8 | VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; 9 | bufferCreateInfo.size = byteSize; 10 | bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; 11 | if (VK_SUCCESS != vkCreateBuffer(this->device, &bufferCreateInfo, nullptr, &buffer)) { 12 | throw ERROR_MALLOC; 13 | } 14 | 15 | // get memory requirements 16 | VkMemoryRequirements memoryRequirements; 17 | vkGetBufferMemoryRequirements(this->device, buffer, &memoryRequirements); 18 | 19 | // allocate memory for the buffer 20 | VkMemoryAllocateInfo memoryAllocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; 21 | memoryAllocateInfo.allocationSize = memoryRequirements.size; 22 | memoryAllocateInfo.memoryTypeIndex = mappable ? memoryTypeMappable : memoryTypeLocal; 23 | if (VK_SUCCESS != vkAllocateMemory(this->device, &memoryAllocateInfo, nullptr, &memory)) { 24 | throw ERROR_MALLOC; 25 | } 26 | 27 | // bind memory to the buffer 28 | if (VK_SUCCESS != vkBindBufferMemory(this->device, buffer, memory, 0)) { 29 | throw ERROR_MALLOC; 30 | } 31 | } 32 | 33 | void Buffer::fill(uint32_t value) 34 | { 35 | implicitCommandBuffer->begin(); 36 | vkCmdFillBuffer(*implicitCommandBuffer, buffer, 0, VK_WHOLE_SIZE, value); 37 | implicitCommandBuffer->end(); 38 | submit(*implicitCommandBuffer); 39 | wait(); 40 | } 41 | 42 | void Buffer::enqueueCopy(Buffer src, Buffer dst, size_t byteSize, VkCommandBuffer commandBuffer) 43 | { 44 | VkBufferCopy bufferCopy = {0, 0, byteSize}; 45 | vkCmdCopyBuffer(commandBuffer, src.buffer, dst.buffer, 1, &bufferCopy); 46 | } 47 | 48 | void Buffer::download(void *hostPtr) 49 | { 50 | VkMemoryRequirements memoryRequirements; 51 | vkGetBufferMemoryRequirements(this->device, buffer, &memoryRequirements); 52 | 53 | Buffer mappable(*this, memoryRequirements.size, memoryTypeMappable); 54 | 55 | implicitCommandBuffer->begin(); 56 | enqueueCopy(*this, mappable, memoryRequirements.size, *implicitCommandBuffer); 57 | implicitCommandBuffer->end(); 58 | submit(*implicitCommandBuffer); 59 | wait(); 60 | 61 | memcpy(hostPtr, mappable.map(), memoryRequirements.size); 62 | mappable.unmap(); 63 | mappable.destroy(); 64 | } 65 | 66 | Buffer::operator VkBuffer() 67 | { 68 | return buffer; 69 | } 70 | 71 | void Buffer::destroy() 72 | { 73 | vkFreeMemory(device, memory, nullptr); 74 | vkDestroyBuffer(device, buffer, nullptr); 75 | } 76 | 77 | void Buffer::unmap() 78 | { 79 | vkUnmapMemory(device, memory); 80 | } 81 | 82 | void *Buffer::map() 83 | { 84 | double *pointer; 85 | if (VK_SUCCESS != vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, (void **) &pointer)) { 86 | throw ERROR_MAP; 87 | } 88 | 89 | return pointer; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/commandbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "commandbuffer.h" 2 | #include "arguments.h" 3 | 4 | namespace vc { 5 | 6 | void CommandBuffer::sharedConstructor() 7 | { 8 | VkCommandPoolCreateInfo commandPoolCreateInfo = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; 9 | commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; 10 | commandPoolCreateInfo.queueFamilyIndex = this->computeQueueFamily; 11 | if (VK_SUCCESS != vkCreateCommandPool(this->device, &commandPoolCreateInfo, nullptr, &commandPool)) { 12 | throw ERROR_COMMAND; 13 | } 14 | 15 | VkCommandBufferAllocateInfo commandBufferAllocateInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; 16 | commandBufferAllocateInfo.commandBufferCount = 1; 17 | commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 18 | commandBufferAllocateInfo.commandPool = commandPool; 19 | if (VK_SUCCESS != vkAllocateCommandBuffers(this->device, &commandBufferAllocateInfo, &commandBuffer)) { 20 | throw ERROR_COMMAND; 21 | } 22 | } 23 | 24 | CommandBuffer::CommandBuffer(Device &device, Program &program, Arguments &arguments) : Device(device) 25 | { 26 | sharedConstructor(); 27 | arguments.bindTo(*this); 28 | program.bindTo(*this); 29 | } 30 | 31 | CommandBuffer::CommandBuffer(Device &device) : Device(device) 32 | { 33 | sharedConstructor(); 34 | } 35 | 36 | CommandBuffer::CommandBuffer(Device &device, Program &program, Arguments &arguments); 37 | 38 | void CommandBuffer::destroy() 39 | { 40 | vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); 41 | vkDestroyCommandPool(device, commandPool, nullptr); 42 | } 43 | 44 | CommandBuffer::operator VkCommandBuffer() 45 | { 46 | return commandBuffer; 47 | } 48 | 49 | void CommandBuffer::begin() 50 | { 51 | VkCommandBufferBeginInfo commandBufferBeginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; 52 | if (VK_SUCCESS != vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo)) { 53 | throw ERROR_COMMAND; 54 | } 55 | } 56 | 57 | void CommandBuffer::barrier() 58 | { 59 | vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 60 | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); 61 | } 62 | 63 | void CommandBuffer::dispatch(int x, int y, int z) 64 | { 65 | vkCmdDispatch(commandBuffer, x, y, z); 66 | } 67 | 68 | void CommandBuffer::end() 69 | { 70 | if (VK_SUCCESS != vkEndCommandBuffer(commandBuffer)) { 71 | throw ERROR_COMMAND; 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/device.cpp: -------------------------------------------------------------------------------- 1 | #include "device.h" 2 | #include "commandbuffer.h" 3 | 4 | namespace vc { 5 | 6 | Device::Device(VkPhysicalDevice physicalDevice) : physicalDevice(physicalDevice) 7 | { 8 | // select a queue family with compute support 9 | uint32_t numQueues; 10 | vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &numQueues, nullptr); 11 | 12 | VkQueueFamilyProperties *queueFamilyProperties = new VkQueueFamilyProperties[numQueues]; 13 | vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &numQueues, queueFamilyProperties); 14 | 15 | for (uint32_t i = 0; i < numQueues; i++) { 16 | if (queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { 17 | computeQueueFamily = i; 18 | break; 19 | } 20 | } 21 | 22 | delete [] queueFamilyProperties; 23 | if (computeQueueFamily == -1) { 24 | throw ERROR_DEVICES; 25 | } 26 | 27 | VkDeviceQueueCreateInfo queueCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; 28 | queueCreateInfo.queueCount = 1; 29 | float priorities[] = {1.0f}; 30 | queueCreateInfo.pQueuePriorities = priorities; 31 | queueCreateInfo.queueFamilyIndex = computeQueueFamily; 32 | 33 | // create the logical device 34 | VkPhysicalDeviceFeatures physicalDeviceFeatures = {}; 35 | VkDeviceCreateInfo deviceCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; 36 | deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; 37 | deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeatures; 38 | deviceCreateInfo.queueCreateInfoCount = 1; 39 | if (VK_SUCCESS != vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)) { 40 | throw ERROR_DEVICES; 41 | } 42 | 43 | vkGetDeviceQueue(device, computeQueueFamily, 0, &queue); 44 | vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); 45 | 46 | // get indices of memory types we care about 47 | VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties; 48 | vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physicalDeviceMemoryProperties); 49 | for (uint32_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++) { 50 | if (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT && memoryTypeMappable == -1) { 51 | memoryTypeMappable = i; 52 | } 53 | 54 | if (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT && memoryTypeLocal == -1) { 55 | memoryTypeLocal = i; 56 | } 57 | } 58 | 59 | // create the implicit command buffer 60 | implicitCommandBuffer = new CommandBuffer(*this); 61 | } 62 | 63 | void Device::destroy() 64 | { 65 | implicitCommandBuffer->destroy(); 66 | delete implicitCommandBuffer; 67 | vkDestroyDevice(device, nullptr); 68 | } 69 | 70 | void Device::submit(VkCommandBuffer commandBuffer) 71 | { 72 | VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; 73 | submitInfo.commandBufferCount = 1; 74 | VkCommandBuffer commandBuffers[1] = {commandBuffer}; 75 | submitInfo.pCommandBuffers = commandBuffers; 76 | if (VK_SUCCESS != vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)) { 77 | throw ERROR_DEVICES; 78 | } 79 | } 80 | 81 | void Device::wait() 82 | { 83 | if (VK_SUCCESS != vkQueueWaitIdle(queue)) { 84 | throw ERROR_DEVICES; 85 | } 86 | } 87 | 88 | const char *Device::getName() 89 | { 90 | return physicalDeviceProperties.deviceName; 91 | } 92 | 93 | uint32_t Device::getVendorId() 94 | { 95 | return physicalDeviceProperties.vendorID; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/devicepool.cpp: -------------------------------------------------------------------------------- 1 | #include "devicepool.h" 2 | 3 | namespace vc { 4 | 5 | DevicePool::DevicePool() 6 | { 7 | VkInstanceCreateInfo instanceCreateInfo = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; 8 | if (VK_SUCCESS != vkCreateInstance(&instanceCreateInfo, nullptr, &instance)) { 9 | throw ERROR_INSTANCE; 10 | } 11 | 12 | uint32_t numDevices; 13 | if (VK_SUCCESS != vkEnumeratePhysicalDevices(instance, &numDevices, nullptr) || !numDevices) { 14 | throw ERROR_DEVICES; 15 | } 16 | 17 | VkPhysicalDevice *physicalDevices = new VkPhysicalDevice[numDevices]; 18 | if (VK_SUCCESS != vkEnumeratePhysicalDevices(instance, &numDevices, physicalDevices)) { 19 | throw ERROR_DEVICES; 20 | } 21 | 22 | for (uint32_t i = 0; i < numDevices; i++) { 23 | devices.push_back(Device(physicalDevices[i])); 24 | } 25 | 26 | delete [] physicalDevices; 27 | } 28 | 29 | std::vector &DevicePool::getDevices() 30 | { 31 | return devices; 32 | } 33 | 34 | VkInstance &DevicePool::getInstance() 35 | { 36 | return instance; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "vc.h" 2 | using namespace vc; 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | using namespace chrono; 10 | 11 | int main() 12 | { 13 | DevicePool devicePool; 14 | for (Device &device : devicePool.getDevices()) { 15 | cout << "[" << device.getName() << "]" << endl; 16 | 17 | try { 18 | Buffer buffer(device, sizeof(double) * 10240); 19 | buffer.fill(0); 20 | 21 | Program program(device, "/home/alexhultman/libvc/shaders/comp.spv", {BUFFER}); 22 | Arguments args(program, {buffer}); 23 | 24 | CommandBuffer commands(device, program, args); 25 | for (int i = 0; i < 100000; i++) { 26 | commands.dispatch(10); 27 | commands.barrier(); 28 | } 29 | commands.end(); 30 | 31 | // time the execution on the GPU 32 | for (int i = 0; i < 5; i++) { 33 | steady_clock::time_point start = steady_clock::now(); 34 | device.submit(commands); 35 | device.wait(); 36 | cout << duration_cast(steady_clock::now() - start).count() << "ms" << endl; 37 | } 38 | 39 | double results[10240]; 40 | buffer.download(results); 41 | 42 | buffer.destroy(); 43 | args.destroy(); 44 | commands.destroy(); 45 | device.destroy(); 46 | 47 | cout << "Scalar is: " << results[0] << endl; 48 | for (int i = 1; i < 10240; i++) { 49 | if (results[i] != results[i - 1]) { 50 | cout << "Corruption at " << i << ": " << results[i] << " != " << results[i - 1] << endl; 51 | return -1; 52 | } 53 | } 54 | } catch(vc::Error e) { 55 | cout << "vc::Error thrown" << endl; 56 | } 57 | } 58 | 59 | cout << "OK" << endl; 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/program.cpp: -------------------------------------------------------------------------------- 1 | #include "program.h" 2 | 3 | namespace vc { 4 | 5 | Program::Program(Device &device, const char *fileName, std::vector resourceTypes) : Device(device) 6 | { 7 | std::ifstream fin(fileName, std::ifstream::ate); 8 | size_t byteLength = fin.tellg(); 9 | fin.seekg(0, std::ifstream::beg); 10 | char *data = new char[byteLength]; 11 | fin.read(data, byteLength); 12 | fin.close(); 13 | 14 | VkShaderModuleCreateInfo shaderModuleCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; 15 | shaderModuleCreateInfo.codeSize = byteLength; 16 | shaderModuleCreateInfo.pCode = (uint32_t *) data; 17 | if (VK_SUCCESS != vkCreateShaderModule(this->device, &shaderModuleCreateInfo, nullptr, &shaderModule)) { 18 | throw ERROR_SHADER; 19 | } 20 | 21 | VkDescriptorSetLayoutBinding *bindings = new VkDescriptorSetLayoutBinding[resourceTypes.size()]; 22 | for (uint32_t i = 0; i < resourceTypes.size(); i++) { 23 | bindings[i].binding = i; 24 | bindings[i].descriptorType = (VkDescriptorType) resourceTypes[i]; 25 | bindings[i].descriptorCount = 1; 26 | bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; 27 | bindings[i].pImmutableSamplers = nullptr; 28 | } 29 | 30 | VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; 31 | descriptorSetLayoutCreateInfo.pBindings = bindings; 32 | descriptorSetLayoutCreateInfo.bindingCount = resourceTypes.size(); 33 | if (VK_SUCCESS != vkCreateDescriptorSetLayout(this->device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayout)) { 34 | throw ERROR_SHADER; 35 | } 36 | 37 | VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; 38 | pipelineLayoutCreateInfo.setLayoutCount = 1; 39 | pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; 40 | if (VK_SUCCESS != vkCreatePipelineLayout(this->device,&pipelineLayoutCreateInfo, nullptr, &pipelineLayout)) { 41 | throw ERROR_SHADER; 42 | } 43 | 44 | delete [] bindings; 45 | delete [] data; 46 | 47 | VkPipelineShaderStageCreateInfo pipelineShaderInfo = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; 48 | pipelineShaderInfo.module = shaderModule; 49 | pipelineShaderInfo.pName = "main"; 50 | pipelineShaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; 51 | 52 | VkComputePipelineCreateInfo pipelineInfo = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; 53 | pipelineInfo.stage = pipelineShaderInfo; 54 | pipelineInfo.layout = pipelineLayout; 55 | if (VK_SUCCESS != vkCreateComputePipelines(this->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline)) { 56 | throw ERROR_DEVICES; 57 | } 58 | } 59 | 60 | void Program::bindTo(VkCommandBuffer commandBuffer) 61 | { 62 | vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); 63 | } 64 | 65 | } 66 | --------------------------------------------------------------------------------