/* * Vulkan SC Example - Attraction based compute shader particle system * * Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher) * * Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de * Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #ifdef VULKANSC #include #include "pipeline_cache.hpp" #include "wfdSwapChain.h" #include "utils.h" #else #include #include #endif // #ifdef VULKANSC #include #include #include #include #include #include #include #include #include #include #define LOG(fn) if (err == VK_SUCCESS) std::cout << fn << " OK" << std::endl; \ else std::cout << fn << " ERROR!!" << std::endl; \ assert(err == VK_SUCCESS); // Default fence timeout in nanoseconds #define DEFAULT_FENCE_TIMEOUT 100000000000 // Particle count #define PARTICLE_COUNT 128 * 1024 struct Particle { float pos[2]; // Particle position float vel[2]; // Particle velocity float gradientPos[4]; // Texture coordinates for the gradient ramp map }; struct { // Compute shader uniform block object float deltaT; // Frame delta time float destX; // x position of the attractor float destY; // y position of the attractor int32_t particleCount = PARTICLE_COUNT; } computeUBO; static std::vector offscreenImageMems; static std::vector offscreenImages; static std::vector offscreenImageViews; static VkDeviceMemory copyImageMem; static VkImage copyImage; static VkDeviceMemory particleImageMem; static VkImage particleImage; static VkDescriptorImageInfo particleImageDescriptor; static VkDeviceMemory gradientImageMem; static VkImage gradientImage; static VkDescriptorImageInfo gradientImageDescriptor; static int width = 1920; static int height = 1080; static VkInstance instance; static VkRenderPass renderPass; static VkDevice dev = VK_NULL_HANDLE; static std::vector physdevs; static VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // different from the format in the example static VkPhysicalDeviceMemoryProperties deviceMemoryProperties; static VkPhysicalDeviceProperties deviceProperties; static VkDescriptorPool descriptorPool; static VkBuffer storageBuffer; static VkDeviceMemory storageMemory; static VkDeviceSize storageBufferSize; static VkDescriptorBufferInfo storageDescriptor; static VkBuffer uniformBuffer; static VkDeviceMemory uniformMemory; static VkDescriptorBufferInfo uniformDescriptor; static void* uniformData; static bool outputImageFile = false; static float animStart = 20.0f; static float frameTimer = 1.0f; static float timer = 0.0f; static float timerSpeed = 0.25f; static uint32_t frameCounter = 0; VkPipelineVertexInputStateCreateInfo vertexInputStateInfo; std::vector vertexBindingDescriptions; std::vector vertexAttributeDescriptions; // Graphics pipeline specific stuff: static VkPipeline graphicsPipeline; static VkPipelineLayout graphicsPipelineLayout; static VkPipelineShaderStageCreateInfo graphicsStageInfo[2] = { { }, { } }; static VkDescriptorSetLayout graphicsDescriptorSetLayout; static VkDescriptorSet graphicsDescriptorSet; static std::vector drawCmdBuffers; static VkCommandBuffer copyCmdBuffer = VK_NULL_HANDLE; static VkCommandPool graphicsCmdPool; static std::vector framebuffers; static uint32_t graphicsQueueFamilyIndex; static VkQueue graphicsQueue = VK_NULL_HANDLE; static VkShaderModule vertexShaderModule = VK_NULL_HANDLE; static VkShaderModule fragmentShaderModule = VK_NULL_HANDLE; static VkSemaphore graphicsSemaphore; // Compute pipeline specific stuff: static VkPipeline computePipeline; static VkPipelineLayout computePipelineLayout; static VkDescriptorSetLayout computeDescriptorSetLayout; static VkDescriptorSet computeDescriptorSet; static VkCommandBuffer computeCmdBuffer; static VkCommandPool computeCmdPool; static uint32_t computeQueueFamilyIndex; static VkQueue computeQueue; static VkPipelineShaderStageCreateInfo computeStageInfo{}; static VkShaderModule computeShaderModule = VK_NULL_HANDLE; static VkSemaphore computeSemaphore; // VKSC specific stuff: #ifdef VULKANSC static VkDeviceObjectReservationCreateInfo devObjectResCreateInfo; static VkPipelineOfflineCreateInfo graphicsPipelineOfflineCreateInfo; static VkPipelineOfflineCreateInfo computePipelineOfflineCreateInfo; static VkPipelineCacheCreateInfo pipelineCacheCreateInfo; static uint32_t pipelineCacheCreateInfoCount = 1; static size_t pipelineCacheSize = 0; static void* pipelineCacheData = nullptr; static VkPipelineCache pipelineCache; static bool useExternalCache = false; static VkDeviceSize const PIPELINE_SIZE = 1024U * 1024U; static VkPipelinePoolSize pipelinePoolSizes[2] = { {VK_STRUCTURE_TYPE_PIPELINE_POOL_SIZE, nullptr, PIPELINE_SIZE, 1}, {VK_STRUCTURE_TYPE_PIPELINE_POOL_SIZE, nullptr, PIPELINE_SIZE, 1} }; // openwfd stuff static bool enableWfd = false; SwapChainWfd swapChainWfd; #else static const char* jsonGenLayerName = "VK_LAYER_KHRONOS_json_gen"; #endif // #ifdef VULKANSC static uint32_t getMemoryType(uint32_t typeBits, VkFlags properties) { for (uint32_t i = 0; i < 32; i++) { if ((typeBits & 1) == 1) { if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { return i; } } typeBits >>= 1; } // todo : throw error return 0; } static void printTestInfo(bool printUsageOnly = false) { std::cout << "\nUsage: \n"; std::cout << "--help / -h : Print information about vksc_01tri\n"; std::cout << "--wfd / -w : Run the test with openwfd enabled (default is to check output against expected hash value)\n"; std::cout << "--cache / -c : Load the cache from tri.bin (default is to load from embedded cache)" << std::endl; std::cout << "--output / -o : The test outputs screenshot of rendered image to a file named tri.ppm, if this flag is passed." << std::endl; } static bool parseCommandLineArgs(int argc, char** argv) { bool earlyExit = false; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { printTestInfo(); earlyExit = true; } #ifdef VULKANSC else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--wfd")) { enableWfd = true; } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache")) { useExternalCache = true; } #endif // #ifdef VULKANSC else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { outputImageFile = true; } else { std::cerr << "\nInvalid arguments specified.\n"; printTestInfo(); } } #ifdef VULKANSC if (enableWfd && outputImageFile) { std::cerr << "Both of --output/-o and --wfd/-w flags are set. --wfd takes precendence." << std::endl; printTestInfo(); outputImageFile = false; } #endif // #ifdef VULKANSC return earlyExit; } #ifdef VULKANSC static VkResult fillDeviceObjResInfo() { // TODO: refector those "+ x" numbers. Because when we disable openwfd during json generate // those resources used by vulkan when enabling openwfd won't be counted. So have to tune // them ourselves. devObjectResCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_OBJECT_RESERVATION_CREATE_INFO; devObjectResCreateInfo.pNext = nullptr; devObjectResCreateInfo.pipelineCacheCreateInfoCount = pipelineCacheCreateInfoCount; devObjectResCreateInfo.pPipelineCacheCreateInfos = &pipelineCacheCreateInfo; devObjectResCreateInfo.pipelinePoolSizeCount = 2; devObjectResCreateInfo.pPipelinePoolSizes = pipelinePoolSizes; devObjectResCreateInfo.semaphoreRequestCount = 2; devObjectResCreateInfo.commandBufferRequestCount = 7 + 2; devObjectResCreateInfo.fenceRequestCount = 4; devObjectResCreateInfo.deviceMemoryRequestCount = 9 + 3; devObjectResCreateInfo.bufferRequestCount = 5; devObjectResCreateInfo.imageRequestCount = 4 + 2; devObjectResCreateInfo.eventRequestCount = 0; devObjectResCreateInfo.queryPoolRequestCount = 0; devObjectResCreateInfo.bufferViewRequestCount = 0; devObjectResCreateInfo.imageViewRequestCount = 3 + 2; devObjectResCreateInfo.layeredImageViewRequestCount = 0; devObjectResCreateInfo.pipelineCacheRequestCount = 2; devObjectResCreateInfo.pipelineLayoutRequestCount = 2; devObjectResCreateInfo.renderPassRequestCount = 1; devObjectResCreateInfo.graphicsPipelineRequestCount = 1; devObjectResCreateInfo.computePipelineRequestCount = 1; devObjectResCreateInfo.descriptorSetLayoutRequestCount = 2; devObjectResCreateInfo.samplerRequestCount = 2; devObjectResCreateInfo.descriptorPoolRequestCount = 1; devObjectResCreateInfo.descriptorSetRequestCount = 2; devObjectResCreateInfo.framebufferRequestCount = 1 + 1; devObjectResCreateInfo.commandPoolRequestCount = 2; devObjectResCreateInfo.samplerYcbcrConversionRequestCount = 0; devObjectResCreateInfo.surfaceRequestCount = 0; devObjectResCreateInfo.swapchainRequestCount = 0; devObjectResCreateInfo.displayModeRequestCount = 0; devObjectResCreateInfo.subpassDescriptionRequestCount = 0; devObjectResCreateInfo.attachmentDescriptionRequestCount = 0; devObjectResCreateInfo.descriptorSetLayoutBindingLimit = 2; devObjectResCreateInfo.maxImageViewMipLevels = 1; devObjectResCreateInfo.maxImageViewArrayLayers = 0; devObjectResCreateInfo.maxLayeredImageViewMipLevels = 0; devObjectResCreateInfo.maxOcclusionQueriesPerPool = 0; devObjectResCreateInfo.maxPipelineStatisticsQueriesPerPool = 0; devObjectResCreateInfo.maxTimestampQueriesPerPool = 0; return VK_SUCCESS; } // We have different binaries for different platforms. static void getCacheFile() { switch (deviceProperties.deviceID) { case 0xA5BA03D7: // gv11b pipelineCacheData = (void*)cache_file_gv11b; pipelineCacheSize = cache_size_gv11b; break; case 0x97BA03D7: // ga10b pipelineCacheData = (void*)cache_file_ga10b; pipelineCacheSize = cache_size_ga10b; break; case 0x97EA03D7: // ga10f pipelineCacheData = (void*)cache_file_ga10f; pipelineCacheSize = cache_size_ga10f; break; default: assert(!"Invalid chip"); break; } } static void loadPipelineCaches() { std::string readFileName("data/pipeline/computeparticles/pipeline_cache.bin"); std::cout << " Trying to read cache from: " << readFileName << std::endl; FILE* pReadFile = useExternalCache ? fopen(readFileName.c_str(), "rb") : nullptr; if (pReadFile) { fseek(pReadFile, 0, SEEK_END); pipelineCacheSize = ftell(pReadFile); rewind(pReadFile); pipelineCacheData = (char*)malloc(sizeof(char) * pipelineCacheSize); if (pipelineCacheData == nullptr) { fputs("Memory error", stderr); exit(EXIT_FAILURE); } size_t result = fread(pipelineCacheData, 1, pipelineCacheSize, pReadFile); if (result != pipelineCacheSize) { fputs("Reading error", stderr); free(pipelineCacheData); exit(EXIT_FAILURE); } fclose(pReadFile); std::cout << " Pipeline cache HIT!" << std::endl; std::cout << " cacheData loaded from " << readFileName.c_str() << std::endl; } else { std::cout << "Error opening cache file, using embedded binary." << std::endl; getCacheFile(); } pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; pipelineCacheCreateInfo.pNext = NULL; pipelineCacheCreateInfo.initialDataSize = pipelineCacheSize; pipelineCacheCreateInfo.pInitialData = pipelineCacheData; pipelineCacheCreateInfo.flags = VK_PIPELINE_CACHE_CREATE_READ_ONLY_BIT | VK_PIPELINE_CACHE_CREATE_USE_APPLICATION_STORAGE_BIT; } static VkResult createPipelineCaches() { VkResult err = vkCreatePipelineCache(dev, &pipelineCacheCreateInfo, nullptr, &pipelineCache); LOG("vkCreatePipelineCache"); return err; } #else static VkResult checkJsonLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (uint32_t i = 0; i < layerCount; i++) { if (!strncmp(availableLayers[i].layerName, jsonGenLayerName, strlen(jsonGenLayerName))) { return VK_SUCCESS; } } return VK_ERROR_INITIALIZATION_FAILED; } #endif // #ifdef VULKANSC static VkResult queryQueueFamilyIndices() { VkResult err; std::vector queueFamilyProperties; uint32_t queueFamilyCount; vkGetPhysicalDeviceQueueFamilyProperties(physdevs[0], &queueFamilyCount, nullptr); assert(queueFamilyCount > 0); queueFamilyProperties.resize(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(physdevs[0], &queueFamilyCount, queueFamilyProperties.data()); bool foundGraphicsIndex = false; bool foundComputeIndex = false; for (uint32_t i = 0; i < queueFamilyCount; i++) { if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT && !foundGraphicsIndex) { graphicsQueueFamilyIndex = i; foundGraphicsIndex = true; } else if (queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT && !foundComputeIndex) { computeQueueFamilyIndex = i; foundComputeIndex = true; } if (foundGraphicsIndex && foundComputeIndex) { err = VK_SUCCESS; break; } } return err; } static VkResult createInstanceAndDevice() { VkResult err = VK_SUCCESS; #ifndef VULKANSC err = checkJsonLayerSupport(); LOG("checkJsonLayerSupport"); #endif // #endif VULKANSC // Create a Vulkan instance VkInstanceCreateInfo instanceCreateInfo = {}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.pNext = NULL; instanceCreateInfo.pApplicationInfo = NULL; instanceCreateInfo.enabledExtensionCount = 0; instanceCreateInfo.ppEnabledExtensionNames = NULL; instanceCreateInfo.enabledLayerCount = 0; instanceCreateInfo.ppEnabledLayerNames = NULL; #ifndef VULKANSC VkLayerInstanceLink layerLink = { .pNext = nullptr, .pfnNextGetInstanceProcAddr = nullptr, // this has to be really vulkan entry point .pfnNextGetPhysicalDeviceProcAddr = nullptr }; // enable json_gen layer for instance VkLayerInstanceCreateInfo layerInfo = {}; layerInfo.sType = VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO; layerInfo.function = VK_LAYER_LINK_INFO; layerInfo.u.pLayerInfo = &layerLink; instanceCreateInfo.pNext = &layerInfo; instanceCreateInfo.enabledLayerCount = 1; instanceCreateInfo.ppEnabledLayerNames = &jsonGenLayerName; #endif // ifndef VULKANSC err = vkCreateInstance(&instanceCreateInfo, NULL, &instance); assert(err == VK_SUCCESS); uint32_t physdevCount = 0; err = vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr); assert(err == VK_SUCCESS); #ifdef VULKANSC assert(physdevCount == 1); #else assert(physdevCount > 0); #endif physdevs.resize(physdevCount); err = vkEnumeratePhysicalDevices(instance, &physdevCount, physdevs.data()); assert(err == VK_SUCCESS); vkGetPhysicalDeviceProperties(physdevs[0], &deviceProperties); err = queryQueueFamilyIndices(); assert(err == VK_SUCCESS); float priority = 1.0f; std::vector queueInfos; VkDeviceQueueCreateInfo graphicsQueueInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, graphicsQueueFamilyIndex, 1, &priority }; if (graphicsQueueFamilyIndex == computeQueueFamilyIndex) { graphicsQueueInfo.queueCount = 2; queueInfos.push_back(graphicsQueueInfo); } else { VkDeviceQueueCreateInfo computeQueueInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, computeQueueFamilyIndex, 1, &priority }; queueInfos.push_back(graphicsQueueInfo); queueInfos.push_back(computeQueueInfo); } #ifdef VULKANSC std::vector extNamesDev; if (enableWfd) { extNamesDev.push_back(VK_NV_EXTERNAL_MEMORY_SCI_BUF_EXTENSION_NAME); } #endif // #ifdef VULKANSC VkDeviceCreateInfo deviceInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, NULL, 0, // flags static_cast(queueInfos.size()), // queueCreateInfoCount queueInfos.data(), // queueCreateInfo 0, NULL, // enabledLayerCount, ppEnabledLayerNames #ifdef VULKANSC (uint32_t)extNamesDev.size(), extNamesDev.data(), // enabledExtensionCount, ppEnabledExtensionNames #else 0, nullptr, #endif // #ifdef VULKANSC NULL // pEnabledFeatures }; #ifdef VULKANSC loadPipelineCaches(); // Get pipeline cache pointer err = fillDeviceObjResInfo(); // Fill VkDeviceObjectReservationCreateInfo assert(err == VK_SUCCESS); deviceInfo.pNext = &devObjectResCreateInfo; #else // enable json_gen layer for device VkLayerDeviceLink layerDevInfo = {}; VkLayerDeviceCreateInfo devLayerCreateInfo = {}; devLayerCreateInfo.sType = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO; devLayerCreateInfo.pNext = &layerDevInfo; devLayerCreateInfo.function = VK_LAYER_LINK_INFO; devLayerCreateInfo.u.pLayerInfo = &layerDevInfo; deviceInfo.pNext = &devLayerCreateInfo; deviceInfo.enabledLayerCount = 1; deviceInfo.ppEnabledLayerNames = &jsonGenLayerName; #endif // #ifdef VULKANSC err = vkCreateDevice(physdevs[0], &deviceInfo, NULL, &dev); LOG("vkCreateDevice"); vkGetPhysicalDeviceMemoryProperties(physdevs[0], &deviceMemoryProperties); vkGetDeviceQueue(dev, graphicsQueueFamilyIndex, 0, &graphicsQueue); if (graphicsQueueFamilyIndex == computeQueueFamilyIndex) { vkGetDeviceQueue(dev, computeQueueFamilyIndex, 1, &computeQueue); } else { vkGetDeviceQueue(dev, computeQueueFamilyIndex, 0, &computeQueue); } return err; } static VkResult setupRenderPass() { // No depth attachment in this simple test. VkResult err; VkAttachmentDescription attachments[1] = {}; // Color attachment attachments[0].format = colorFormat; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference colorReference = {}; colorReference.attachment = 0; colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.flags = 0; subpass.inputAttachmentCount = 0; subpass.pInputAttachments = NULL; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorReference; subpass.pResolveAttachments = NULL; subpass.pDepthStencilAttachment = NULL; subpass.preserveAttachmentCount = 0; subpass.pPreserveAttachments = NULL; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.pNext = NULL; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = attachments; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 0; renderPassInfo.pDependencies = NULL; err = vkCreateRenderPass(dev, &renderPassInfo, NULL, &renderPass); LOG("vkCreateRenderPass"); return err; } #ifndef VULKANSC static VkResult loadShaderModule(std::string fileName, VkShaderModule* pShaderModule) { VkResult err = VK_SUCCESS; std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate); if (is.is_open()) { size_t size = is.tellg(); is.seekg(0, std::ios::beg); char* shaderCode = new char[size]; is.read(shaderCode, size); is.close(); assert(size > 0); VkShaderModuleCreateInfo moduleCreateInfo{}; moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; moduleCreateInfo.codeSize = size; moduleCreateInfo.pCode = (uint32_t*)shaderCode; err = vkCreateShaderModule(dev, &moduleCreateInfo, nullptr, pShaderModule); LOG("vkCreateShaderModule"); delete[] shaderCode; shaderCode = nullptr; } return err; } #endif // #ifndef VULKANSC static VkResult createShaderStageInfo() { VkResult err; #ifdef VULKANSC // In VKSC, the shader compilation is offline. err = VK_SUCCESS; #else err = loadShaderModule("data/shaders/computeparticles/vk_computeparticles.vert.spv", &vertexShaderModule); assert(err == VK_SUCCESS); err = loadShaderModule("data/shaders/computeparticles/vk_computeparticles.frag.spv", &fragmentShaderModule); assert(err == VK_SUCCESS); err = loadShaderModule("data/shaders/computeparticles/vk_computeparticles.comp.spv", &computeShaderModule); assert(err == VK_SUCCESS); #endif // #ifdef VULKANSC graphicsStageInfo[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; graphicsStageInfo[0].stage = VK_SHADER_STAGE_VERTEX_BIT; graphicsStageInfo[0].module = vertexShaderModule; graphicsStageInfo[0].pName = "main"; graphicsStageInfo[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; graphicsStageInfo[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; graphicsStageInfo[1].module = fragmentShaderModule; graphicsStageInfo[1].pName = "main"; computeStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; computeStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; computeStageInfo.pName = "main"; computeStageInfo.module = computeShaderModule; return err; } static VkResult createCommandPools() { VkResult err; #ifdef VULKANSC VkCommandPoolMemoryReservationCreateInfo memReserveInfo{}; memReserveInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_RESERVATION_CREATE_INFO; memReserveInfo.pNext = NULL; memReserveInfo.commandPoolReservedSize = 1536ULL * 1024ULL; // A 1.5 MB placeholder (default) memReserveInfo.commandPoolMaxCommandBuffers = 4U; // Fix the VUID checking error #endif // #ifndef VULKAN VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; #ifdef VULKANSC cmdPoolInfo.pNext = &memReserveInfo; #endif // #ifndef VULKAN cmdPoolInfo.queueFamilyIndex = graphicsQueueFamilyIndex; cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // Create command pool for graphics pipeline err = vkCreateCommandPool(dev, &cmdPoolInfo, NULL, &graphicsCmdPool); // Create command pool for compute pipeline cmdPoolInfo.queueFamilyIndex = computeQueueFamilyIndex; err = vkCreateCommandPool(dev, &cmdPoolInfo, NULL, &computeCmdPool); return err; } static VkResult createCommandBuffers() { VkResult err; if (createCommandPools() != VK_SUCCESS) { std::cout << "Error creating command pools" << std::endl; assert(0); } #ifdef VULKANSC if (enableWfd) { // When using OpenWFD, we use double buffer for presentation. drawCmdBuffers.resize(SwapChainWfd::IMAGE_COUNT); } else #endif // #ifdef VULKANSC { drawCmdBuffers.resize(1); } VkCommandBufferAllocateInfo cmdBufAllocateInfo = {}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = graphicsCmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = drawCmdBuffers.size(); // Allocate command buffer for graphics pipeline err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, drawCmdBuffers.data()); // Allocate command buffer for compute pipeline cmdBufAllocateInfo.commandPool = computeCmdPool; cmdBufAllocateInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, &computeCmdBuffer); return err; } static VkResult setupRenderBuffer(uint32_t index) { VkResult err; #ifdef VULKANSC if (!enableWfd) #endif // #ifdef VULKANSC { // When enable OpenWFD, the images are created by SwapChainWfd VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.pNext = nullptr; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = colorFormat; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; err = vkCreateImage(dev, &imageInfo, nullptr, &offscreenImages[index]); assert(err == VK_SUCCESS); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(dev, offscreenImages[index], &memReqs); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &offscreenImageMems[index]); assert(err == VK_SUCCESS); LOG("vkAllocateMemory"); err = vkBindImageMemory(dev, offscreenImages[index], offscreenImageMems[index], 0); LOG("vkBindImageMemory"); } VkImageViewCreateInfo colorAttachmentView = {}; colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; colorAttachmentView.pNext = NULL; colorAttachmentView.format = colorFormat; colorAttachmentView.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; colorAttachmentView.subresourceRange.baseMipLevel = 0; colorAttachmentView.subresourceRange.levelCount = 1; colorAttachmentView.subresourceRange.baseArrayLayer = 0; colorAttachmentView.subresourceRange.layerCount = 1; colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D; colorAttachmentView.flags = 0; colorAttachmentView.image = offscreenImages[index]; err = vkCreateImageView(dev, &colorAttachmentView, NULL, &offscreenImageViews[index]); LOG("vkCreateImageView"); return err; } static VkResult setupFrameBuffer() { VkResult err = VK_SUCCESS; VkImageView attachments[1]; VkFramebufferCreateInfo frameBufferCreateInfo = {}; frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; frameBufferCreateInfo.pNext = NULL; frameBufferCreateInfo.renderPass = renderPass; frameBufferCreateInfo.attachmentCount = 1; frameBufferCreateInfo.pAttachments = attachments; frameBufferCreateInfo.width = width; frameBufferCreateInfo.height = height; frameBufferCreateInfo.layers = 1; framebuffers.resize(1); offscreenImageViews.resize(1); offscreenImages.resize(1); offscreenImageMems.resize(1); #ifdef VULKANSC if(enableWfd) { framebuffers.resize(SwapChainWfd::IMAGE_COUNT); offscreenImageViews.resize(SwapChainWfd::IMAGE_COUNT); offscreenImages.resize(SwapChainWfd::IMAGE_COUNT); offscreenImageMems.resize(1); swapChainWfd.GetSwapChainImages(offscreenImages.data(), offscreenImages.size()); } #endif // #ifdef VULKANSC // Set up all framebuffers for(uint32_t i = 0; i < framebuffers.size(); i++) { err = setupRenderBuffer(i); assert(err == VK_SUCCESS); attachments[0] = offscreenImageViews[i]; err = vkCreateFramebuffer(dev, &frameBufferCreateInfo, NULL, &framebuffers[i]); LOG("vkCreateFramebuffer"); } return err; } static VkResult setupCopyImage() { VkResult err; VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_LINEAR; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; err = vkCreateImage(dev, &imageInfo, nullptr, ©Image); assert(err == VK_SUCCESS); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(dev, copyImage, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, ©ImageMem); LOG("vkAllocateMemory"); err = vkBindImageMemory(dev, copyImage, copyImageMem, 0); LOG("vkBindImageMemory"); VkCommandBufferAllocateInfo cmdBufAllocateInfo{}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = graphicsCmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, ©CmdBuffer); LOG("vkAllocateCommandBuffers"); return err; } static VkResult loadTexture2D(std::string filename, VkImage* pImage, VkDeviceMemory* pImageMem, VkDescriptorImageInfo* pDescriptor) { VkResult err; // Check if file exists std::ifstream f(filename.c_str()); if (f.fail()) { fputs("Error reading texture", stderr); exit(EXIT_FAILURE); } // load ktx texture ktxTexture* ktxTexture; ktxResult result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); assert(result == KTX_SUCCESS); uint32_t width = ktxTexture->baseWidth; uint32_t height = ktxTexture->baseHeight; uint32_t mipLevels = ktxTexture->numLevels; ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture); ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture); VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; // Create staging buffer VkBufferCreateInfo bufferCreateInfo{}; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.size = ktxTextureSize; bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(dev, &bufferCreateInfo, nullptr, &stagingBuffer); LOG("vkCreateBuffer"); // Allocate host-visible memory for staging buffer VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(dev, stagingBuffer, &memReqs); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &stagingMemory); LOG("vkAllocateMemory"); err = vkBindBufferMemory(dev, stagingBuffer, stagingMemory, 0); LOG("vkBindBufferMemory"); // Copy texture data into staging buffer uint8_t* data; err = vkMapMemory(dev, stagingMemory, 0, memReqs.size, 0, (void**)&data); LOG("vkMapMemory"); memcpy(data, ktxTextureData, ktxTextureSize); vkUnmapMemory(dev, stagingMemory); // Setup buffer copy regions for each mip level std::vector bufferCopyRegions; for (uint32_t i = 0; i < mipLevels; i++) { ktx_size_t offset; KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset); assert(result == KTX_SUCCESS); VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = i; bufferCopyRegion.imageSubresource.baseArrayLayer = 0; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i); bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i); bufferCopyRegion.imageExtent.depth = 1; bufferCopyRegion.bufferOffset = offset; bufferCopyRegions.push_back(bufferCopyRegion); } // Create optimal tiled target image VkImageCreateInfo imageCreateInfo{}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = colorFormat; imageCreateInfo.mipLevels = mipLevels; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; err = vkCreateImage(dev, &imageCreateInfo, nullptr, pImage); LOG("vkCreateImage"); // Allocate device-only memory for target image vkGetImageMemoryRequirements(dev, *pImage, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, pImageMem); LOG("vkAllocateMemory"); err = vkBindImageMemory(dev, *pImage, *pImageMem, 0); LOG("vkBindImageMemory"); // Allocate copy command buffer VkCommandBuffer cmdBuffer; VkCommandBufferAllocateInfo cmdBufAllocateInfo{}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = graphicsCmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, &cmdBuffer); LOG("vkAllocateCommandBuffers"); // Build copy command buffer VkCommandBufferBeginInfo cmdBufInfo{}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; err = vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo); LOG("vkBeginCommandBuffer copy"); { // Create barrier for target image VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mipLevels; subresourceRange.layerCount = 1; VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.srcAccessMask = 0; imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemoryBarrier.image = *pImage; imageMemoryBarrier.subresourceRange = subresourceRange; // Put barrier inside setup command buffer vkCmdPipelineBarrier( cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier ); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( cmdBuffer, stagingBuffer, *pImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(bufferCopyRegions.size()), bufferCopyRegions.data() ); imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; // Change texture image layout to shader read after all mip levels have been copied vkCmdPipelineBarrier( cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier ); } vkEndCommandBuffer(cmdBuffer); // Submit copy command buffer to graphics queue VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &cmdBuffer; VkFenceCreateInfo fenceCreateInfo{}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceCreateInfo.flags = 0; VkFence fence; err = vkCreateFence(dev, &fenceCreateInfo, nullptr, &fence); LOG("vkCreateFence"); err = vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence); LOG("vkQueueSubmit"); err = vkWaitForFences(dev, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT); LOG("vkWaitForFences"); // Clean up after copy vkDestroyFence(dev, fence, nullptr); vkFreeCommandBuffers(dev, graphicsCmdPool, 1, &cmdBuffer); vkDestroyBuffer(dev, stagingBuffer, nullptr); ktxTexture_Destroy(ktxTexture); #ifndef VULKANSC vkFreeMemory(dev, stagingMemory, nullptr); #endif // #ifndef VULKANSC // Create descriptor sampler VkSamplerCreateInfo samplerCreateInfo = {}; samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerCreateInfo.magFilter = VK_FILTER_LINEAR; samplerCreateInfo.minFilter = VK_FILTER_LINEAR; samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.mipLodBias = 0.0f; samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; samplerCreateInfo.minLod = 0.0f; samplerCreateInfo.maxLod = (float)mipLevels; samplerCreateInfo.maxAnisotropy = 1.0f; samplerCreateInfo.anisotropyEnable = VK_FALSE; samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; err = vkCreateSampler(dev, &samplerCreateInfo, nullptr, &(pDescriptor->sampler)); LOG("vkCreateSampler"); // Create descriptor image view VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewCreateInfo.format = colorFormat; viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange.levelCount = mipLevels; viewCreateInfo.image = *pImage; err = vkCreateImageView(dev, &viewCreateInfo, nullptr, &(pDescriptor->imageView)); LOG("vkCreateImageView"); // Set descriptor image layout pDescriptor->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; return err; } static VkResult createDescriptorPool() { VkResult err; std::vector poolSizes; poolSizes.resize(3); poolSizes[0] = { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }; poolSizes[1] = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 }; poolSizes[2] = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2 }; VkDescriptorPoolCreateInfo descriptorPoolInfo{}; descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolInfo.poolSizeCount = static_cast(poolSizes.size()); descriptorPoolInfo.pPoolSizes = poolSizes.data(); descriptorPoolInfo.maxSets = 2; descriptorPoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; err = vkCreateDescriptorPool(dev, &descriptorPoolInfo, nullptr, &descriptorPool); LOG("vkCreateDescriptorPool"); return err; } static VkResult createStorageBuffers() { VkResult err; std::default_random_engine rndEngine((unsigned)time(nullptr)); std::uniform_real_distribution rndDist(-1.0f, 1.0f); // Initialize particles std::vector particles(PARTICLE_COUNT); for (Particle& particle : particles) { particle.pos[0] = rndDist(rndEngine); particle.pos[1] = rndDist(rndEngine); particle.vel[0] = 0.0f; particle.vel[1] = 0.0f; particle.gradientPos[0] = particle.pos[0] / 2.0f; particle.gradientPos[1] = 0.0f; particle.gradientPos[2] = 0.0f; particle.gradientPos[3] = 0.0f; } storageBufferSize = particles.size() * sizeof(Particle); VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; // Create staging buffer VkBufferCreateInfo bufferCreateInfo{}; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.size = storageBufferSize; bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(dev, &bufferCreateInfo, nullptr, &stagingBuffer); LOG("vkCreateBuffer"); // Allocate host-visible memory for staging buffer VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(dev, stagingBuffer, &memReqs); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &stagingMemory); LOG("vkAllocateMemory"); err = vkBindBufferMemory(dev, stagingBuffer, stagingMemory, 0); LOG("vkBindBufferMemory"); // Copy particle data into staging buffer uint8_t* data; err = vkMapMemory(dev, stagingMemory, 0, memReqs.size, 0, (void**)&data); LOG("vkMapMemory"); memcpy(data, particles.data(), storageBufferSize); vkUnmapMemory(dev, stagingMemory); // Create storage buffer bufferCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; err = vkCreateBuffer(dev, &bufferCreateInfo, nullptr, &storageBuffer); LOG("vkCreateBuffer"); // Allocate device-only memory for storage buffer vkGetBufferMemoryRequirements(dev, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &storageMemory); LOG("vkAllocateMemory"); err = vkBindBufferMemory(dev, storageBuffer, storageMemory, 0); LOG("vkBindBufferMemory"); // Allocate copy command buffer VkCommandBuffer cmdBuffer; VkCommandBufferAllocateInfo cmdBufAllocateInfo{}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = graphicsCmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, &cmdBuffer); LOG("vkAllocateCommandBuffers"); // Build copy command buffer VkCommandBufferBeginInfo cmdBufInfo{}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; err = vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo); { // Copy from staging buffer to storage buffer VkBufferCopy copyRegion{}; copyRegion.size = storageBufferSize; vkCmdCopyBuffer(cmdBuffer, stagingBuffer, storageBuffer, 1, ©Region); // Execute a transfer barrier to the compute queue, if necessary if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { VkBufferMemoryBarrier bufferBarrier{}; bufferBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufferBarrier.pNext = NULL; bufferBarrier.srcAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; bufferBarrier.dstAccessMask = 0; bufferBarrier.srcQueueFamilyIndex = graphicsQueueFamilyIndex; bufferBarrier.dstQueueFamilyIndex = computeQueueFamilyIndex; bufferBarrier.buffer = storageBuffer; bufferBarrier.offset = 0; bufferBarrier.size = storageBufferSize; vkCmdPipelineBarrier( cmdBuffer, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &bufferBarrier, 0, nullptr); } } err = vkEndCommandBuffer(cmdBuffer); // Submit copy command buffer to graphics queue VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &cmdBuffer; VkFenceCreateInfo fenceCreateInfo{}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceCreateInfo.flags = 0; VkFence fence; err = vkCreateFence(dev, &fenceCreateInfo, nullptr, &fence); LOG("vkCreateFence"); err = vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence); LOG("vkQueueSubmit"); err = vkWaitForFences(dev, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT); LOG("vkWaitForFences"); // Setup descriptor storageDescriptor = { storageBuffer, 0, VK_WHOLE_SIZE }; // Clean up after copy vkDestroyFence(dev, fence, nullptr); vkFreeCommandBuffers(dev, graphicsCmdPool, 1, &cmdBuffer); vkDestroyBuffer(dev, stagingBuffer, nullptr); #ifndef VULKANSC vkFreeMemory(dev, stagingMemory, nullptr); #endif // #ifndef VULKANSC // Vertex binding description vertexBindingDescriptions.resize(1); vertexBindingDescriptions[0] = { 0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX }; // Vertex attribute descriptions vertexAttributeDescriptions.resize(2); // Location 0 : Position vertexAttributeDescriptions[0] = { 0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, pos) }; // Location 1 : Gradient position vertexAttributeDescriptions[1] = { 1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, gradientPos) }; // Attach descriptions to vertex buffer vertexInputStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputStateInfo.vertexBindingDescriptionCount = static_cast(vertexBindingDescriptions.size()); vertexInputStateInfo.pVertexBindingDescriptions = vertexBindingDescriptions.data(); vertexInputStateInfo.vertexAttributeDescriptionCount = static_cast(vertexAttributeDescriptions.size()); vertexInputStateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data(); return err; } static VkResult updateUniformBuffers() { computeUBO.deltaT = frameTimer * 2.5f; computeUBO.destX = std::sin(timer * 2.0f * M_PI) * 0.75f; computeUBO.destY = 0.0f; memcpy(uniformData, &computeUBO, sizeof(computeUBO)); return VK_SUCCESS; } static VkResult createUniformBuffers() { VkResult err; // Create uniform buffer VkBufferCreateInfo bufferCreateInfo{}; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.size = sizeof(computeUBO); bufferCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; err = vkCreateBuffer(dev, &bufferCreateInfo, nullptr, &uniformBuffer); LOG("vkCreateBuffer"); // Allocate host-visible memory for uniform buffer VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(dev, uniformBuffer, &memReqs); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &uniformMemory); LOG("vkAllocateMemory"); err = vkBindBufferMemory(dev, uniformBuffer, uniformMemory, 0); LOG("vkBindBufferMemory"); err = vkMapMemory(dev, uniformMemory, 0, VK_WHOLE_SIZE, 0, &uniformData); LOG("vkMapMemory"); // Setup descriptor uniformDescriptor = { uniformBuffer, 0, VK_WHOLE_SIZE }; err = updateUniformBuffers(); return err; } static VkResult createDescriptorSetLayouts() { VkResult err; std::vector setLayoutBindings; // Binding 0 : Particle color map VkDescriptorSetLayoutBinding colorMapBinding{}; colorMapBinding.binding = 0; colorMapBinding.descriptorCount = 1; colorMapBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; colorMapBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; // Binding 1 : Particle gradient ramp VkDescriptorSetLayoutBinding gradientRampBinding{}; gradientRampBinding.binding = 1; gradientRampBinding.descriptorCount = 1; gradientRampBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; gradientRampBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; setLayoutBindings.push_back(colorMapBinding); setLayoutBindings.push_back(gradientRampBinding); // Create graphics descriptor set layout VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{}; descriptorSetLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorSetLayoutInfo.bindingCount = static_cast(setLayoutBindings.size()); descriptorSetLayoutInfo.pBindings = setLayoutBindings.data(); err = vkCreateDescriptorSetLayout(dev, &descriptorSetLayoutInfo, nullptr, &graphicsDescriptorSetLayout); LOG("vkCreateDescriptorSetLayout"); // Create graphics pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &graphicsDescriptorSetLayout; err = vkCreatePipelineLayout(dev, &pipelineLayoutInfo, nullptr, &graphicsPipelineLayout); LOG("vkCreatePipelineLayout"); // Binding 0 : Particle position storage buffer VkDescriptorSetLayoutBinding storageBufferBinding{}; storageBufferBinding.binding = 0; storageBufferBinding.descriptorCount = 1; storageBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; storageBufferBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; // Binding 1 : Uniform buffer VkDescriptorSetLayoutBinding uniformBufferBinding{}; uniformBufferBinding.binding = 1; uniformBufferBinding.descriptorCount = 1; uniformBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uniformBufferBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; setLayoutBindings[0] = storageBufferBinding; setLayoutBindings[1] = uniformBufferBinding; // Create compute descriptor set layout descriptorSetLayoutInfo.pBindings = setLayoutBindings.data(); err = vkCreateDescriptorSetLayout(dev, &descriptorSetLayoutInfo, nullptr, &computeDescriptorSetLayout); LOG("vkCreateDescriptorSetLayout"); // Create compute pipeline layout pipelineLayoutInfo.pSetLayouts = &computeDescriptorSetLayout; err = vkCreatePipelineLayout(dev, &pipelineLayoutInfo, nullptr, &computePipelineLayout); LOG("vkCreatePipelineLayout"); return err; } static VkResult setupDescriptorSets() { VkResult err; std::vector writeDescriptorSets; VkDescriptorSetAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &graphicsDescriptorSetLayout; // Allocate descriptor set for graphics pipeline err = vkAllocateDescriptorSets(dev, &allocInfo, &graphicsDescriptorSet); LOG("vkAllocateDescriptorSets"); // Binding 0 : Particle color map VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.dstSet = graphicsDescriptorSet; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSet.dstBinding = 0; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.pImageInfo = &particleImageDescriptor; writeDescriptorSets.push_back(writeDescriptorSet); // Binding 1 : Particle gradient ramp writeDescriptorSet.dstBinding = 1; writeDescriptorSet.pImageInfo = &gradientImageDescriptor; writeDescriptorSets.push_back(writeDescriptorSet); // Allocate descriptor set for compute pipeline allocInfo.pSetLayouts = &computeDescriptorSetLayout; err = vkAllocateDescriptorSets(dev, &allocInfo, &computeDescriptorSet); LOG("vkAllocateDescriptorSets"); // Binding 0 : Particle position storage buffer writeDescriptorSet.dstSet = computeDescriptorSet; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; writeDescriptorSet.dstBinding = 0; writeDescriptorSet.pImageInfo = nullptr; writeDescriptorSet.pBufferInfo = &storageDescriptor; writeDescriptorSets.push_back(writeDescriptorSet); // Binding 1 : Uniform buffer writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; writeDescriptorSet.dstBinding = 1; writeDescriptorSet.pBufferInfo = &uniformDescriptor; writeDescriptorSets.push_back(writeDescriptorSet); vkUpdateDescriptorSets(dev, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); return err; } static VkResult createPipelines() { VkResult err; // Create graphics pipeline VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{}; inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; inputAssemblyState.flags = 0; inputAssemblyState.primitiveRestartEnable = VK_FALSE; VkPipelineRasterizationStateCreateInfo rasterizationState{}; rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; rasterizationState.cullMode = VK_CULL_MODE_NONE; rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizationState.flags = 0; rasterizationState.depthClampEnable = VK_FALSE; rasterizationState.lineWidth = 1.0f; VkPipelineColorBlendAttachmentState blendAttachmentState{}; blendAttachmentState.colorWriteMask = 0xF; blendAttachmentState.blendEnable = VK_TRUE; blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA; VkPipelineColorBlendStateCreateInfo colorBlendState{}; colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlendState.attachmentCount = 1; colorBlendState.pAttachments = &blendAttachmentState; VkPipelineDepthStencilStateCreateInfo depthStencilState{}; depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilState.depthTestEnable = VK_FALSE; depthStencilState.depthWriteEnable = VK_FALSE; depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS; VkPipelineViewportStateCreateInfo viewportState{}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; viewportState.flags = 0; VkPipelineMultisampleStateCreateInfo multisampleState{}; multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampleState.flags = 0; std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState{}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStateEnables.data(); dynamicState.dynamicStateCount = static_cast(dynamicStateEnables.size()); dynamicState.flags = 0; VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo{}; graphicsPipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; graphicsPipelineCreateInfo.layout = graphicsPipelineLayout; graphicsPipelineCreateInfo.renderPass = renderPass; graphicsPipelineCreateInfo.flags = 0; graphicsPipelineCreateInfo.basePipelineIndex = -1; graphicsPipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; graphicsPipelineCreateInfo.pVertexInputState = &vertexInputStateInfo; graphicsPipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; graphicsPipelineCreateInfo.pRasterizationState = &rasterizationState; graphicsPipelineCreateInfo.pColorBlendState = &colorBlendState; graphicsPipelineCreateInfo.pMultisampleState = &multisampleState; graphicsPipelineCreateInfo.pViewportState = &viewportState; graphicsPipelineCreateInfo.pDepthStencilState = &depthStencilState; graphicsPipelineCreateInfo.pDynamicState = &dynamicState; graphicsPipelineCreateInfo.stageCount = 2; graphicsPipelineCreateInfo.pStages = graphicsStageInfo; graphicsPipelineCreateInfo.renderPass = renderPass; #ifdef VULKANSC char graphicsUUID[VK_UUID_SIZE]; err = readPipelineUUID("data/pipeline/computeparticles/vk_computeparticles_pipeline_0.json", graphicsUUID); LOG("readPipelineUUID"); graphicsPipelineOfflineCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_OFFLINE_CREATE_INFO; memcpy(graphicsPipelineOfflineCreateInfo.pipelineIdentifier, graphicsUUID, VK_UUID_SIZE); // Obtained in json. graphicsPipelineOfflineCreateInfo.poolEntrySize = PIPELINE_SIZE; graphicsPipelineCreateInfo.pNext = &graphicsPipelineOfflineCreateInfo; err = vkCreateGraphicsPipelines(dev, pipelineCache, 1, &graphicsPipelineCreateInfo, nullptr, &graphicsPipeline); LOG("vkCreateGraphicsPipelines"); #else err = vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &graphicsPipelineCreateInfo, nullptr, &graphicsPipeline); LOG("vkCreateGraphicsPipelines"); #endif // #ifdef VULKANSC // Create compute pipeline VkComputePipelineCreateInfo computePipelineCreateInfo{}; computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; computePipelineCreateInfo.layout = computePipelineLayout; computePipelineCreateInfo.flags = 0; computePipelineCreateInfo.stage = computeStageInfo; #ifdef VULKANSC char computeUUID[VK_UUID_SIZE]; err = readPipelineUUID("data/pipeline/computeparticles/vk_computeparticles_pipeline_1.json", computeUUID); LOG("readPipelineUUID"); computePipelineOfflineCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_OFFLINE_CREATE_INFO; memcpy(computePipelineOfflineCreateInfo.pipelineIdentifier, computeUUID, VK_UUID_SIZE); // Obtained in json. computePipelineOfflineCreateInfo.poolEntrySize = PIPELINE_SIZE; computePipelineCreateInfo.pNext = &computePipelineOfflineCreateInfo; err = vkCreateComputePipelines(dev, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &computePipeline); LOG("vkCreateComputePipelines"); #else err = vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &computePipeline); LOG("vkCreateComputePipelines"); #endif // #ifdef VULKANSC return err; } static VkResult createSemaphores() { VkResult err; // Create semaphores for graphics and compute sync VkSemaphoreCreateInfo semaphoreCreateInfo{}; semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; err = vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &graphicsSemaphore); LOG("vkCreateSemaphore"); err = vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &computeSemaphore); LOG("vkCreateSemaphore"); // Signal the compute semaphore VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = &computeSemaphore; err = vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); LOG("vkQueueSubmit"); err = vkQueueWaitIdle(graphicsQueue); LOG("vkQueueWaitIdle"); return err; } static VkResult buildComputeCommandBuffer() { VkResult err; VkCommandBufferBeginInfo cmdBufBeginInfo{}; cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; err = vkBeginCommandBuffer(computeCmdBuffer, &cmdBufBeginInfo); LOG("vkBeginCommandBuffer"); // Add memory barrier to ensure that the (graphics) vertex shader has fetched attributes before compute starts to write to the buffer if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { VkBufferMemoryBarrier bufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, 0, VK_ACCESS_SHADER_WRITE_BIT, graphicsQueueFamilyIndex, computeQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( computeCmdBuffer, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &bufferBarrier, 0, nullptr); } // Dispatch the compute job vkCmdBindPipeline(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); vkCmdBindDescriptorSets(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSet, 0, 0); vkCmdDispatch(computeCmdBuffer, PARTICLE_COUNT / 256, 1, 1); // Add barrier to ensure that compute shader has finished writing to the buffer // Without this the (rendering) vertex shader may display incomplete results (partial data from last frame) if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { VkBufferMemoryBarrier bufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT, 0, computeQueueFamilyIndex, graphicsQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( computeCmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, &bufferBarrier, 0, nullptr); } err = vkEndCommandBuffer(computeCmdBuffer); LOG("vkEndCommandBuffer"); return err; } static VkResult transferStorageBuffer() { VkResult err; // If graphics and compute queue family indices differ, acquire and immediately release the // storage buffer, so that the initial acquire from the graphics command buffers are matched up properly // Allocate command buffer for transfer command VkCommandBuffer cmdBuffer; VkCommandBufferAllocateInfo cmdBufferAllocInfo{}; cmdBufferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufferAllocInfo.commandPool = computeCmdPool; cmdBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufferAllocInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufferAllocInfo, &cmdBuffer); LOG("vkAllocateCommandBuffers"); // Build transfer command VkCommandBufferBeginInfo cmdBufBeginInfo{}; cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; err = vkBeginCommandBuffer(cmdBuffer, &cmdBufBeginInfo); LOG("vkBeginCommandBuffer"); VkBufferMemoryBarrier acquireBufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, 0, VK_ACCESS_SHADER_WRITE_BIT, graphicsQueueFamilyIndex, computeQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( cmdBuffer, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &acquireBufferBarrier, 0, nullptr); VkBufferMemoryBarrier releaseBufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT, 0, computeQueueFamilyIndex, graphicsQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( cmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, &releaseBufferBarrier, 0, nullptr); err = vkEndCommandBuffer(cmdBuffer); LOG("vkEndCommandBuffer"); // Submit transfer command buffer to compute queue VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &cmdBuffer; VkFenceCreateInfo fenceCreateInfo{}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceCreateInfo.flags = 0; VkFence fence; err = vkCreateFence(dev, &fenceCreateInfo, nullptr, &fence); LOG("vkCreateFence"); err = vkQueueSubmit(computeQueue, 1, &submitInfo, fence); LOG("vkQueueSubmit"); err = vkWaitForFences(dev, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT); LOG("vkWaitForFences"); // Clean up after transfer vkDestroyFence(dev, fence, nullptr); vkFreeCommandBuffers(dev, computeCmdPool, 1, &cmdBuffer); return err; } static VkResult buildGraphicsCommandBuffer() { VkResult err; VkClearColorValue clearColor = { { 0.0, 0.0, 0.0, 1.0 } }; VkClearRect rect = { { {0,0}, {(uint32_t)width, (uint32_t)height} }, 0, 1 }; VkClearAttachment clearAttachment = { }; clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clearAttachment.colorAttachment = 0; clearAttachment.clearValue.color = clearColor; VkClearValue clearValues[2]; clearValues[0].color = { { 0.025f, 0.025f, 0.025f, 1.0f } }; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo{}; renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = width; renderPassBeginInfo.renderArea.extent.height = height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; VkCommandBufferBeginInfo cmdBufBeginInfo{}; cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; for (uint32_t i = 0; i < drawCmdBuffers.size(); i++) { renderPassBeginInfo.framebuffer = framebuffers[i]; err = vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufBeginInfo); LOG("vkBeginCommandBuffer"); // Acquire barrier if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { VkBufferMemoryBarrier bufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, 0, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, computeQueueFamilyIndex, graphicsQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( drawCmdBuffers[i], VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, &bufferBarrier, 0, nullptr); } vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); { // Update dynamic viewport state VkViewport viewport{}; viewport.height = (float)height; viewport.width = (float)width; viewport.minDepth = (float)0.0f; viewport.maxDepth = (float)1.0f; vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); // Update dynamic scissor state VkRect2D scissor{}; scissor.extent.width = width; scissor.extent.height = height; scissor.offset.x = 0; scissor.offset.y = 0; vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 0, 1, &graphicsDescriptorSet, 0, NULL); // Draw particles VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &storageBuffer, offsets); vkCmdClearAttachments(drawCmdBuffers[i], 1, &clearAttachment, 1, &rect); vkCmdDraw(drawCmdBuffers[i], PARTICLE_COUNT, 1, 0, 0); } vkCmdEndRenderPass(drawCmdBuffers[i]); // Release barrier if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { VkBufferMemoryBarrier bufferBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, 0, graphicsQueueFamilyIndex, computeQueueFamilyIndex, storageBuffer, 0, storageBufferSize }; vkCmdPipelineBarrier( drawCmdBuffers[i], VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &bufferBarrier, 0, nullptr); } err = vkEndCommandBuffer(drawCmdBuffers[i]); LOG("vkEndCommandBuffer"); } return err; } static VkResult draw() { VkResult err; uint32_t currentBufferIdx = 0; #ifdef VULKANSC if(enableWfd) { swapChainWfd.GetNexImage(¤tBufferIdx, nullptr); } #endif // #ifdef VULKANSC // Submit to graphics queue VkPipelineStageFlags graphicsWaitStageMasks[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT }; VkSubmitInfo graphicsSubmitInfo{}; graphicsSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; graphicsSubmitInfo.commandBufferCount = 1; graphicsSubmitInfo.pCommandBuffers = &drawCmdBuffers[currentBufferIdx]; graphicsSubmitInfo.waitSemaphoreCount = 1; graphicsSubmitInfo.pWaitSemaphores = &computeSemaphore; graphicsSubmitInfo.pWaitDstStageMask = graphicsWaitStageMasks; graphicsSubmitInfo.signalSemaphoreCount = 1; graphicsSubmitInfo.pSignalSemaphores = &graphicsSemaphore; err = vkQueueSubmit(graphicsQueue, 1, &graphicsSubmitInfo, VK_NULL_HANDLE); //LOG("vkQueueSubmit"); // Submit to compute queue VkPipelineStageFlags computeWaitStageMasks[] = { VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT }; VkSubmitInfo computeSubmitInfo{}; computeSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; computeSubmitInfo.commandBufferCount = 1; computeSubmitInfo.pCommandBuffers = &computeCmdBuffer; computeSubmitInfo.waitSemaphoreCount = 1; computeSubmitInfo.pWaitSemaphores = &graphicsSemaphore; computeSubmitInfo.pWaitDstStageMask = computeWaitStageMasks; computeSubmitInfo.signalSemaphoreCount = 1; computeSubmitInfo.pSignalSemaphores = &computeSemaphore; err = vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE); //LOG("vkQueueSubmit"); err = vkDeviceWaitIdle(dev); //LOG("vkDeviceWaitIdle"); #ifdef VULKANSC if(enableWfd) { swapChainWfd.PresentImage(nullptr); } #endif // #ifdef VULKANSC return err; } static VkResult render() { VkResult err; auto tStart = std::chrono::high_resolution_clock::now(); // Draw err = draw(); assert(err == VK_SUCCESS); err = updateUniformBuffers(); assert(err == VK_SUCCESS); // Update time if (animStart > 0.0f) { animStart -= frameTimer * 5.0f; } else if (animStart <= 0.0f) { timer += frameTimer * 0.04f; if (timer > 1.f) timer = 0.f; } auto tEnd = std::chrono::high_resolution_clock::now(); auto tDiff = std::chrono::duration(tEnd - tStart).count(); frameTimer = (float)tDiff / 1000.0f; timer += timerSpeed * frameTimer; if (timer > 1.0) timer -= 1.0f; ++frameCounter; return err; } static void writeOutPPMFile(uint8_t const* components, int const width, int const height, int const bytesPerPixel) { // Taken from Sascha Willems' screenshot app. Write out ppm file. std::ofstream file("particles.ppm", std::ios::out | std::ios::binary); if (file.fail()) { std::cerr << "Failed to open output file tri.ppm. Skipping file output!" << std::endl; return; } file << "P3\n" << width << " " << height << "\n" << 255 << "\n"; for (int x = 0; x < width * height; x++) { file << (unsigned int)components[2] << " " << (unsigned int)components[1] << " " << (unsigned int)components[0] << "\n"; components += bytesPerPixel; } file.close(); std::cout << "wrote out file." << std::endl; } static VkResult issueCopyImage() { VkResult err; VkCommandBufferBeginInfo cmdBufInfo{}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; err = vkBeginCommandBuffer(copyCmdBuffer, &cmdBufInfo); LOG("vkBeginCommandBuffer copy"); { // Otherwise use image copy (requires us to manually flip components) VkImageCopy imageCopyRegion{}; imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageCopyRegion.srcSubresource.layerCount = 1; imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageCopyRegion.dstSubresource.layerCount = 1; imageCopyRegion.extent.width = width; imageCopyRegion.extent.height = height; imageCopyRegion.extent.depth = 1; // Issue the copy command vkCmdCopyImage( copyCmdBuffer, offscreenImages[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion); } err = vkEndCommandBuffer(copyCmdBuffer); LOG("vkEndCommandBuffer copy"); VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pWaitDstStageMask = &pipelineStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = ©CmdBuffer; err = vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); LOG("vkQueueSubmit"); err = vkQueueWaitIdle(graphicsQueue); LOG("vkQueueWaitIdle"); // We're trying to render to a pitch linear buffer and read it back. uint8_t* imageData{ nullptr }; err = vkMapMemory(dev, copyImageMem, 0, VK_WHOLE_SIZE, 0, (void**)&imageData); LOG("vkMapMemory"); if (outputImageFile) { writeOutPPMFile(imageData, width, height, 4); } return err; } static VkResult cleanup() { vkDestroyBuffer(dev, storageBuffer, nullptr); vkDestroyBuffer(dev, uniformBuffer, nullptr); vkDestroyRenderPass(dev, renderPass, nullptr); for(uint32_t i = 0; i < framebuffers.size(); i++) { vkDestroyFramebuffer(dev, framebuffers[i], nullptr); vkDestroyImageView(dev, offscreenImageViews[i], nullptr); vkDestroyImage(dev, offscreenImages[i], nullptr); } vkDestroyImageView(dev, particleImageDescriptor.imageView, nullptr); vkDestroyImage(dev, particleImage, nullptr); vkDestroySampler(dev, particleImageDescriptor.sampler, nullptr); vkDestroyImageView(dev, gradientImageDescriptor.imageView, nullptr); vkDestroyImage(dev, gradientImage, nullptr); vkDestroySampler(dev, gradientImageDescriptor.sampler, nullptr); vkDestroyImage(dev, copyImage, nullptr); vkFreeCommandBuffers(dev, graphicsCmdPool, drawCmdBuffers.size(), drawCmdBuffers.data()); if (copyCmdBuffer != VK_NULL_HANDLE) { vkFreeCommandBuffers(dev, graphicsCmdPool, 1, ©CmdBuffer); } vkFreeCommandBuffers(dev, computeCmdPool, 1, &computeCmdBuffer); vkDestroyDescriptorSetLayout(dev, graphicsDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(dev, computeDescriptorSetLayout, nullptr); vkDestroyPipelineLayout(dev, graphicsPipelineLayout, nullptr); vkDestroyPipelineLayout(dev, computePipelineLayout, nullptr); vkDestroyPipeline(dev, graphicsPipeline, nullptr); vkDestroyPipeline(dev, computePipeline, nullptr); vkFreeDescriptorSets(dev, descriptorPool, 1, &graphicsDescriptorSet); vkFreeDescriptorSets(dev, descriptorPool, 1, &computeDescriptorSet); vkResetDescriptorPool(dev, descriptorPool, 0); vkDestroySemaphore(dev, graphicsSemaphore, nullptr); vkDestroySemaphore(dev, computeSemaphore, nullptr); #ifdef VULKANSC vkDestroyPipelineCache(dev, pipelineCache, nullptr); #else vkDestroyCommandPool(dev, graphicsCmdPool, nullptr); vkDestroyCommandPool(dev, computeCmdPool, nullptr); vkFreeMemory(dev, particleImageMem, nullptr); vkFreeMemory(dev, gradientImageMem, nullptr); for(uint32_t i = 0; i < offscreenImageMems.size(); i++) { vkFreeMemory(dev, offscreenImageMems[i], nullptr); } vkFreeMemory(dev, copyImageMem, nullptr); vkFreeMemory(dev, storageMemory, nullptr); vkFreeMemory(dev, uniformMemory, nullptr); vkDestroyDescriptorPool(dev, descriptorPool, nullptr); vkDestroyShaderModule(dev, vertexShaderModule, nullptr); vkDestroyShaderModule(dev, fragmentShaderModule, nullptr); vkDestroyShaderModule(dev, computeShaderModule, nullptr); #endif // #ifdef VULKANSC vkDestroyDevice(dev, NULL); vkDestroyInstance(instance, NULL); return VK_SUCCESS; } int main(int argc, char** argv) { #ifdef VULKANSC std::cout << "VKSC BUILD" << std::endl; #else std::cout << "VK BUILD" << std::endl; #endif bool earlyExit = parseCommandLineArgs(argc, argv); if (earlyExit) return 0; VkResult err = VK_SUCCESS; err = createInstanceAndDevice(); assert(err == VK_SUCCESS); err = setupRenderPass(); assert(err == VK_SUCCESS); err = createShaderStageInfo(); assert(err == VK_SUCCESS); #ifdef VULKANSC if (enableWfd) { err = swapChainWfd.Init(instance, physdevs[0], dev, width, height); assert(err == VK_SUCCESS); } #endif // #ifdef VULKANSC err = createCommandBuffers(); assert(err == VK_SUCCESS); err = setupFrameBuffer(); assert(err == VK_SUCCESS); #ifdef VULKANSC err = createPipelineCaches(); assert(err == VK_SUCCESS); if (!enableWfd) #endif // #ifdef VULKANSC { err = setupCopyImage(); assert(err == VK_SUCCESS); } err = loadTexture2D("data/textures/particle_gradient_rgba.ktx", &gradientImage, &gradientImageMem, &gradientImageDescriptor); assert(err == VK_SUCCESS); err = loadTexture2D("data/textures/particle01_rgba.ktx", &particleImage, &particleImageMem, &particleImageDescriptor); assert(err == VK_SUCCESS); err = createDescriptorPool(); assert(err == VK_SUCCESS); err = createStorageBuffers(); assert(err == VK_SUCCESS); err = createUniformBuffers(); assert(err == VK_SUCCESS); err = createDescriptorSetLayouts(); assert(err == VK_SUCCESS); err = setupDescriptorSets(); assert(err == VK_SUCCESS); err = createPipelines(); assert(err == VK_SUCCESS); err = createSemaphores(); assert(err == VK_SUCCESS); err = buildComputeCommandBuffer(); assert(err == VK_SUCCESS); if (graphicsQueueFamilyIndex != computeQueueFamilyIndex) { err = transferStorageBuffer(); assert(err == VK_SUCCESS); } err = buildGraphicsCommandBuffer(); assert(err == VK_SUCCESS); #ifdef VULKANSC while (1) #endif // #ifdef VULKANSC { err = render(); assert(err == VK_SUCCESS); #ifdef VULKANSC if (outputImageFile) break; #endif // #ifdef VULKANSC } #ifdef VULKANSC if (!enableWfd) #endif // #ifdef VULKANSC { err = issueCopyImage(); assert(err == VK_SUCCESS); } err = cleanup(); assert(err == VK_SUCCESS); (void) err; return 0; }