Created
May 2, 2021 17:46
-
-
Save dov/8c9aa60d870bbe0f7389941af2a1e227 to your computer and use it in GitHub Desktop.
A Vulkan renderheadless example with two render passes (not working)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Minimal makefile for multipass-minimal | |
%.o : %.cpp | |
g++ -o $@ -c -Wall -g -std=c++17 $? | |
multipass-minimal: multipass-minimal.o VulkanTools.o | |
g++ -o $@ $? -lglfw -lvulkan | |
clean: | |
$(RM) multipass-minimal.o multipass-minimal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Based on: https://github.com/SaschaWillems/Vulkan/blob/master/examples/renderheadless/renderheadless.cpp | |
* | |
* Vulkan Example - Minimal headless rendering example | |
* | |
* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de | |
* | |
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) | |
* | |
* | |
* Modified by Dov Grobgeld <dov.grobgeld@gmail.com> to use split | |
* clearing and drawing into two passes. | |
* | |
*/ | |
#if defined(_WIN32) | |
#pragma comment(linker, "/subsystem:console") | |
#elif defined(VK_USE_PLATFORM_ANDROID_KHR) | |
#include <android/native_activity.h> | |
#include <android/asset_manager.h> | |
#include <android_native_app_glue.h> | |
#include <android/log.h> | |
#include "VulkanAndroid.h" | |
#endif | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <vector> | |
#include <array> | |
#include <iostream> | |
#include <algorithm> | |
#define GLM_FORCE_RADIANS | |
#define GLM_FORCE_DEPTH_ZERO_TO_ONE | |
#include <glm/glm.hpp> | |
#include <glm/gtc/matrix_transform.hpp> | |
#include <vulkan/vulkan.h> | |
#include "VulkanTools.h" | |
#include <memory> | |
using namespace std; | |
#define DEBUG (!NDEBUG) | |
#define BUFFER_ELEMENTS 32 | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
#define LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__)) | |
#else | |
#define LOG(...) printf(__VA_ARGS__) | |
#endif | |
static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback( | |
VkDebugReportFlagsEXT flags, | |
VkDebugReportObjectTypeEXT objectType, | |
uint64_t object, | |
size_t location, | |
int32_t messageCode, | |
const char* pLayerPrefix, | |
const char* pMessage, | |
void* pUserData) | |
{ | |
LOG("[VALIDATION]: %s - %s\n", pLayerPrefix, pMessage); | |
return VK_FALSE; | |
} | |
class VulkanExample | |
{ | |
public: | |
VkInstance instance; | |
VkPhysicalDevice physicalDevice; | |
VkDevice device; | |
uint32_t queueFamilyIndex; | |
VkPipelineCache pipelineCache; | |
VkQueue queue; | |
VkCommandPool commandPool; | |
VkCommandBuffer commandBuffer; | |
VkDescriptorSetLayout descriptorSetLayout; | |
VkPipelineLayout pipelineLayout; | |
VkPipeline pipeline; | |
std::vector<VkShaderModule> shaderModules; | |
VkBuffer vertexBuffer, indexBuffer; | |
VkDeviceMemory vertexMemory, indexMemory; | |
struct FrameBufferAttachment { | |
VkImage image; | |
VkDeviceMemory memory; | |
VkImageView view; | |
}; | |
int32_t width, height; | |
VkFramebuffer framebuffer; | |
FrameBufferAttachment colorAttachment, depthAttachment; | |
VkRenderPass renderPass; | |
VkDebugReportCallbackEXT debugReportCallback{}; | |
uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) { | |
VkPhysicalDeviceMemoryProperties deviceMemoryProperties; | |
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); | |
for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) { | |
if ((typeBits & 1) == 1) { | |
if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { | |
return i; | |
} | |
} | |
typeBits >>= 1; | |
} | |
return 0; | |
} | |
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr) | |
{ | |
// Create the buffer handle | |
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size); | |
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer)); | |
// Create the memory backing up the buffer handle | |
VkMemoryRequirements memReqs; | |
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); | |
vkGetBufferMemoryRequirements(device, *buffer, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, memoryPropertyFlags); | |
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory)); | |
if (data != nullptr) { | |
void *mapped; | |
VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped)); | |
memcpy(mapped, data, size); | |
vkUnmapMemory(device, *memory); | |
} | |
VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0)); | |
return VK_SUCCESS; | |
} | |
/* | |
Submit command buffer to a queue and wait for fence until queue operations have been finished | |
*/ | |
void submitWork(VkCommandBuffer cmdBuffer, VkQueue queue) | |
{ | |
VkSubmitInfo submitInfo = vks::initializers::submitInfo(); | |
submitInfo.commandBufferCount = 1; | |
submitInfo.pCommandBuffers = &cmdBuffer; | |
VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(); | |
VkFence fence; | |
VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence)); | |
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); | |
VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX)); | |
vkDestroyFence(device, fence, nullptr); | |
} | |
VulkanExample() | |
{ | |
LOG("Running headless rendering example\n"); | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
LOG("loading vulkan lib"); | |
vks::android::loadVulkanLibrary(); | |
#endif | |
VkApplicationInfo appInfo = {}; | |
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
appInfo.pApplicationName = "Vulkan headless example"; | |
appInfo.pEngineName = "VulkanExample"; | |
appInfo.apiVersion = VK_API_VERSION_1_0; | |
/* | |
Vulkan instance creation (without surface extensions) | |
*/ | |
VkInstanceCreateInfo instanceCreateInfo = {}; | |
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
instanceCreateInfo.pApplicationInfo = &appInfo; | |
uint32_t layerCount = 0; | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
const char* validationLayers[] = { "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_object_tracker","VK_LAYER_LUNARG_core_validation", "VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects" }; | |
layerCount = 6; | |
#else | |
const char* validationLayers[] = { "VK_LAYER_LUNARG_standard_validation" }; | |
layerCount = 1; | |
#endif | |
#if DEBUG | |
// Check if layers are available | |
uint32_t instanceLayerCount; | |
vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); | |
std::vector<VkLayerProperties> instanceLayers(instanceLayerCount); | |
vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayers.data()); | |
bool layersAvailable = true; | |
for (auto layerName : validationLayers) { | |
bool layerAvailable = false; | |
for (auto instanceLayer : instanceLayers) { | |
if (strcmp(instanceLayer.layerName, layerName) == 0) { | |
layerAvailable = true; | |
break; | |
} | |
} | |
if (!layerAvailable) { | |
layersAvailable = false; | |
break; | |
} | |
} | |
if (layersAvailable) { | |
instanceCreateInfo.ppEnabledLayerNames = validationLayers; | |
const char *validationExt = VK_EXT_DEBUG_REPORT_EXTENSION_NAME; | |
instanceCreateInfo.enabledLayerCount = layerCount; | |
instanceCreateInfo.enabledExtensionCount = 1; | |
instanceCreateInfo.ppEnabledExtensionNames = &validationExt; | |
} | |
#endif | |
VK_CHECK_RESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
vks::android::loadVulkanFunctions(instance); | |
#endif | |
#if DEBUG | |
if (layersAvailable) { | |
VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo = {}; | |
debugReportCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; | |
debugReportCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; | |
debugReportCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)debugMessageCallback; | |
// We have to explicitly load this function. | |
PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); | |
assert(vkCreateDebugReportCallbackEXT); | |
VK_CHECK_RESULT(vkCreateDebugReportCallbackEXT(instance, &debugReportCreateInfo, nullptr, &debugReportCallback)); | |
} | |
#endif | |
/* | |
Vulkan device creation | |
*/ | |
uint32_t deviceCount = 0; | |
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr)); | |
std::vector<VkPhysicalDevice> physicalDevices(deviceCount); | |
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data())); | |
physicalDevice = physicalDevices[0]; | |
VkPhysicalDeviceProperties deviceProperties; | |
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); | |
LOG("GPU: %s\n", deviceProperties.deviceName); | |
// Request a single graphics queue | |
const float defaultQueuePriority(0.0f); | |
VkDeviceQueueCreateInfo queueCreateInfo = {}; | |
uint32_t queueFamilyCount; | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); | |
std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount); | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data()); | |
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++) { | |
if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | |
queueFamilyIndex = i; | |
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
queueCreateInfo.queueFamilyIndex = i; | |
queueCreateInfo.queueCount = 1; | |
queueCreateInfo.pQueuePriorities = &defaultQueuePriority; | |
break; | |
} | |
} | |
// Create logical device | |
VkDeviceCreateInfo deviceCreateInfo = {}; | |
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
deviceCreateInfo.queueCreateInfoCount = 1; | |
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; | |
VK_CHECK_RESULT(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)); | |
// Get a graphics queue | |
vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue); | |
// Command pool | |
VkCommandPoolCreateInfo cmdPoolInfo = {}; | |
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
cmdPoolInfo.queueFamilyIndex = queueFamilyIndex; | |
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; | |
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool)); | |
/* | |
Prepare vertex and index buffers | |
*/ | |
struct Vertex { | |
float position[3]; | |
float color[3]; | |
}; | |
{ | |
std::vector<Vertex> vertices = { | |
{ { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, | |
{ { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, | |
{ { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } } | |
}; | |
std::vector<uint32_t> indices = { 0, 1, 2 }; | |
const VkDeviceSize vertexBufferSize = vertices.size() * sizeof(Vertex); | |
const VkDeviceSize indexBufferSize = indices.size() * sizeof(uint32_t); | |
VkBuffer stagingBuffer; | |
VkDeviceMemory stagingMemory; | |
// Command buffer for copy commands (reused) | |
VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); | |
VkCommandBuffer copyCmd; | |
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); | |
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); | |
// Copy input data to VRAM using a staging buffer | |
{ | |
// Vertices | |
createBuffer( | |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, | |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, | |
&stagingBuffer, | |
&stagingMemory, | |
vertexBufferSize, | |
vertices.data()); | |
createBuffer( | |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, | |
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, | |
&vertexBuffer, | |
&vertexMemory, | |
vertexBufferSize); | |
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
VkBufferCopy copyRegion = {}; | |
copyRegion.size = vertexBufferSize; | |
vkCmdCopyBuffer(copyCmd, stagingBuffer, vertexBuffer, 1, ©Region); | |
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
submitWork(copyCmd, queue); | |
vkDestroyBuffer(device, stagingBuffer, nullptr); | |
vkFreeMemory(device, stagingMemory, nullptr); | |
// Indices | |
createBuffer( | |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, | |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, | |
&stagingBuffer, | |
&stagingMemory, | |
indexBufferSize, | |
indices.data()); | |
createBuffer( | |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, | |
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, | |
&indexBuffer, | |
&indexMemory, | |
indexBufferSize); | |
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
copyRegion.size = indexBufferSize; | |
vkCmdCopyBuffer(copyCmd, stagingBuffer, indexBuffer, 1, ©Region); | |
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
submitWork(copyCmd, queue); | |
vkDestroyBuffer(device, stagingBuffer, nullptr); | |
vkFreeMemory(device, stagingMemory, nullptr); | |
} | |
} | |
/* | |
Create framebuffer attachments | |
*/ | |
width = 1024; | |
height = 1024; | |
VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
VkFormat depthFormat; | |
vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat); | |
{ | |
// Color attachment | |
VkImageCreateInfo image = vks::initializers::imageCreateInfo(); | |
image.imageType = VK_IMAGE_TYPE_2D; | |
image.format = colorFormat; | |
image.extent.width = width; | |
image.extent.height = height; | |
image.extent.depth = 1; | |
image.mipLevels = 1; | |
image.arrayLayers = 1; | |
image.samples = VK_SAMPLE_COUNT_1_BIT; | |
image.tiling = VK_IMAGE_TILING_OPTIMAL; | |
image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; | |
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); | |
VkMemoryRequirements memReqs; | |
VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &colorAttachment.image)); | |
vkGetImageMemoryRequirements(device, colorAttachment.image, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); | |
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &colorAttachment.memory)); | |
VK_CHECK_RESULT(vkBindImageMemory(device, colorAttachment.image, colorAttachment.memory, 0)); | |
VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo(); | |
colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
colorImageView.format = colorFormat; | |
colorImageView.subresourceRange = {}; | |
colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
colorImageView.subresourceRange.baseMipLevel = 0; | |
colorImageView.subresourceRange.levelCount = 1; | |
colorImageView.subresourceRange.baseArrayLayer = 0; | |
colorImageView.subresourceRange.layerCount = 1; | |
colorImageView.image = colorAttachment.image; | |
VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &colorAttachment.view)); | |
// Depth stencil attachment | |
image.format = depthFormat; | |
image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; | |
VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthAttachment.image)); | |
vkGetImageMemoryRequirements(device, depthAttachment.image, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); | |
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depthAttachment.memory)); | |
VK_CHECK_RESULT(vkBindImageMemory(device, depthAttachment.image, depthAttachment.memory, 0)); | |
VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo(); | |
depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
depthStencilView.format = depthFormat; | |
depthStencilView.flags = 0; | |
depthStencilView.subresourceRange = {}; | |
depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; | |
depthStencilView.subresourceRange.baseMipLevel = 0; | |
depthStencilView.subresourceRange.levelCount = 1; | |
depthStencilView.subresourceRange.baseArrayLayer = 0; | |
depthStencilView.subresourceRange.layerCount = 1; | |
depthStencilView.image = depthAttachment.image; | |
VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthAttachment.view)); | |
} | |
/* | |
Create renderpass | |
*/ | |
{ | |
std::array<VkAttachmentDescription, 2> attchmentDescriptions = {}; | |
// Color attachment | |
attchmentDescriptions[0].format = colorFormat; | |
attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT; | |
attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; | |
attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; | |
// Depth attachment | |
attchmentDescriptions[1].format = depthFormat; | |
attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT; | |
attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; | |
attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; | |
VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; | |
VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; | |
VkSubpassDescription subpassDescription = {}; | |
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; | |
subpassDescription.colorAttachmentCount = 1; | |
subpassDescription.pColorAttachments = &colorReference; | |
subpassDescription.pDepthStencilAttachment = &depthReference; | |
// Use subpass dependencies for layout transitions | |
std::array<VkSubpassDependency, 1> dependencies; | |
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; | |
dependencies[0].dstSubpass = 0; | |
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; | |
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | |
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; | |
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; | |
// Create the actual renderpass | |
VkRenderPassCreateInfo renderPassInfo = {}; | |
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; | |
renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size()); | |
renderPassInfo.pAttachments = attchmentDescriptions.data(); | |
renderPassInfo.subpassCount = 1; | |
renderPassInfo.pSubpasses = &subpassDescription; | |
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size()); | |
renderPassInfo.pDependencies = dependencies.data(); | |
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); | |
VkImageView attachments[2]; | |
attachments[0] = colorAttachment.view; | |
attachments[1] = depthAttachment.view; | |
VkFramebufferCreateInfo framebufferCreateInfo = vks::initializers::framebufferCreateInfo(); | |
framebufferCreateInfo.renderPass = renderPass; | |
framebufferCreateInfo.attachmentCount = 2; | |
framebufferCreateInfo.pAttachments = attachments; | |
framebufferCreateInfo.width = width; | |
framebufferCreateInfo.height = height; | |
framebufferCreateInfo.layers = 1; | |
VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &framebuffer)); | |
} | |
/* | |
Prepare graphics pipeline | |
*/ | |
{ | |
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {}; | |
VkDescriptorSetLayoutCreateInfo descriptorLayout = | |
vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); | |
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); | |
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = | |
vks::initializers::pipelineLayoutCreateInfo(nullptr, 0); | |
// MVP via push constant block | |
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0); | |
pipelineLayoutCreateInfo.pushConstantRangeCount = 1; | |
pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; | |
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); | |
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; | |
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; | |
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); | |
// Create pipeline | |
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = | |
vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); | |
VkPipelineRasterizationStateCreateInfo rasterizationState = | |
vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE); | |
VkPipelineColorBlendAttachmentState blendAttachmentState = | |
vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); | |
VkPipelineColorBlendStateCreateInfo colorBlendState = | |
vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); | |
VkPipelineDepthStencilStateCreateInfo depthStencilState = | |
vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); | |
VkPipelineViewportStateCreateInfo viewportState = | |
vks::initializers::pipelineViewportStateCreateInfo(1, 1); | |
VkPipelineMultisampleStateCreateInfo multisampleState = | |
vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); | |
std::vector<VkDynamicState> dynamicStateEnables = { | |
VK_DYNAMIC_STATE_VIEWPORT, | |
VK_DYNAMIC_STATE_SCISSOR | |
}; | |
VkPipelineDynamicStateCreateInfo dynamicState = | |
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); | |
VkGraphicsPipelineCreateInfo pipelineCreateInfo = | |
vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); | |
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{}; | |
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; | |
pipelineCreateInfo.pRasterizationState = &rasterizationState; | |
pipelineCreateInfo.pColorBlendState = &colorBlendState; | |
pipelineCreateInfo.pMultisampleState = &multisampleState; | |
pipelineCreateInfo.pViewportState = &viewportState; | |
pipelineCreateInfo.pDepthStencilState = &depthStencilState; | |
pipelineCreateInfo.pDynamicState = &dynamicState; | |
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size()); | |
pipelineCreateInfo.pStages = shaderStages.data(); | |
// Vertex bindings an attributes | |
// Binding description | |
std::vector<VkVertexInputBindingDescription> vertexInputBindings = { | |
vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), | |
}; | |
// Attribute descriptions | |
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = { | |
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position | |
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Color | |
}; | |
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); | |
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size()); | |
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); | |
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size()); | |
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); | |
pipelineCreateInfo.pVertexInputState = &vertexInputState; | |
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; | |
shaderStages[0].pName = "main"; | |
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; | |
shaderStages[1].pName = "main"; | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
shaderStages[0].module = vks::tools::loadShader(androidapp->activity->assetManager, ASSET_PATH "shaders/triangle.vert.spv", device); | |
shaderStages[1].module = vks::tools::loadShader(androidapp->activity->assetManager, ASSET_PATH "shaders/triangle.frag.spv", device); | |
#else | |
shaderStages[0].module = vks::tools::loadShader(ASSET_PATH "shaders/triangle.vert.spv", device); | |
shaderStages[1].module = vks::tools::loadShader(ASSET_PATH "shaders/triangle.frag.spv", device); | |
#endif | |
shaderModules = { shaderStages[0].module, shaderStages[1].module }; | |
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); | |
} | |
/* | |
Command buffer creation | |
*/ | |
{ | |
VkCommandBuffer commandBuffer; | |
VkCommandBufferAllocateInfo cmdBufAllocateInfo = | |
vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); | |
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer)); | |
VkCommandBufferBeginInfo cmdBufInfo = | |
vks::initializers::commandBufferBeginInfo(); | |
VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo)); | |
VkRenderPassBeginInfo renderPassBeginInfo = {}; | |
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |
renderPassBeginInfo.renderArea.extent.width = width; | |
renderPassBeginInfo.renderArea.extent.height = height; | |
renderPassBeginInfo.clearValueCount = 0; | |
renderPassBeginInfo.pClearValues = nullptr; | |
renderPassBeginInfo.renderPass = renderPass; | |
renderPassBeginInfo.framebuffer = framebuffer; | |
VkViewport viewport = {}; | |
viewport.height = (float)height; | |
viewport.width = (float)width; | |
viewport.minDepth = (float)0.0f; | |
viewport.maxDepth = (float)1.0f; | |
vkCmdSetViewport(commandBuffer, 0, 1, &viewport); | |
// Update dynamic scissor state | |
VkRect2D scissor = {}; | |
scissor.extent.width = width; | |
scissor.extent.height = height; | |
vkCmdSetScissor(commandBuffer, 0, 1, &scissor); | |
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | |
const VkClearAttachment clearAttachements[2] = { | |
{ | |
VK_IMAGE_ASPECT_COLOR_BIT, | |
0, | |
{ 0.0f,0.0f,0.2f,1.0f }, | |
}, | |
{ | |
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, | |
0, | |
{ 1.0f,0}, | |
} | |
}; | |
const VkClearRect rects[1] = { | |
{ | |
{ | |
{0,0}, | |
{(uint32_t)width,(uint32_t)height}, | |
}, | |
0,1 | |
} | |
}; | |
vkCmdClearAttachments(commandBuffer, | |
2, // attachmentCount | |
clearAttachements, | |
1, // rectCount | |
rects); | |
vkCmdEndRenderPass(commandBuffer); | |
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); | |
submitWork(commandBuffer, queue); | |
vkDeviceWaitIdle(device); | |
// 2nd pass draw some geometry | |
{ | |
vkResetCommandBuffer(commandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); | |
VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo)); | |
if (0) { // If I turn this on the background is cleared! | |
// Reuse the earlier render render pass. | |
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
// TBD - Draw here! | |
vkCmdEndRenderPass(commandBuffer); | |
} | |
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); | |
} | |
submitWork(commandBuffer, queue); | |
vkDeviceWaitIdle(device); | |
} | |
/* | |
Copy framebuffer image to host visible image | |
*/ | |
const char* imagedata; | |
{ | |
// Create the linear tiled destination image to copy to and to read the memory from | |
VkImageCreateInfo imgCreateInfo(vks::initializers::imageCreateInfo()); | |
imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; | |
imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; | |
imgCreateInfo.extent.width = width; | |
imgCreateInfo.extent.height = height; | |
imgCreateInfo.extent.depth = 1; | |
imgCreateInfo.arrayLayers = 1; | |
imgCreateInfo.mipLevels = 1; | |
imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; | |
imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; | |
imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; | |
// Create the image | |
VkImage dstImage; | |
VK_CHECK_RESULT(vkCreateImage(device, &imgCreateInfo, nullptr, &dstImage)); | |
// Create memory to back up the image | |
VkMemoryRequirements memRequirements; | |
VkMemoryAllocateInfo memAllocInfo(vks::initializers::memoryAllocateInfo()); | |
VkDeviceMemory dstImageMemory; | |
vkGetImageMemoryRequirements(device, dstImage, &memRequirements); | |
memAllocInfo.allocationSize = memRequirements.size; | |
// Memory must be host visible to copy from | |
memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); | |
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory)); | |
VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0)); | |
// Do the actual blit from the offscreen image to our host visible destination image | |
VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); | |
VkCommandBuffer copyCmd; | |
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); | |
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); | |
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
// Transition destination image to transfer destination layout | |
vks::tools::insertImageMemoryBarrier( | |
copyCmd, | |
dstImage, | |
0, | |
VK_ACCESS_TRANSFER_WRITE_BIT, | |
VK_IMAGE_LAYOUT_UNDEFINED, | |
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); | |
// colorAttachment.image is already in VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, and does not need to be transitioned | |
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; | |
vkCmdCopyImage( | |
copyCmd, | |
colorAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, | |
dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
1, | |
&imageCopyRegion); | |
// Transition destination image to general layout, which is the required layout for mapping the image memory later on | |
vks::tools::insertImageMemoryBarrier( | |
copyCmd, | |
dstImage, | |
VK_ACCESS_TRANSFER_WRITE_BIT, | |
VK_ACCESS_MEMORY_READ_BIT, | |
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
VK_IMAGE_LAYOUT_GENERAL, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); | |
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
submitWork(copyCmd, queue); | |
// Get layout of the image (including row pitch) | |
VkImageSubresource subResource{}; | |
subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
VkSubresourceLayout subResourceLayout; | |
vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout); | |
// Map image memory so we can start copying from it | |
vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&imagedata); | |
imagedata += subResourceLayout.offset; | |
/* | |
Save host visible framebuffer image to disk (ppm format) | |
*/ | |
const char* filename = "multipass.ppm"; | |
std::ofstream file(filename, std::ios::out | std::ios::binary); | |
// ppm header | |
file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n"; | |
// If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components | |
// Check if source is BGR and needs swizzle | |
std::vector<VkFormat> formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM }; | |
const bool colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), VK_FORMAT_R8G8B8A8_UNORM) != formatsBGR.end()); | |
// ppm binary pixel data | |
for (int32_t y = 0; y < height; y++) { | |
unsigned int *row = (unsigned int*)imagedata; | |
for (int32_t x = 0; x < width; x++) { | |
if (colorSwizzle) { | |
file.write((char*)row + 2, 1); | |
file.write((char*)row + 1, 1); | |
file.write((char*)row, 1); | |
} | |
else { | |
file.write((char*)row, 3); | |
} | |
row++; | |
} | |
imagedata += subResourceLayout.rowPitch; | |
} | |
file.close(); | |
LOG("Framebuffer image saved to %s\n", filename); | |
// Clean up resources | |
vkUnmapMemory(device, dstImageMemory); | |
vkFreeMemory(device, dstImageMemory, nullptr); | |
vkDestroyImage(device, dstImage, nullptr); | |
} | |
vkQueueWaitIdle(queue); | |
} | |
~VulkanExample() | |
{ | |
vkDestroyBuffer(device, vertexBuffer, nullptr); | |
vkFreeMemory(device, vertexMemory, nullptr); | |
vkDestroyBuffer(device, indexBuffer, nullptr); | |
vkFreeMemory(device, indexMemory, nullptr); | |
vkDestroyImageView(device, colorAttachment.view, nullptr); | |
vkDestroyImage(device, colorAttachment.image, nullptr); | |
vkFreeMemory(device, colorAttachment.memory, nullptr); | |
vkDestroyImageView(device, depthAttachment.view, nullptr); | |
vkDestroyImage(device, depthAttachment.image, nullptr); | |
vkFreeMemory(device, depthAttachment.memory, nullptr); | |
vkDestroyRenderPass(device, renderPass, nullptr); | |
vkDestroyFramebuffer(device, framebuffer, nullptr); | |
vkDestroyPipelineLayout(device, pipelineLayout, nullptr); | |
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); | |
vkDestroyPipeline(device, pipeline, nullptr); | |
vkDestroyPipelineCache(device, pipelineCache, nullptr); | |
vkDestroyCommandPool(device, commandPool, nullptr); | |
for (auto shadermodule : shaderModules) { | |
vkDestroyShaderModule(device, shadermodule, nullptr); | |
} | |
vkDestroyDevice(device, nullptr); | |
#if DEBUG | |
if (debugReportCallback) { | |
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); | |
assert(vkDestroyDebugReportCallback); | |
vkDestroyDebugReportCallback(instance, debugReportCallback, nullptr); | |
} | |
#endif | |
vkDestroyInstance(instance, nullptr); | |
#if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
vks::android::freeVulkanLibrary(); | |
#endif | |
} | |
}; | |
int main() { | |
auto vulkan_example = make_shared<VulkanExample>(); | |
return 0; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Assorted commonly used Vulkan helper functions | |
* | |
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de | |
* | |
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) | |
*/ | |
#include "VulkanTools.h" | |
namespace vks | |
{ | |
namespace tools | |
{ | |
bool errorModeSilent = false; | |
std::string errorString(VkResult errorCode) | |
{ | |
switch (errorCode) | |
{ | |
#define STR(r) case VK_ ##r: return #r | |
STR(NOT_READY); | |
STR(TIMEOUT); | |
STR(EVENT_SET); | |
STR(EVENT_RESET); | |
STR(INCOMPLETE); | |
STR(ERROR_OUT_OF_HOST_MEMORY); | |
STR(ERROR_OUT_OF_DEVICE_MEMORY); | |
STR(ERROR_INITIALIZATION_FAILED); | |
STR(ERROR_DEVICE_LOST); | |
STR(ERROR_MEMORY_MAP_FAILED); | |
STR(ERROR_LAYER_NOT_PRESENT); | |
STR(ERROR_EXTENSION_NOT_PRESENT); | |
STR(ERROR_FEATURE_NOT_PRESENT); | |
STR(ERROR_INCOMPATIBLE_DRIVER); | |
STR(ERROR_TOO_MANY_OBJECTS); | |
STR(ERROR_FORMAT_NOT_SUPPORTED); | |
STR(ERROR_SURFACE_LOST_KHR); | |
STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); | |
STR(SUBOPTIMAL_KHR); | |
STR(ERROR_OUT_OF_DATE_KHR); | |
STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); | |
STR(ERROR_VALIDATION_FAILED_EXT); | |
STR(ERROR_INVALID_SHADER_NV); | |
#undef STR | |
default: | |
return "UNKNOWN_ERROR"; | |
} | |
} | |
std::string physicalDeviceTypeString(VkPhysicalDeviceType type) | |
{ | |
switch (type) | |
{ | |
#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r | |
STR(OTHER); | |
STR(INTEGRATED_GPU); | |
STR(DISCRETE_GPU); | |
STR(VIRTUAL_GPU); | |
#undef STR | |
default: return "UNKNOWN_DEVICE_TYPE"; | |
} | |
} | |
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat) | |
{ | |
// Since all depth formats may be optional, we need to find a suitable depth format to use | |
// Start with the highest precision packed format | |
std::vector<VkFormat> depthFormats = { | |
VK_FORMAT_D32_SFLOAT_S8_UINT, | |
VK_FORMAT_D32_SFLOAT, | |
VK_FORMAT_D24_UNORM_S8_UINT, | |
VK_FORMAT_D16_UNORM_S8_UINT, | |
VK_FORMAT_D16_UNORM | |
}; | |
for (auto& format : depthFormats) | |
{ | |
VkFormatProperties formatProps; | |
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps); | |
// Format must support depth stencil attachment for optimal tiling | |
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) | |
{ | |
*depthFormat = format; | |
return true; | |
} | |
} | |
return false; | |
} | |
// Create an image memory barrier for changing the layout of | |
// an image and put it into an active command buffer | |
// See chapter 11.4 "Image Layout" for details | |
void setImageLayout( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkImageSubresourceRange subresourceRange, | |
VkPipelineStageFlags srcStageMask, | |
VkPipelineStageFlags dstStageMask) | |
{ | |
// Create an image barrier object | |
VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier(); | |
imageMemoryBarrier.oldLayout = oldImageLayout; | |
imageMemoryBarrier.newLayout = newImageLayout; | |
imageMemoryBarrier.image = image; | |
imageMemoryBarrier.subresourceRange = subresourceRange; | |
// Source layouts (old) | |
// Source access mask controls actions that have to be finished on the old layout | |
// before it will be transitioned to the new layout | |
switch (oldImageLayout) | |
{ | |
case VK_IMAGE_LAYOUT_UNDEFINED: | |
// Image layout is undefined (or does not matter) | |
// Only valid as initial layout | |
// No flags required, listed only for completeness | |
imageMemoryBarrier.srcAccessMask = 0; | |
break; | |
case VK_IMAGE_LAYOUT_PREINITIALIZED: | |
// Image is preinitialized | |
// Only valid as initial layout for linear images, preserves memory contents | |
// Make sure host writes have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: | |
// Image is a color attachment | |
// Make sure any writes to the color buffer have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: | |
// Image is a depth/stencil attachment | |
// Make sure any writes to the depth/stencil buffer have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: | |
// Image is a transfer source | |
// Make sure any reads from the image have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: | |
// Image is a transfer destination | |
// Make sure any writes to the image have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: | |
// Image is read by a shader | |
// Make sure any shader reads from the image have been finished | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; | |
break; | |
default: | |
// Other source layouts aren't handled (yet) | |
break; | |
} | |
// Target layouts (new) | |
// Destination access mask controls the dependency for the new image layout | |
switch (newImageLayout) | |
{ | |
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: | |
// Image will be used as a transfer destination | |
// Make sure any writes to the image have been finished | |
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: | |
// Image will be used as a transfer source | |
// Make sure any reads from the image have been finished | |
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: | |
// Image will be used as a color attachment | |
// Make sure any writes to the color buffer have been finished | |
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: | |
// Image layout will be used as a depth/stencil attachment | |
// Make sure any writes to depth/stencil buffer have been finished | |
imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; | |
break; | |
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: | |
// Image will be read in a shader (sampler, input attachment) | |
// Make sure any writes to the image have been finished | |
if (imageMemoryBarrier.srcAccessMask == 0) | |
{ | |
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; | |
} | |
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; | |
break; | |
default: | |
// Other source layouts aren't handled (yet) | |
break; | |
} | |
// Put barrier inside setup command buffer | |
vkCmdPipelineBarrier( | |
cmdbuffer, | |
srcStageMask, | |
dstStageMask, | |
0, | |
0, nullptr, | |
0, nullptr, | |
1, &imageMemoryBarrier); | |
} | |
// Fixed sub resource on first mip level and layer | |
void setImageLayout( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkImageAspectFlags aspectMask, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkPipelineStageFlags srcStageMask, | |
VkPipelineStageFlags dstStageMask) | |
{ | |
VkImageSubresourceRange subresourceRange = {}; | |
subresourceRange.aspectMask = aspectMask; | |
subresourceRange.baseMipLevel = 0; | |
subresourceRange.levelCount = 1; | |
subresourceRange.layerCount = 1; | |
setImageLayout(cmdbuffer, image, oldImageLayout, newImageLayout, subresourceRange, srcStageMask, dstStageMask); | |
} | |
void insertImageMemoryBarrier( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkAccessFlags srcAccessMask, | |
VkAccessFlags dstAccessMask, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkPipelineStageFlags srcStageMask, | |
VkPipelineStageFlags dstStageMask, | |
VkImageSubresourceRange subresourceRange) | |
{ | |
VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier(); | |
imageMemoryBarrier.srcAccessMask = srcAccessMask; | |
imageMemoryBarrier.dstAccessMask = dstAccessMask; | |
imageMemoryBarrier.oldLayout = oldImageLayout; | |
imageMemoryBarrier.newLayout = newImageLayout; | |
imageMemoryBarrier.image = image; | |
imageMemoryBarrier.subresourceRange = subresourceRange; | |
vkCmdPipelineBarrier( | |
cmdbuffer, | |
srcStageMask, | |
dstStageMask, | |
0, | |
0, nullptr, | |
0, nullptr, | |
1, &imageMemoryBarrier); | |
} | |
void exitFatal(std::string message, int32_t exitCode) | |
{ | |
#if defined(_WIN32) | |
if (!errorModeSilent) { | |
MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR); | |
} | |
#elif defined(__ANDROID__) | |
LOGE("Fatal error: %s", message.c_str()); | |
vks::android::showAlert(message.c_str()); | |
#endif | |
std::cerr << message << "\n"; | |
#if !defined(__ANDROID__) | |
exit(exitCode); | |
#endif | |
} | |
void exitFatal(std::string message, VkResult resultCode) | |
{ | |
exitFatal(message, (int32_t)resultCode); | |
} | |
std::string readTextFile(const char *fileName) | |
{ | |
std::string fileContent; | |
std::ifstream fileStream(fileName, std::ios::in); | |
if (!fileStream.is_open()) { | |
printf("File %s not found\n", fileName); | |
return ""; | |
} | |
std::string line = ""; | |
while (!fileStream.eof()) { | |
getline(fileStream, line); | |
fileContent.append(line + "\n"); | |
} | |
fileStream.close(); | |
return fileContent; | |
} | |
#if defined(__ANDROID__) | |
// Android shaders are stored as assets in the apk | |
// So they need to be loaded via the asset manager | |
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device) | |
{ | |
// Load shader from compressed asset | |
AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING); | |
assert(asset); | |
size_t size = AAsset_getLength(asset); | |
assert(size > 0); | |
char *shaderCode = new char[size]; | |
AAsset_read(asset, shaderCode, size); | |
AAsset_close(asset); | |
VkShaderModule shaderModule; | |
VkShaderModuleCreateInfo moduleCreateInfo; | |
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
moduleCreateInfo.pNext = NULL; | |
moduleCreateInfo.codeSize = size; | |
moduleCreateInfo.pCode = (uint32_t*)shaderCode; | |
moduleCreateInfo.flags = 0; | |
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
delete[] shaderCode; | |
return shaderModule; | |
} | |
#else | |
VkShaderModule loadShader(const char *fileName, VkDevice device) | |
{ | |
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); | |
VkShaderModule shaderModule; | |
VkShaderModuleCreateInfo moduleCreateInfo{}; | |
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
moduleCreateInfo.codeSize = size; | |
moduleCreateInfo.pCode = (uint32_t*)shaderCode; | |
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
delete[] shaderCode; | |
return shaderModule; | |
} | |
else | |
{ | |
std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << std::endl; | |
return VK_NULL_HANDLE; | |
} | |
} | |
#endif | |
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage) | |
{ | |
std::string shaderSrc = readTextFile(fileName); | |
const char *shaderCode = shaderSrc.c_str(); | |
size_t size = strlen(shaderCode); | |
assert(size > 0); | |
VkShaderModule shaderModule; | |
VkShaderModuleCreateInfo moduleCreateInfo; | |
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
moduleCreateInfo.pNext = NULL; | |
moduleCreateInfo.codeSize = 3 * sizeof(uint32_t) + size + 1; | |
moduleCreateInfo.pCode = (uint32_t*)malloc(moduleCreateInfo.codeSize); | |
moduleCreateInfo.flags = 0; | |
// Magic SPV number | |
((uint32_t *)moduleCreateInfo.pCode)[0] = 0x07230203; | |
((uint32_t *)moduleCreateInfo.pCode)[1] = 0; | |
((uint32_t *)moduleCreateInfo.pCode)[2] = stage; | |
memcpy(((uint32_t *)moduleCreateInfo.pCode + 3), shaderCode, size + 1); | |
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
return shaderModule; | |
} | |
bool fileExists(const std::string &filename) | |
{ | |
std::ifstream f(filename.c_str()); | |
return !f.fail(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Assorted Vulkan helper functions | |
* | |
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de | |
* | |
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) | |
*/ | |
#pragma once | |
#include "vulkan/vulkan.h" | |
#include "VulkanInitializers.hpp" | |
#include <math.h> | |
#include <stdlib.h> | |
#include <string> | |
#include <cstring> | |
#include <fstream> | |
#include <assert.h> | |
#include <stdio.h> | |
#include <vector> | |
#include <iostream> | |
#include <stdexcept> | |
#include <fstream> | |
#if defined(_WIN32) | |
#include <windows.h> | |
#include <fcntl.h> | |
#include <io.h> | |
#elif defined(__ANDROID__) | |
#include "VulkanAndroid.h" | |
#include <android/asset_manager.h> | |
#endif | |
// Custom define for better code readability | |
#define VK_FLAGS_NONE 0 | |
// Default fence timeout in nanoseconds | |
#define DEFAULT_FENCE_TIMEOUT 100000000000 | |
// Macro to check and display Vulkan return results | |
#if defined(__ANDROID__) | |
#define VK_CHECK_RESULT(f) \ | |
{ \ | |
VkResult res = (f); \ | |
if (res != VK_SUCCESS) \ | |
{ \ | |
LOGE("Fatal : VkResult is \" %s \" in %s at line %d", vks::tools::errorString(res).c_str(), __FILE__, __LINE__); \ | |
assert(res == VK_SUCCESS); \ | |
} \ | |
} | |
#else | |
#define VK_CHECK_RESULT(f) \ | |
{ \ | |
VkResult res = (f); \ | |
if (res != VK_SUCCESS) \ | |
{ \ | |
std::cout << "Fatal : VkResult is \"" << vks::tools::errorString(res) << "\" in " << __FILE__ << " at line " << __LINE__ << std::endl; \ | |
assert(res == VK_SUCCESS); \ | |
} \ | |
} | |
#endif | |
#if defined(__ANDROID__) | |
#define ASSET_PATH "" | |
#else | |
#define ASSET_PATH "./" | |
#endif | |
namespace vks | |
{ | |
namespace tools | |
{ | |
/** @brief Disable message boxes on fatal errors */ | |
extern bool errorModeSilent; | |
/** @brief Returns an error code as a string */ | |
std::string errorString(VkResult errorCode); | |
/** @brief Returns the device type as a string */ | |
std::string physicalDeviceTypeString(VkPhysicalDeviceType type); | |
// Selected a suitable supported depth format starting with 32 bit down to 16 bit | |
// Returns false if none of the depth formats in the list is supported by the device | |
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat); | |
// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer | |
void setImageLayout( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkImageSubresourceRange subresourceRange, | |
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | |
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
// Uses a fixed sub resource layout with first mip level and layer | |
void setImageLayout( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkImageAspectFlags aspectMask, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | |
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
/** @brief Inser an image memory barrier into the command buffer */ | |
void insertImageMemoryBarrier( | |
VkCommandBuffer cmdbuffer, | |
VkImage image, | |
VkAccessFlags srcAccessMask, | |
VkAccessFlags dstAccessMask, | |
VkImageLayout oldImageLayout, | |
VkImageLayout newImageLayout, | |
VkPipelineStageFlags srcStageMask, | |
VkPipelineStageFlags dstStageMask, | |
VkImageSubresourceRange subresourceRange); | |
// Display error message and exit on fatal error | |
void exitFatal(std::string message, int32_t exitCode); | |
void exitFatal(std::string message, VkResult resultCode); | |
// Load a SPIR-V shader (binary) | |
#if defined(__ANDROID__) | |
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device); | |
#else | |
VkShaderModule loadShader(const char *fileName, VkDevice device); | |
#endif | |
// Load a GLSL shader (text) | |
// Note: GLSL support requires vendor-specific extensions to be enabled and is not a core-feature of Vulkan | |
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage); | |
/** @brief Checks if a file exists */ | |
bool fileExists(const std::string &filename); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment