-
-
Save Overv/7ac07356037592a121225172d7d78f2d to your computer and use it in GitHub Desktop.
#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; | |
} |
@MohammadFakhreddin I'm not sure where you found this gist, but it's pretty similar to the Hello Triangle code in my Vulkan tutorial, which includes links to the shader files: https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation#page_Handling-minimization
@Overv Thanks
It doesn't seem to work for me? also the shaders there are spirv and the shaders in that link seem to be in glsl?
Thanks god I decided to go for GLFW + OpenGL (but in all fairness this seems to be creating the context by itself)
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
Thank you for sharing this complete example.
Are the shaders for this example available in github as well ?