Created
April 24, 2016 18:54
-
-
Save Overv/7ac07356037592a121225172d7d78f2d to your computer and use it in GitHub Desktop.
Vulkan hello triangle
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
#include <iostream> | |
#include <fstream> | |
#include <vector> | |
#include <string> | |
#include <algorithm> | |
#include <chrono> | |
#include <functional> | |
#define GLFW_INCLUDE_VULKAN | |
#include <GLFW/glfw3.h> | |
#include <glm/gtc/matrix_transform.hpp> | |
using namespace std::placeholders; | |
// Configuration | |
const uint32_t WIDTH = 640; | |
const uint32_t HEIGHT = 480; | |
const bool ENABLE_DEBUGGING = false; | |
const char* DEBUG_LAYER = "VK_LAYER_LUNARG_standard_validation"; | |
// Debug callback | |
VkBool32 debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg, void* pUserData) { | |
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { | |
std::cerr << "ERROR: [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << std::endl; | |
} else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { | |
std::cerr << "WARNING: [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << std::endl; | |
} | |
//exit(1); | |
return VK_FALSE; | |
} | |
// Vertex layout | |
struct Vertex { | |
float pos[3]; | |
float color[3]; | |
}; | |
bool windowResized = false; | |
// Note: support swap chain recreation (not only required for resized windows!) | |
// Note: window resize may not result in Vulkan telling that the swap chain should be recreated, should be handled explicitly! | |
class TriangleApplication { | |
public: | |
TriangleApplication() { | |
timeStart = std::chrono::high_resolution_clock::now(); | |
} | |
void run() { | |
// Note: dynamically loading loader may be a better idea to fail gracefully when Vulkan is not supported | |
// Create window for Vulkan | |
glfwInit(); | |
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
window = glfwCreateWindow(WIDTH, HEIGHT, "The spinning triangle that took 1397 lines of code", nullptr, nullptr); | |
glfwSetWindowSizeCallback(window, TriangleApplication::onWindowResized); | |
// Use Vulkan | |
setupVulkan(); | |
mainLoop(); | |
cleanup(true); | |
} | |
private: | |
GLFWwindow* window; | |
VkInstance instance; | |
VkSurfaceKHR windowSurface; | |
VkPhysicalDevice physicalDevice; | |
VkDevice device; | |
VkDebugReportCallbackEXT callback; | |
VkQueue graphicsQueue; | |
VkQueue presentQueue; | |
VkPhysicalDeviceMemoryProperties deviceMemoryProperties; | |
VkSemaphore imageAvailableSemaphore; | |
VkSemaphore renderingFinishedSemaphore; | |
VkBuffer vertexBuffer; | |
VkDeviceMemory vertexBufferMemory; | |
VkBuffer indexBuffer; | |
VkDeviceMemory indexBufferMemory; | |
VkVertexInputBindingDescription vertexBindingDescription; | |
std::vector<VkVertexInputAttributeDescription> vertexAttributeDescriptions; | |
struct { | |
glm::mat4 transformationMatrix; | |
} uniformBufferData; | |
VkBuffer uniformBuffer; | |
VkDeviceMemory uniformBufferMemory; | |
VkDescriptorSetLayout descriptorSetLayout; | |
VkDescriptorPool descriptorPool; | |
VkDescriptorSet descriptorSet; | |
VkExtent2D swapChainExtent; | |
VkFormat swapChainFormat; | |
VkSwapchainKHR oldSwapChain; | |
VkSwapchainKHR swapChain; | |
std::vector<VkImage> swapChainImages; | |
std::vector<VkImageView> swapChainImageViews; | |
std::vector<VkFramebuffer> swapChainFramebuffers; | |
VkRenderPass renderPass; | |
VkPipeline graphicsPipeline; | |
VkPipelineLayout pipelineLayout; | |
VkCommandPool commandPool; | |
std::vector<VkCommandBuffer> graphicsCommandBuffers; | |
uint32_t graphicsQueueFamily; | |
uint32_t presentQueueFamily; | |
std::chrono::high_resolution_clock::time_point timeStart; | |
void setupVulkan() { | |
oldSwapChain = VK_NULL_HANDLE; | |
createInstance(); | |
createDebugCallback(); | |
createWindowSurface(); | |
findPhysicalDevice(); | |
checkSwapChainSupport(); | |
findQueueFamilies(); | |
createLogicalDevice(); | |
createSemaphores(); | |
createCommandPool(); | |
createVertexBuffer(); | |
createUniformBuffer(); | |
createSwapChain(); | |
createRenderPass(); | |
createImageViews(); | |
createFramebuffers(); | |
createGraphicsPipeline(); | |
createDescriptorPool(); | |
createDescriptorSet(); | |
createCommandBuffers(); | |
} | |
void mainLoop() { | |
while (!glfwWindowShouldClose(window)) { | |
updateUniformData(); | |
draw(); | |
glfwPollEvents(); | |
} | |
} | |
static void onWindowResized(GLFWwindow* window, int width, int height) { | |
windowResized = true; | |
} | |
void onWindowSizeChanged() { | |
windowResized = false; | |
// Only recreate objects that are affected by framebuffer size changes | |
cleanup(false); | |
createSwapChain(); | |
createRenderPass(); | |
createImageViews(); | |
createFramebuffers(); | |
createGraphicsPipeline(); | |
createCommandBuffers(); | |
} | |
void cleanup(bool fullClean) { | |
vkDeviceWaitIdle(device); | |
vkFreeCommandBuffers(device, commandPool, (uint32_t) graphicsCommandBuffers.size(), graphicsCommandBuffers.data()); | |
vkDestroyPipeline(device, graphicsPipeline, nullptr); | |
vkDestroyRenderPass(device, renderPass, nullptr); | |
for (size_t i = 0; i < swapChainImages.size(); i++) { | |
vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); | |
vkDestroyImageView(device, swapChainImageViews[i], nullptr); | |
} | |
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); | |
if (fullClean) { | |
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); | |
vkDestroySemaphore(device, renderingFinishedSemaphore, nullptr); | |
vkDestroyCommandPool(device, commandPool, nullptr); | |
// Clean up uniform buffer related objects | |
vkDestroyDescriptorPool(device, descriptorPool, nullptr); | |
vkDestroyBuffer(device, uniformBuffer, nullptr); | |
vkFreeMemory(device, uniformBufferMemory, nullptr); | |
// Buffers must be destroyed after no command buffers are referring to them anymore | |
vkDestroyBuffer(device, vertexBuffer, nullptr); | |
vkFreeMemory(device, vertexBufferMemory, nullptr); | |
vkDestroyBuffer(device, indexBuffer, nullptr); | |
vkFreeMemory(device, indexBufferMemory, nullptr); | |
// Note: implicitly destroys images (in fact, we're not allowed to do that explicitly) | |
vkDestroySwapchainKHR(device, swapChain, nullptr); | |
vkDestroyDevice(device, nullptr); | |
vkDestroySurfaceKHR(instance, windowSurface, nullptr); | |
if (ENABLE_DEBUGGING) { | |
PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); | |
DestroyDebugReportCallback(instance, callback, nullptr); | |
} | |
vkDestroyInstance(instance, nullptr); | |
} | |
} | |
void createInstance() { | |
VkApplicationInfo appInfo = {}; | |
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
appInfo.pApplicationName = "VulkanClear"; | |
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); | |
appInfo.pEngineName = "ClearScreenEngine"; | |
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); | |
appInfo.apiVersion = VK_API_VERSION_1_0; | |
// Get instance extensions required by GLFW to draw to window | |
unsigned int glfwExtensionCount; | |
const char** glfwExtensions; | |
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); | |
std::vector<const char*> extensions; | |
for (size_t i = 0; i < glfwExtensionCount; i++) { | |
extensions.push_back(glfwExtensions[i]); | |
} | |
if (ENABLE_DEBUGGING) { | |
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | |
} | |
// Check for extensions | |
uint32_t extensionCount = 0; | |
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); | |
if (extensionCount == 0) { | |
std::cerr << "no extensions supported!" << std::endl; | |
exit(1); | |
} | |
std::vector<VkExtensionProperties> availableExtensions(extensionCount); | |
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data()); | |
std::cout << "supported extensions:" << std::endl; | |
for (const auto& extension : availableExtensions) { | |
std::cout << "\t" << extension.extensionName << std::endl; | |
} | |
VkInstanceCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
createInfo.pApplicationInfo = &appInfo; | |
createInfo.enabledExtensionCount = (uint32_t) extensions.size(); | |
createInfo.ppEnabledExtensionNames = extensions.data(); | |
if (ENABLE_DEBUGGING) { | |
createInfo.enabledLayerCount = 1; | |
createInfo.ppEnabledLayerNames = &DEBUG_LAYER; | |
} | |
// Initialize Vulkan instance | |
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { | |
std::cerr << "failed to create instance!" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created vulkan instance" << std::endl; | |
} | |
} | |
void createWindowSurface() { | |
if (glfwCreateWindowSurface(instance, window, NULL, &windowSurface) != VK_SUCCESS) { | |
std::cerr << "failed to create window surface!" << std::endl; | |
exit(1); | |
} | |
std::cout << "created window surface" << std::endl; | |
} | |
void findPhysicalDevice() { | |
// Try to find 1 Vulkan supported device | |
// Note: perhaps refactor to loop through devices and find first one that supports all required features and extensions | |
uint32_t deviceCount = 0; | |
if (vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr) != VK_SUCCESS || deviceCount == 0) { | |
std::cerr << "failed to get number of physical devices" << std::endl; | |
exit(1); | |
} | |
deviceCount = 1; | |
VkResult res = vkEnumeratePhysicalDevices(instance, &deviceCount, &physicalDevice); | |
if (res != VK_SUCCESS && res != VK_INCOMPLETE) { | |
std::cerr << "enumerating physical devices failed!" << std::endl; | |
exit(1); | |
} | |
if (deviceCount == 0) { | |
std::cerr << "no physical devices that support vulkan!" << std::endl; | |
exit(1); | |
} | |
std::cout << "physical device with vulkan support found" << std::endl; | |
// Check device features | |
// Note: will apiVersion >= appInfo.apiVersion? Probably yes, but spec is unclear. | |
VkPhysicalDeviceProperties deviceProperties; | |
VkPhysicalDeviceFeatures deviceFeatures; | |
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); | |
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures); | |
uint32_t supportedVersion[] = { | |
VK_VERSION_MAJOR(deviceProperties.apiVersion), | |
VK_VERSION_MINOR(deviceProperties.apiVersion), | |
VK_VERSION_PATCH(deviceProperties.apiVersion) | |
}; | |
std::cout << "physical device supports version " << supportedVersion[0] << "." << supportedVersion[1] << "." << supportedVersion[2] << std::endl; | |
} | |
void checkSwapChainSupport() { | |
uint32_t extensionCount = 0; | |
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr); | |
if (extensionCount == 0) { | |
std::cerr << "physical device doesn't support any extensions" << std::endl; | |
exit(1); | |
} | |
std::vector<VkExtensionProperties> deviceExtensions(extensionCount); | |
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, deviceExtensions.data()); | |
for (const auto& extension : deviceExtensions) { | |
if (strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { | |
std::cout << "physical device supports swap chains" << std::endl; | |
return; | |
} | |
} | |
std::cerr << "physical device doesn't support swap chains" << std::endl; | |
exit(1); | |
} | |
void findQueueFamilies() { | |
// Check queue families | |
uint32_t queueFamilyCount = 0; | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); | |
if (queueFamilyCount == 0) { | |
std::cout << "physical device has no queue families!" << std::endl; | |
exit(1); | |
} | |
// Find queue family with graphics support | |
// Note: is a transfer queue necessary to copy vertices to the gpu or can a graphics queue handle that? | |
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); | |
std::cout << "physical device has " << queueFamilyCount << " queue families" << std::endl; | |
bool foundGraphicsQueueFamily = false; | |
bool foundPresentQueueFamily = false; | |
for (uint32_t i = 0; i < queueFamilyCount; i++) { | |
VkBool32 presentSupport = false; | |
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, windowSurface, &presentSupport); | |
if (queueFamilies[i].queueCount > 0 && queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | |
graphicsQueueFamily = i; | |
foundGraphicsQueueFamily = true; | |
if (presentSupport) { | |
presentQueueFamily = i; | |
foundPresentQueueFamily = true; | |
break; | |
} | |
} | |
if (!foundPresentQueueFamily && presentSupport) { | |
presentQueueFamily = i; | |
foundPresentQueueFamily = true; | |
} | |
} | |
if (foundGraphicsQueueFamily) { | |
std::cout << "queue family #" << graphicsQueueFamily << " supports graphics" << std::endl; | |
if (foundPresentQueueFamily) { | |
std::cout << "queue family #" << presentQueueFamily << " supports presentation" << std::endl; | |
} else { | |
std::cerr << "could not find a valid queue family with present support" << std::endl; | |
exit(1); | |
} | |
} else { | |
std::cerr << "could not find a valid queue family with graphics support" << std::endl; | |
exit(1); | |
} | |
} | |
void createLogicalDevice() { | |
// Greate one graphics queue and optionally a separate presentation queue | |
float queuePriority = 1.0f; | |
VkDeviceQueueCreateInfo queueCreateInfo[2] = {}; | |
queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
queueCreateInfo[0].queueFamilyIndex = graphicsQueueFamily; | |
queueCreateInfo[0].queueCount = 1; | |
queueCreateInfo[0].pQueuePriorities = &queuePriority; | |
queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
queueCreateInfo[0].queueFamilyIndex = presentQueueFamily; | |
queueCreateInfo[0].queueCount = 1; | |
queueCreateInfo[0].pQueuePriorities = &queuePriority; | |
// Create logical device from physical device | |
// Note: there are separate instance and device extensions! | |
VkDeviceCreateInfo deviceCreateInfo = {}; | |
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
deviceCreateInfo.pQueueCreateInfos = queueCreateInfo; | |
if (graphicsQueueFamily == presentQueueFamily) { | |
deviceCreateInfo.queueCreateInfoCount = 1; | |
} else { | |
deviceCreateInfo.queueCreateInfoCount = 2; | |
} | |
// Necessary for shader (for some reason) | |
VkPhysicalDeviceFeatures enabledFeatures = {}; | |
enabledFeatures.shaderClipDistance = VK_TRUE; | |
enabledFeatures.shaderCullDistance = VK_TRUE; | |
const char* deviceExtensions = VK_KHR_SWAPCHAIN_EXTENSION_NAME; | |
deviceCreateInfo.enabledExtensionCount = 1; | |
deviceCreateInfo.ppEnabledExtensionNames = &deviceExtensions; | |
deviceCreateInfo.pEnabledFeatures = &enabledFeatures; | |
if (ENABLE_DEBUGGING) { | |
deviceCreateInfo.enabledLayerCount = 1; | |
deviceCreateInfo.ppEnabledLayerNames = &DEBUG_LAYER; | |
} | |
if (vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device) != VK_SUCCESS) { | |
std::cerr << "failed to create logical device" << std::endl; | |
exit(1); | |
} | |
std::cout << "created logical device" << std::endl; | |
// Get graphics and presentation queues (which may be the same) | |
vkGetDeviceQueue(device, graphicsQueueFamily, 0, &graphicsQueue); | |
vkGetDeviceQueue(device, presentQueueFamily, 0, &presentQueue); | |
std::cout << "acquired graphics and presentation queues" << std::endl; | |
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); | |
} | |
void createDebugCallback() { | |
if (ENABLE_DEBUGGING) { | |
VkDebugReportCallbackCreateInfoEXT createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; | |
createInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT) debugCallback; | |
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; | |
PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); | |
if (CreateDebugReportCallback(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { | |
std::cerr << "failed to create debug callback" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created debug callback" << std::endl; | |
} | |
} else { | |
std::cout << "skipped creating debug callback" << std::endl; | |
} | |
} | |
void createSemaphores() { | |
VkSemaphoreCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | |
if (vkCreateSemaphore(device, &createInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || | |
vkCreateSemaphore(device, &createInfo, nullptr, &renderingFinishedSemaphore) != VK_SUCCESS) { | |
std::cerr << "failed to create semaphores" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created semaphores" << std::endl; | |
} | |
} | |
void createCommandPool() { | |
// Create graphics command pool | |
VkCommandPoolCreateInfo poolCreateInfo = {}; | |
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
poolCreateInfo.queueFamilyIndex = graphicsQueueFamily; | |
if (vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool) != VK_SUCCESS) { | |
std::cerr << "failed to create command queue for graphics queue family" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created command pool for graphics queue family" << std::endl; | |
} | |
} | |
void createVertexBuffer() { | |
// Setup vertices | |
std::vector<Vertex> vertices = { | |
{ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, | |
{ { -0.5f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, | |
{ { 0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f } } | |
}; | |
uint32_t verticesSize = (uint32_t) (vertices.size() * sizeof(vertices[0])); | |
// Setup indices | |
std::vector<uint32_t> indices = { 0, 1, 2 }; | |
uint32_t indicesSize = (uint32_t) (indices.size() * sizeof(indices[0])); | |
VkMemoryAllocateInfo memAlloc = {}; | |
memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |
VkMemoryRequirements memReqs; | |
void* data; | |
struct StagingBuffer { | |
VkDeviceMemory memory; | |
VkBuffer buffer; | |
}; | |
struct { | |
StagingBuffer vertices; | |
StagingBuffer indices; | |
} stagingBuffers; | |
// Allocate command buffer for copy operation | |
VkCommandBufferAllocateInfo cmdBufInfo = {}; | |
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |
cmdBufInfo.commandPool = commandPool; | |
cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
cmdBufInfo.commandBufferCount = 1; | |
VkCommandBuffer copyCommandBuffer; | |
vkAllocateCommandBuffers(device, &cmdBufInfo, ©CommandBuffer); | |
// First copy vertices to host accessible vertex buffer memory | |
VkBufferCreateInfo vertexBufferInfo = {}; | |
vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
vertexBufferInfo.size = verticesSize; | |
vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; | |
vkCreateBuffer(device, &vertexBufferInfo, nullptr, &stagingBuffers.vertices.buffer); | |
vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); | |
vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory); | |
vkMapMemory(device, stagingBuffers.vertices.memory, 0, verticesSize, 0, &data); | |
memcpy(data, vertices.data(), verticesSize); | |
vkUnmapMemory(device, stagingBuffers.vertices.memory); | |
vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0); | |
// Then allocate a gpu only buffer for vertices | |
vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; | |
vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertexBuffer); | |
vkGetBufferMemoryRequirements(device, vertexBuffer, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); | |
vkAllocateMemory(device, &memAlloc, nullptr, &vertexBufferMemory); | |
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); | |
// Next copy indices to host accessible index buffer memory | |
VkBufferCreateInfo indexBufferInfo = {}; | |
indexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
indexBufferInfo.size = indicesSize; | |
indexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; | |
vkCreateBuffer(device, &indexBufferInfo, nullptr, &stagingBuffers.indices.buffer); | |
vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); | |
vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory); | |
vkMapMemory(device, stagingBuffers.indices.memory, 0, indicesSize, 0, &data); | |
memcpy(data, indices.data(), indicesSize); | |
vkUnmapMemory(device, stagingBuffers.indices.memory); | |
vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0); | |
// And allocate another gpu only buffer for indices | |
indexBufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; | |
vkCreateBuffer(device, &indexBufferInfo, nullptr, &indexBuffer); | |
vkGetBufferMemoryRequirements(device, indexBuffer, &memReqs); | |
memAlloc.allocationSize = memReqs.size; | |
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); | |
vkAllocateMemory(device, &memAlloc, nullptr, &indexBufferMemory); | |
vkBindBufferMemory(device, indexBuffer, indexBufferMemory, 0); | |
// Now copy data from host visible buffer to gpu only buffer | |
VkCommandBufferBeginInfo bufferBeginInfo = {}; | |
bufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
bufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
vkBeginCommandBuffer(copyCommandBuffer, &bufferBeginInfo); | |
VkBufferCopy copyRegion = {}; | |
copyRegion.size = verticesSize; | |
vkCmdCopyBuffer(copyCommandBuffer, stagingBuffers.vertices.buffer, vertexBuffer, 1, ©Region); | |
copyRegion.size = indicesSize; | |
vkCmdCopyBuffer(copyCommandBuffer, stagingBuffers.indices.buffer, indexBuffer, 1, ©Region); | |
vkEndCommandBuffer(copyCommandBuffer); | |
// Submit to queue | |
VkSubmitInfo submitInfo = {}; | |
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |
submitInfo.commandBufferCount = 1; | |
submitInfo.pCommandBuffers = ©CommandBuffer; | |
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); | |
vkQueueWaitIdle(graphicsQueue); | |
vkFreeCommandBuffers(device, commandPool, 1, ©CommandBuffer); | |
vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr); | |
vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr); | |
vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr); | |
vkFreeMemory(device, stagingBuffers.indices.memory, nullptr); | |
std::cout << "set up vertex and index buffers" << std::endl; | |
// Binding and attribute descriptions | |
vertexBindingDescription.binding = 0; | |
vertexBindingDescription.stride = sizeof(vertices[0]); | |
vertexBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; | |
// vec2 position | |
vertexAttributeDescriptions.resize(2); | |
vertexAttributeDescriptions[0].binding = 0; | |
vertexAttributeDescriptions[0].location = 0; | |
vertexAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; | |
// vec3 color | |
vertexAttributeDescriptions[1].binding = 0; | |
vertexAttributeDescriptions[1].location = 1; | |
vertexAttributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; | |
vertexAttributeDescriptions[1].offset = sizeof(float) * 3; | |
} | |
void createUniformBuffer() { | |
VkBufferCreateInfo bufferInfo = {}; | |
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
bufferInfo.size = sizeof(uniformBufferData); | |
bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; | |
vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBuffer); | |
VkMemoryRequirements memReqs; | |
vkGetBufferMemoryRequirements(device, uniformBuffer, &memReqs); | |
VkMemoryAllocateInfo allocInfo = {}; | |
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |
allocInfo.allocationSize = memReqs.size; | |
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); | |
vkAllocateMemory(device, &allocInfo, nullptr, &uniformBufferMemory); | |
vkBindBufferMemory(device, uniformBuffer, uniformBufferMemory, 0); | |
updateUniformData(); | |
} | |
void updateUniformData() { | |
// Rotate based on time | |
auto timeNow = std::chrono::high_resolution_clock::now(); | |
long long millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeStart - timeNow).count(); | |
float angle = (millis % 4000) / 4000.0f * glm::radians(360.f); | |
glm::mat4 modelMatrix; | |
modelMatrix = glm::rotate(modelMatrix, angle, glm::vec3(0, 0, 1)); | |
modelMatrix = glm::translate(modelMatrix, glm::vec3(0.5f / 3.0f, -0.5f / 3.0f, 0.0f)); | |
// Set up view | |
auto viewMatrix = glm::lookAt(glm::vec3(1, 1, 1), glm::vec3(0, 0, 0), glm::vec3(0, 0, -1)); | |
// Set up projection | |
auto projMatrix = glm::perspective(glm::radians(70.f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); | |
uniformBufferData.transformationMatrix = projMatrix * viewMatrix * modelMatrix; | |
void* data; | |
vkMapMemory(device, uniformBufferMemory, 0, sizeof(uniformBufferData), 0, &data); | |
memcpy(data, &uniformBufferData, sizeof(uniformBufferData)); | |
vkUnmapMemory(device, uniformBufferMemory); | |
} | |
// Find device memory that is supported by the requirements (typeBits) and meets the desired properties | |
VkBool32 getMemoryType(uint32_t typeBits, VkFlags properties, uint32_t* typeIndex) { | |
for (uint32_t i = 0; i < 32; i++) { | |
if ((typeBits & 1) == 1) { | |
if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { | |
*typeIndex = i; | |
return true; | |
} | |
} | |
typeBits >>= 1; | |
} | |
return false; | |
} | |
void createSwapChain() { | |
// Find surface capabilities | |
VkSurfaceCapabilitiesKHR surfaceCapabilities; | |
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, windowSurface, &surfaceCapabilities) != VK_SUCCESS) { | |
std::cerr << "failed to acquire presentation surface capabilities" << std::endl; | |
exit(1); | |
} | |
// Find supported surface formats | |
uint32_t formatCount; | |
if (vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, windowSurface, &formatCount, nullptr) != VK_SUCCESS || formatCount == 0) { | |
std::cerr << "failed to get number of supported surface formats" << std::endl; | |
exit(1); | |
} | |
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount); | |
if (vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, windowSurface, &formatCount, surfaceFormats.data()) != VK_SUCCESS) { | |
std::cerr << "failed to get supported surface formats" << std::endl; | |
exit(1); | |
} | |
// Find supported present modes | |
uint32_t presentModeCount; | |
if (vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, windowSurface, &presentModeCount, nullptr) != VK_SUCCESS || presentModeCount == 0) { | |
std::cerr << "failed to get number of supported presentation modes" << std::endl; | |
exit(1); | |
} | |
std::vector<VkPresentModeKHR> presentModes(presentModeCount); | |
if (vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, windowSurface, &presentModeCount, presentModes.data()) != VK_SUCCESS) { | |
std::cerr << "failed to get supported presentation modes" << std::endl; | |
exit(1); | |
} | |
// Determine number of images for swap chain | |
uint32_t imageCount = surfaceCapabilities.minImageCount + 1; | |
if (surfaceCapabilities.maxImageCount != 0 && imageCount > surfaceCapabilities.maxImageCount) { | |
imageCount = surfaceCapabilities.maxImageCount; | |
} | |
std::cout << "using " << imageCount << " images for swap chain" << std::endl; | |
// Select a surface format | |
VkSurfaceFormatKHR surfaceFormat = chooseSurfaceFormat(surfaceFormats); | |
// Select swap chain size | |
swapChainExtent = chooseSwapExtent(surfaceCapabilities); | |
// Determine transformation to use (preferring no transform) | |
VkSurfaceTransformFlagBitsKHR surfaceTransform; | |
if (surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { | |
surfaceTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; | |
} else { | |
surfaceTransform = surfaceCapabilities.currentTransform; | |
} | |
// Choose presentation mode (preferring MAILBOX ~= triple buffering) | |
VkPresentModeKHR presentMode = choosePresentMode(presentModes); | |
// Finally, create the swap chain | |
VkSwapchainCreateInfoKHR createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | |
createInfo.surface = windowSurface; | |
createInfo.minImageCount = imageCount; | |
createInfo.imageFormat = surfaceFormat.format; | |
createInfo.imageColorSpace = surfaceFormat.colorSpace; | |
createInfo.imageExtent = swapChainExtent; | |
createInfo.imageArrayLayers = 1; | |
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; | |
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
createInfo.queueFamilyIndexCount = 0; | |
createInfo.pQueueFamilyIndices = nullptr; | |
createInfo.preTransform = surfaceTransform; | |
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; | |
createInfo.presentMode = presentMode; | |
createInfo.clipped = VK_TRUE; | |
createInfo.oldSwapchain = oldSwapChain; | |
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { | |
std::cerr << "failed to create swap chain" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created swap chain" << std::endl; | |
} | |
if (oldSwapChain != VK_NULL_HANDLE) { | |
vkDestroySwapchainKHR(device, oldSwapChain, nullptr); | |
} | |
oldSwapChain = swapChain; | |
swapChainFormat = surfaceFormat.format; | |
// Store the images used by the swap chain | |
// Note: these are the images that swap chain image indices refer to | |
// Note: actual number of images may differ from requested number, since it's a lower bound | |
uint32_t actualImageCount = 0; | |
if (vkGetSwapchainImagesKHR(device, swapChain, &actualImageCount, nullptr) != VK_SUCCESS || actualImageCount == 0) { | |
std::cerr << "failed to acquire number of swap chain images" << std::endl; | |
exit(1); | |
} | |
swapChainImages.resize(actualImageCount); | |
if (vkGetSwapchainImagesKHR(device, swapChain, &actualImageCount, swapChainImages.data()) != VK_SUCCESS) { | |
std::cerr << "failed to acquire swap chain images" << std::endl; | |
exit(1); | |
} | |
std::cout << "acquired swap chain images" << std::endl; | |
} | |
VkSurfaceFormatKHR chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { | |
// We can either choose any format | |
if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { | |
return{ VK_FORMAT_R8G8B8A8_UNORM, VK_COLORSPACE_SRGB_NONLINEAR_KHR }; | |
} | |
// Or go with the standard format - if available | |
for (const auto& availableSurfaceFormat : availableFormats) { | |
if (availableSurfaceFormat.format == VK_FORMAT_R8G8B8A8_UNORM) { | |
return availableSurfaceFormat; | |
} | |
} | |
// Or fall back to the first available one | |
return availableFormats[0]; | |
} | |
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& surfaceCapabilities) { | |
if (surfaceCapabilities.currentExtent.width == -1) { | |
VkExtent2D swapChainExtent = {}; | |
swapChainExtent.width = std::min(std::max(WIDTH, surfaceCapabilities.minImageExtent.width), surfaceCapabilities.maxImageExtent.width); | |
swapChainExtent.height = std::min(std::max(HEIGHT, surfaceCapabilities.minImageExtent.height), surfaceCapabilities.maxImageExtent.height); | |
return swapChainExtent; | |
} else { | |
return surfaceCapabilities.currentExtent; | |
} | |
} | |
VkPresentModeKHR choosePresentMode(const std::vector<VkPresentModeKHR> presentModes) { | |
for (const auto& presentMode : presentModes) { | |
if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { | |
return presentMode; | |
} | |
} | |
// If mailbox is unavailable, fall back to FIFO (guaranteed to be available) | |
return VK_PRESENT_MODE_FIFO_KHR; | |
} | |
void createRenderPass() { | |
VkAttachmentDescription attachmentDescription = {}; | |
attachmentDescription.format = swapChainFormat; | |
attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; | |
attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; | |
attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
// Note: hardware will automatically transition attachment to the specified layout | |
// Note: index refers to attachment descriptions array | |
VkAttachmentReference colorAttachmentReference = {}; | |
colorAttachmentReference.attachment = 0; | |
colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
// Note: this is a description of how the attachments of the render pass will be used in this sub pass | |
// e.g. if they will be read in shaders and/or drawn to | |
VkSubpassDescription subPassDescription = {}; | |
subPassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; | |
subPassDescription.colorAttachmentCount = 1; | |
subPassDescription.pColorAttachments = &colorAttachmentReference; | |
// Create the render pass | |
VkRenderPassCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; | |
createInfo.attachmentCount = 1; | |
createInfo.pAttachments = &attachmentDescription; | |
createInfo.subpassCount = 1; | |
createInfo.pSubpasses = &subPassDescription; | |
if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) { | |
std::cerr << "failed to create render pass" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created render pass" << std::endl; | |
} | |
} | |
void createImageViews() { | |
swapChainImageViews.resize(swapChainImages.size()); | |
// Create an image view for every image in the swap chain | |
for (size_t i = 0; i < swapChainImages.size(); i++) { | |
VkImageViewCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | |
createInfo.image = swapChainImages[i]; | |
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
createInfo.format = swapChainFormat; | |
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
createInfo.subresourceRange.baseMipLevel = 0; | |
createInfo.subresourceRange.levelCount = 1; | |
createInfo.subresourceRange.baseArrayLayer = 0; | |
createInfo.subresourceRange.layerCount = 1; | |
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { | |
std::cerr << "failed to create image view for swap chain image #" << i << std::endl; | |
exit(1); | |
} | |
} | |
std::cout << "created image views for swap chain images" << std::endl; | |
} | |
void createFramebuffers() { | |
swapChainFramebuffers.resize(swapChainImages.size()); | |
// Note: Framebuffer is basically a specific choice of attachments for a render pass | |
// That means all attachments must have the same dimensions, interesting restriction | |
for (size_t i = 0; i < swapChainImages.size(); i++) { | |
VkFramebufferCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; | |
createInfo.renderPass = renderPass; | |
createInfo.attachmentCount = 1; | |
createInfo.pAttachments = &swapChainImageViews[i]; | |
createInfo.width = swapChainExtent.width; | |
createInfo.height = swapChainExtent.height; | |
createInfo.layers = 1; | |
if (vkCreateFramebuffer(device, &createInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { | |
std::cerr << "failed to create framebuffer for swap chain image view #" << i << std::endl; | |
exit(1); | |
} | |
} | |
std::cout << "created framebuffers for swap chain image views" << std::endl; | |
} | |
VkShaderModule createShaderModule(const std::string& filename) { | |
std::ifstream file(filename, std::ios::ate | std::ios::binary); | |
std::vector<char> fileBytes(file.tellg()); | |
file.seekg(0, std::ios::beg); | |
file.read(fileBytes.data(), fileBytes.size()); | |
file.close(); | |
VkShaderModuleCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
createInfo.codeSize = fileBytes.size(); | |
createInfo.pCode = (uint32_t*) fileBytes.data(); | |
VkShaderModule shaderModule; | |
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { | |
std::cerr << "failed to create shader module for " << filename << std::endl; | |
exit(1); | |
} | |
std::cout << "created shader module for " << filename << std::endl; | |
return shaderModule; | |
} | |
void createGraphicsPipeline() { | |
VkShaderModule vertexShaderModule = createShaderModule("shaders/vert.spv"); | |
VkShaderModule fragmentShaderModule = createShaderModule("shaders/frag.spv"); | |
// Set up shader stage info | |
VkPipelineShaderStageCreateInfo vertexShaderCreateInfo = {}; | |
vertexShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
vertexShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; | |
vertexShaderCreateInfo.module = vertexShaderModule; | |
vertexShaderCreateInfo.pName = "main"; | |
VkPipelineShaderStageCreateInfo fragmentShaderCreateInfo = {}; | |
fragmentShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
fragmentShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; | |
fragmentShaderCreateInfo.module = fragmentShaderModule; | |
fragmentShaderCreateInfo.pName = "main"; | |
VkPipelineShaderStageCreateInfo shaderStages[] = { vertexShaderCreateInfo, fragmentShaderCreateInfo }; | |
// Describe vertex input | |
VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {}; | |
vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; | |
vertexInputCreateInfo.vertexBindingDescriptionCount = 1; | |
vertexInputCreateInfo.pVertexBindingDescriptions = &vertexBindingDescription; | |
vertexInputCreateInfo.vertexAttributeDescriptionCount = 2; | |
vertexInputCreateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data(); | |
// Describe input assembly | |
VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {}; | |
inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; | |
inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; | |
inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; | |
// Describe viewport and scissor | |
VkViewport viewport = {}; | |
viewport.x = 0.0f; | |
viewport.y = 0.0f; | |
viewport.width = (float) swapChainExtent.width; | |
viewport.height = (float) swapChainExtent.height; | |
viewport.minDepth = 0.0f; | |
viewport.maxDepth = 1.0f; | |
VkRect2D scissor = {}; | |
scissor.offset.x = 0; | |
scissor.offset.y = 0; | |
scissor.extent.width = swapChainExtent.width; | |
scissor.extent.height = swapChainExtent.height; | |
// Note: scissor test is always enabled (although dynamic scissor is possible) | |
// Number of viewports must match number of scissors | |
VkPipelineViewportStateCreateInfo viewportCreateInfo = {}; | |
viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; | |
viewportCreateInfo.viewportCount = 1; | |
viewportCreateInfo.pViewports = &viewport; | |
viewportCreateInfo.scissorCount = 1; | |
viewportCreateInfo.pScissors = &scissor; | |
// Describe rasterization | |
// Note: depth bias and using polygon modes other than fill require changes to logical device creation (device features) | |
VkPipelineRasterizationStateCreateInfo rasterizationCreateInfo = {}; | |
rasterizationCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; | |
rasterizationCreateInfo.depthClampEnable = VK_FALSE; | |
rasterizationCreateInfo.rasterizerDiscardEnable = VK_FALSE; | |
rasterizationCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; | |
rasterizationCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT; | |
rasterizationCreateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; | |
rasterizationCreateInfo.depthBiasEnable = VK_FALSE; | |
rasterizationCreateInfo.depthBiasConstantFactor = 0.0f; | |
rasterizationCreateInfo.depthBiasClamp = 0.0f; | |
rasterizationCreateInfo.depthBiasSlopeFactor = 0.0f; | |
rasterizationCreateInfo.lineWidth = 1.0f; | |
// Describe multisampling | |
// Note: using multisampling also requires turning on device features | |
VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {}; | |
multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; | |
multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; | |
multisampleCreateInfo.sampleShadingEnable = VK_FALSE; | |
multisampleCreateInfo.minSampleShading = 1.0f; | |
multisampleCreateInfo.alphaToCoverageEnable = VK_FALSE; | |
multisampleCreateInfo.alphaToOneEnable = VK_FALSE; | |
// Describing color blending | |
// Note: all paramaters except blendEnable and colorWriteMask are irrelevant here | |
VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {}; | |
colorBlendAttachmentState.blendEnable = VK_FALSE; | |
colorBlendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; | |
colorBlendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; | |
colorBlendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; | |
colorBlendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; | |
colorBlendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; | |
colorBlendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; | |
colorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; | |
// Note: all attachments must have the same values unless a device feature is enabled | |
VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {}; | |
colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; | |
colorBlendCreateInfo.logicOpEnable = VK_FALSE; | |
colorBlendCreateInfo.logicOp = VK_LOGIC_OP_COPY; | |
colorBlendCreateInfo.attachmentCount = 1; | |
colorBlendCreateInfo.pAttachments = &colorBlendAttachmentState; | |
colorBlendCreateInfo.blendConstants[0] = 0.0f; | |
colorBlendCreateInfo.blendConstants[1] = 0.0f; | |
colorBlendCreateInfo.blendConstants[2] = 0.0f; | |
colorBlendCreateInfo.blendConstants[3] = 0.0f; | |
// Describe pipeline layout | |
// Note: this describes the mapping between memory and shader resources (descriptor sets) | |
// This is for uniform buffers and samplers | |
VkDescriptorSetLayoutBinding layoutBinding = {}; | |
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
layoutBinding.descriptorCount = 1; | |
layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; | |
VkDescriptorSetLayoutCreateInfo descriptorLayoutCreateInfo = {}; | |
descriptorLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; | |
descriptorLayoutCreateInfo.bindingCount = 1; | |
descriptorLayoutCreateInfo.pBindings = &layoutBinding; | |
if (vkCreateDescriptorSetLayout(device, &descriptorLayoutCreateInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { | |
std::cerr << "failed to create descriptor layout" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created descriptor layout" << std::endl; | |
} | |
VkPipelineLayoutCreateInfo layoutCreateInfo = {}; | |
layoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; | |
layoutCreateInfo.setLayoutCount = 1; | |
layoutCreateInfo.pSetLayouts = &descriptorSetLayout; | |
if (vkCreatePipelineLayout(device, &layoutCreateInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { | |
std::cerr << "failed to create pipeline layout" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created pipeline layout" << std::endl; | |
} | |
// Create the graphics pipeline | |
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; | |
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; | |
pipelineCreateInfo.stageCount = 2; | |
pipelineCreateInfo.pStages = shaderStages; | |
pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; | |
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; | |
pipelineCreateInfo.pViewportState = &viewportCreateInfo; | |
pipelineCreateInfo.pRasterizationState = &rasterizationCreateInfo; | |
pipelineCreateInfo.pMultisampleState = &multisampleCreateInfo; | |
pipelineCreateInfo.pColorBlendState = &colorBlendCreateInfo; | |
pipelineCreateInfo.layout = pipelineLayout; | |
pipelineCreateInfo.renderPass = renderPass; | |
pipelineCreateInfo.subpass = 0; | |
pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; | |
pipelineCreateInfo.basePipelineIndex = -1; | |
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { | |
std::cerr << "failed to create graphics pipeline" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created graphics pipeline" << std::endl; | |
} | |
// No longer necessary | |
vkDestroyShaderModule(device, vertexShaderModule, nullptr); | |
vkDestroyShaderModule(device, fragmentShaderModule, nullptr); | |
} | |
void createDescriptorPool() { | |
// This describes how many descriptor sets we'll create from this pool for each type | |
VkDescriptorPoolSize typeCount; | |
typeCount.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
typeCount.descriptorCount = 1; | |
VkDescriptorPoolCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; | |
createInfo.poolSizeCount = 1; | |
createInfo.pPoolSizes = &typeCount; | |
createInfo.maxSets = 1; | |
if (vkCreateDescriptorPool(device, &createInfo, nullptr, &descriptorPool) != VK_SUCCESS) { | |
std::cerr << "failed to create descriptor pool" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created descriptor pool" << std::endl; | |
} | |
} | |
void createDescriptorSet() { | |
// There needs to be one descriptor set per binding point in the shader | |
VkDescriptorSetAllocateInfo allocInfo = {}; | |
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; | |
allocInfo.descriptorPool = descriptorPool; | |
allocInfo.descriptorSetCount = 1; | |
allocInfo.pSetLayouts = &descriptorSetLayout; | |
if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { | |
std::cerr << "failed to create descriptor set" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "created descriptor set" << std::endl; | |
} | |
// Update descriptor set with uniform binding | |
VkDescriptorBufferInfo descriptorBufferInfo = {}; | |
descriptorBufferInfo.buffer = uniformBuffer; | |
descriptorBufferInfo.offset = 0; | |
descriptorBufferInfo.range = sizeof(uniformBufferData); | |
VkWriteDescriptorSet writeDescriptorSet = {}; | |
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; | |
writeDescriptorSet.dstSet = descriptorSet; | |
writeDescriptorSet.descriptorCount = 1; | |
writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
writeDescriptorSet.pBufferInfo = &descriptorBufferInfo; | |
writeDescriptorSet.dstBinding = 0; | |
vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); | |
} | |
void createCommandBuffers() { | |
// Allocate graphics command buffers | |
graphicsCommandBuffers.resize(swapChainImages.size()); | |
VkCommandBufferAllocateInfo allocInfo = {}; | |
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |
allocInfo.commandPool = commandPool; | |
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
allocInfo.commandBufferCount = (uint32_t) swapChainImages.size(); | |
if (vkAllocateCommandBuffers(device, &allocInfo, graphicsCommandBuffers.data()) != VK_SUCCESS) { | |
std::cerr << "failed to allocate graphics command buffers" << std::endl; | |
exit(1); | |
} else { | |
std::cout << "allocated graphics command buffers" << std::endl; | |
} | |
// Prepare data for recording command buffers | |
VkCommandBufferBeginInfo beginInfo = {}; | |
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; | |
VkImageSubresourceRange subResourceRange = {}; | |
subResourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
subResourceRange.baseMipLevel = 0; | |
subResourceRange.levelCount = 1; | |
subResourceRange.baseArrayLayer = 0; | |
subResourceRange.layerCount = 1; | |
VkClearValue clearColor = { | |
{ 0.1f, 0.1f, 0.1f, 1.0f } // R, G, B, A | |
}; | |
// Record command buffer for each swap image | |
for (size_t i = 0; i < swapChainImages.size(); i++) { | |
vkBeginCommandBuffer(graphicsCommandBuffers[i], &beginInfo); | |
// If present queue family and graphics queue family are different, then a barrier is necessary | |
// The barrier is also needed initially to transition the image to the present layout | |
VkImageMemoryBarrier presentToDrawBarrier = {}; | |
presentToDrawBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
presentToDrawBarrier.srcAccessMask = 0; | |
presentToDrawBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
presentToDrawBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
presentToDrawBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
if (presentQueueFamily != graphicsQueueFamily) { | |
presentToDrawBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
presentToDrawBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
} else { | |
presentToDrawBarrier.srcQueueFamilyIndex = presentQueueFamily; | |
presentToDrawBarrier.dstQueueFamilyIndex = graphicsQueueFamily; | |
} | |
presentToDrawBarrier.image = swapChainImages[i]; | |
presentToDrawBarrier.subresourceRange = subResourceRange; | |
vkCmdPipelineBarrier(graphicsCommandBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentToDrawBarrier); | |
VkRenderPassBeginInfo renderPassBeginInfo = {}; | |
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |
renderPassBeginInfo.renderPass = renderPass; | |
renderPassBeginInfo.framebuffer = swapChainFramebuffers[i]; | |
renderPassBeginInfo.renderArea.offset.x = 0; | |
renderPassBeginInfo.renderArea.offset.y = 0; | |
renderPassBeginInfo.renderArea.extent = swapChainExtent; | |
renderPassBeginInfo.clearValueCount = 1; | |
renderPassBeginInfo.pClearValues = &clearColor; | |
vkCmdBeginRenderPass(graphicsCommandBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
vkCmdBindDescriptorSets(graphicsCommandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); | |
vkCmdBindPipeline(graphicsCommandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); | |
VkDeviceSize offset = 0; | |
vkCmdBindVertexBuffers(graphicsCommandBuffers[i], 0, 1, &vertexBuffer, &offset); | |
vkCmdBindIndexBuffer(graphicsCommandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); | |
vkCmdDrawIndexed(graphicsCommandBuffers[i], 3, 1, 0, 0, 0); | |
vkCmdEndRenderPass(graphicsCommandBuffers[i]); | |
// If present and graphics queue families differ, then another barrier is required | |
if (presentQueueFamily != graphicsQueueFamily) { | |
VkImageMemoryBarrier drawToPresentBarrier = {}; | |
drawToPresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
drawToPresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
drawToPresentBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; | |
drawToPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
drawToPresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
drawToPresentBarrier.srcQueueFamilyIndex = graphicsQueueFamily; | |
drawToPresentBarrier.dstQueueFamilyIndex = presentQueueFamily; | |
drawToPresentBarrier.image = swapChainImages[i]; | |
drawToPresentBarrier.subresourceRange = subResourceRange; | |
vkCmdPipelineBarrier(graphicsCommandBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &drawToPresentBarrier); | |
} | |
if (vkEndCommandBuffer(graphicsCommandBuffers[i]) != VK_SUCCESS) { | |
std::cerr << "failed to record command buffer" << std::endl; | |
exit(1); | |
} | |
} | |
std::cout << "recorded command buffers" << std::endl; | |
// No longer needed | |
vkDestroyPipelineLayout(device, pipelineLayout, nullptr); | |
} | |
void draw() { | |
// Acquire image | |
uint32_t imageIndex; | |
VkResult res = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); | |
// Unless surface is out of date right now, defer swap chain recreation until end of this frame | |
if (res == VK_ERROR_OUT_OF_DATE_KHR) { | |
onWindowSizeChanged(); | |
return; | |
} else if (res != VK_SUCCESS) { | |
std::cerr << "failed to acquire image" << std::endl; | |
exit(1); | |
} | |
// Wait for image to be available and draw | |
VkSubmitInfo submitInfo = {}; | |
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |
submitInfo.waitSemaphoreCount = 1; | |
submitInfo.pWaitSemaphores = &imageAvailableSemaphore; | |
submitInfo.signalSemaphoreCount = 1; | |
submitInfo.pSignalSemaphores = &renderingFinishedSemaphore; | |
// This is the stage where the queue should wait on the semaphore | |
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; | |
submitInfo.pWaitDstStageMask = &waitDstStageMask; | |
submitInfo.commandBufferCount = 1; | |
submitInfo.pCommandBuffers = &graphicsCommandBuffers[imageIndex]; | |
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { | |
std::cerr << "failed to submit draw command buffer" << std::endl; | |
exit(1); | |
} | |
// Present drawn image | |
// Note: semaphore here is not strictly necessary, because commands are processed in submission order within a single queue | |
VkPresentInfoKHR presentInfo = {}; | |
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; | |
presentInfo.waitSemaphoreCount = 1; | |
presentInfo.pWaitSemaphores = &renderingFinishedSemaphore; | |
presentInfo.swapchainCount = 1; | |
presentInfo.pSwapchains = &swapChain; | |
presentInfo.pImageIndices = &imageIndex; | |
res = vkQueuePresentKHR(presentQueue, &presentInfo); | |
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR || windowResized) { | |
onWindowSizeChanged(); | |
} else if (res != VK_SUCCESS) { | |
std::cerr << "failed to submit present command buffer" << std::endl; | |
exit(1); | |
} | |
} | |
}; | |
int main() { | |
TriangleApplication app; | |
app.run(); | |
return 0; | |
} |
damn bro wrote 1.3k lines for a triangle opengl you need 5
@Overv
Hey, do you have any intentions of ever completing this? This is an incomplete source of information without the shaders. You are 99.9% of the way to having a very valuable resource here.
@jacksonlevine I have completed it as part of my Vulkan tutorial: https://github.com/Overv/VulkanTutorial
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks god I decided to go for GLFW + OpenGL (but in all fairness this seems to be creating the context by itself)