commit b1720560a64d5014ca7ed5a10d0cbe3c0d062745 Author: Darren VanBuren Date: Sun Oct 30 01:21:33 2016 -0700 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bbd65b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +VULKAN_SDK_PATH = /mnt/storage/Downloads/VulkanSDK/1.0.30.0/x86_64 +CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include +LDFLAGS = -L$(VULKAN_SDK_PATH)/lib -L/usr/local/lib -lglfw3 -lrt -lm -ldl -lXrandr -lXinerama -lXxf86vm -lXext -lXcursor -lXrender -lXfixes -lX11 -lpthread -lxcb -lXau -lvulkan + +VulkanTest: main.cpp + g++ $(CFLAGS) -g -o VulkanTest main.cpp $(LDFLAGS) + +.PHONY: test clean + +test: VulkanTest + LD_LIBRARY_PATH=$(VULKAN_SDK_PATH)/lib VK_LAYER_PATH=$(VULKAN_SDK_PATH)/etc/explicit_layer.d ./VulkanTest + +clean: + rm -f VulkanTest diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c7ac506 --- /dev/null +++ b/main.cpp @@ -0,0 +1,768 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +const int WIDTH = 1200; +const int HEIGHT = 900; + +const std::vector validationLayers = { + "VK_LAYER_LUNARG_standard_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG + const bool enableValidationLayers = false; +#else + const bool enableValidationLayers = true; +#endif + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + } + + // our debug callback function, just prints the message from the Validation layer to stderr + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, + const char* layerPrefix, const char* msg, void* userData) { + std::cerr << "[Validation Layer]: " << msg << std::endl; + + return VK_FALSE; + } + +private: + // Instance variables: + GLFWwindow* window; + VDeleter instance {vkDestroyInstance}; + VDeleter callback {instance, DestroyDebugReportCallbackEXT}; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkQueue graphicsQueue; + VDeleter device {vkDestroyDevice}; + VDeleter surface {instance, vkDestroySurfaceKHR}; + VkQueue presentQueue; + VDeleter swapChain {device, vkDestroySwapchainKHR}; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector> swapChainImageViews; + VDeleter renderPass {device, vkDestroyRenderPass}; + VDeleter pipelineLayout {device, vkDestroyPipelineLayout}; + VDeleter graphicsPipeline {device, vkDestroyPipeline}; + std::vector> swapChainFramebuffers; + VDeleter commandPool {device, vkDestroyCommandPool}; + + // Initialize our GLFW window. + void initWindow() { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugCallback(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + } + + // Figure out what extensions GLFW requires to make a Vulkan surface. + // Also add the debug report extension, so we can add our debug report callback function. + std::vector getRequiredExtensions() { + std::vector extensions; + + unsigned int glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + for(unsigned int i = 0; i < glfwExtensionCount; i++) { + extensions.push_back(glfwExtensions[i]); + } + + if(enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } + + return extensions; + } + + // Create our VkInstance with the validation layers enabled and the extensions needed for + // presentation on our surface GLFW will create. + void createInstance() { + if(enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not supported"); + } + + // Create VkApplicationInfo + VkApplicationInfo appInfo = {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + // Create VkInstanceCreateInfo and have it point to VkApplicationInfo + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + // Enable the validation layers if they are requested + if(enableValidationLayers) { + createInfo.enabledLayerCount = validationLayers.size(); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + auto reqExtensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = reqExtensions.size(); + createInfo.ppEnabledExtensionNames = reqExtensions.data(); + + if(vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create Vulkan instance!"); + } + + uint32_t extensionCount = 0; + + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + std::vector extensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + + std::cout << "available extensions:" << std::endl; + + for(const auto& extension : extensions) { + std::cout << "\t" << extension.extensionName << std::endl; + } + } + + // Check if the validation layers are supported.s + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for(const char* layerName : validationLayers) { + bool layerFound = false; + + for(const auto& layerProperties : availableLayers) { + if(strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if(!layerFound) { + return false; + } + } + + return true; + } + + // Set up our debug report callback to get information back from the validation layers. + void setupDebugCallback() { + if (!enableValidationLayers) return; + + VkDebugReportCallbackCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + createInfo.pfnCallback = debugCallback; + + if(CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to setup debug callback!"); + } + } + + // Pick the first suitable physical device, using isDeviceSuitable(VkPhysicalDevice) + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if(deviceCount == 0) { + throw std::runtime_error("Failed to find a device with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for(const auto& device : devices) { + if(isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if(physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("Failed to find a suitable device!"); + } + } + + // Determine if the chosen physical device is suitable for our application. + bool isDeviceSuitable(VkPhysicalDevice device) { + /* example: device must be a discrete GPU and support geometry shaders + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceFeatures.geometryShader; + */ + + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + + if(extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + // Check in the given physical device supports the extensions we require. + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for(const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + // Find the queue families that support graphics and present. These could be + // two different queue families, or the same one. + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for(const auto& queueFamily : queueFamilies) { + if(queueFamily.queueCount > 0 && (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) { + indices.graphicsFamily = i; + } + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if(queueFamily.queueCount > 0 && presentSupport) { + indices.presentFamily = i; + } + + if(indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + // Create the logical device with our required extensions, and the validation layers, + // then create the graphics and present queues. + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + + float queuePriority = 1.0f; + for(int queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures = {}; + + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); + + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if(enableValidationLayers) { + createInfo.enabledLayerCount = validationLayers.size(); + createInfo.ppEnabledLayerNames= validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if(vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + } + + // Have GLFW create a surface for us, this lets us not be concerned with the platform-specifics + // involved in surfaces. + void createSurface() { + if(glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + // Find out what formats and present modes the physical device supports. + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if(formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if(presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + // If the surface has no preference of format, use 24-bit BGR and the SRGB color space + if(availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { + return {VK_FORMAT_B8G8R8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + } + + // See if this same 24-bit BGR and SRGB combo is available + for(const auto& availableFormat : availableFormats) { + if(availableFormat.format == VK_FORMAT_B8G8R8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + // As a last resort, pick the surface's first preferred format + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { + // Look if the the surface supports the "mailbox" present mode (similar to triple buffering) + for(const auto& availablePresentMode : availablePresentModes) { + if(availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + // If not, use the first in first out present mode (similar to basic double buffered vsync) + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + // Swap extent is resolution of the swap chain images we will be drawing to + + // Attempt to use the current extent, if it is valid. + if(capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + VkExtent2D actualExtent = {WIDTH, HEIGHT}; + + // Get the size of extent we can use. + actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); + + return actualExtent; + } + } + + // Create the swap chain that will be used to submit completed frames to + void createSwapChain() { + SwapChainSupportDetails swapChainSupport= querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + // Attempt to use 1 + minimum image count for this device's swap chain support + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + // If this is too many, clamp to the maximum image count. + if(swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + // Number of layers each image in swap chain consists of (stereoscopic app might use 2 layers) + createInfo.imageArrayLayers = 1; + // Tell Vulkan we will be rendering directly to the images in this swap chain. + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + // Array of queue family indices we will use. Only used if they are different queue families. + uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + + // If graphics queue and present queue are on different queue families, we must tell Vulkan both queue + // families will be concurrently sharing this swap chain. + if(indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + // Graphics queue and present queue are on same queue family, + // so the swap chain does not need to be shared. + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; + createInfo.pQueueFamilyIndices = nullptr; + } + + // We do not want the swap chain images to be transformed, so use the current transform. + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + // Ignore alpha for blending with other windows. + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; // Clip pixels that are obscured for improved perf. + + // We have no old swap chain for now. Could be used if the window is resized or something + // and we want to create a new swap chain to match the new window size. + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if(vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + // Get the swapchain images. + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + // Store image format and extent of the swap chain images. + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + + // For each image in the swap chain create a VkImageView + for(uint32_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 = swapChainImageFormat; + + // all our components should only be "swizzled" with the identity matrix. + 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].replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VDeleter vertShaderModule{device, vkDestroyShaderModule}; + VDeleter fragShaderModule{device, vkDestroyShaderModule}; + + createShaderModule(vertShaderCode, vertShaderModule); + createShaderModule(fragShaderCode, fragShaderModule); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + 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 = {0, 0}; + scissor.extent = swapChainExtent; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = nullptr; + + if(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = nullptr; + + pipelineInfo.layout = pipelineLayout; + + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + } + + static std::vector readFile(const std::string& filename) { + // Start reading at end of the file, as binary. + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if(!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + + return buffer; + } + + void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = (uint32_t*) code.data(); + + if(vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subPass = {}; + subPass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subPass.colorAttachmentCount = 1; + subPass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subPass; + + if(vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter {device, vkDestroyFramebuffer}); + + for(size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { swapChainImageViews[i] }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if(vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + + } + } + + void mainLoop() { + // While we shouldn't close, have glfw poll for events + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::runtime_error& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/main.h b/main.h new file mode 100644 index 0000000..9cc1fd1 --- /dev/null +++ b/main.h @@ -0,0 +1,91 @@ +// Wrappers for extension functions that must be located using vkGetInstanceProcAddr +VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, +const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { + auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + if(func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); + if(func != nullptr) { + func(instance, callback, pAllocator); + } +} + +// VDeleter utility class that automatically destroys objects when they fall out of scope. +template +class VDeleter { +public: + VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} + + VDeleter(std::function deletef) { + this->deleter = [=](T obj) { deletef(obj, nullptr); }; + } + + VDeleter(const VDeleter& instance, std::function deletef) { + this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; + } + + VDeleter(const VDeleter& device, std::function deletef) { + this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; + } + + ~VDeleter() { + cleanup(); + } + + const T* operator &() const { + return &object; + } + + T* replace() { + cleanup(); + return &object; + } + + operator T() const { + return object; + } + + void operator=(T rhs) { + if (rhs != object) { + cleanup(); + object = rhs; + } + } + + template + bool operator==(V rhs) { + return object == T(rhs); + } + +private: + T object{VK_NULL_HANDLE}; + std::function deleter; + + void cleanup() { + if (object != VK_NULL_HANDLE) { + deleter(object); + } + object = VK_NULL_HANDLE; + } +}; + +struct QueueFamilyIndices { + int graphicsFamily = -1; + int presentFamily = -1; + + bool isComplete() { + return graphicsFamily >= 0 && presentFamily >= 0; + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; diff --git a/shaders/compile.bat b/shaders/compile.bat new file mode 100644 index 0000000..4c87d21 --- /dev/null +++ b/shaders/compile.bat @@ -0,0 +1,3 @@ +C:/VulkanSDK/1.0.30.0/Bin32/glslangValidator.exe -V shader.vert +C:/VulkanSDK/1.0.30.0/Bin32/glslangValidator.exe -V shader.frag +pause \ No newline at end of file diff --git a/shaders/compile.sh b/shaders/compile.sh new file mode 100755 index 0000000..d914538 --- /dev/null +++ b/shaders/compile.sh @@ -0,0 +1,2 @@ +/mnt/storage/Downloads/VulkanSDK/1.0.30.0/x86_64/bin/glslangValidator -V shader.vert +/mnt/storage/Downloads/VulkanSDK/1.0.30.0/x86_64/bin/glslangValidator -V shader.frag \ No newline at end of file diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..ea83d82 --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,10 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..2e0c442 --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,25 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +out gl_PerVertex { + vec4 gl_Position; +}; + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file