Add stanford dragon model

This commit is contained in:
Lillian Salehi 2024-10-24 20:21:10 -05:00
parent 7d2949ca73
commit 5c2f82b995
35 changed files with 2729087 additions and 676 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
.gitignore vendored
View File

@ -35,3 +35,5 @@
*.out
*.app
build/
compile_commands.json

View File

@ -1,4 +1,4 @@
CPPFLAGS=-g
CPPFLAGS=-std=c++23 -g
LDFLAGS=-lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi -ltinyobjloader
DEBUGFLAGS=-DDEBUG -fsanitize=address
GDBFLAGS=
@ -42,7 +42,7 @@ $(BIN): $(OBJ) $(SPV)
g++ $(CPPFLAGS) -o $(BIN) $(OBJ) $(LDFLAGS)
%.o: %.cpp
g++ -c -g $< -o $@ $(LDFLAGS)
g++ -c $(CPPFLAGS) $< -o $@ $(LDFLAGS)
%.spv: %.frag
glslc $< -o $@

File diff suppressed because it is too large Load Diff

18901
assets/models/teapot.obj Normal file

File diff suppressed because it is too large Load Diff

16053
assets/models/viking_room.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,150 +0,0 @@
#include "vulkandebuglibs.h"
// This is our messenger object! It handles passing along debug messages to the debug callback we will also set.
VkDebugUtilsMessengerEXT debugMessenger;
// This is the set of "layers" to hook into. Basically, layers are used to tell the messenger what data we want, its a filter. *validation* is the general blanket layer to cover incorrect usage.
namespace debug_libs {
std::vector<const char*> getRequiredExtensions() {
// This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed
// So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions!
// From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic!
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if(Global::enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) {
// One hell of a function, this is using the *PFN_vkDestroyDebugUtilsMessengerEXT* prototype, the prototype for an, "Application-defined debug messenger callback function".
// The VKAPI_CALL and VKAPI_ATTR ensure that the function has the right signature for vulkan to call it. The callback message can be anything from a diagnostic to error!
// You can even sort by those diagnostics with their flags, since they are just integers, maybe TODO?
std::cerr << "Validation layer: " << pCallbackData->pMessage << std::endl;
std::cout << "\n";
return VK_FALSE;
}
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
// There is absolutely nothing about this i like, those long ass flags for messageType and Severity are just fucking hex values. Khronos should never cook again ToT
// On a serious note, this is just a struct to define the parameters of the debug messenger, nothing super special.
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional
}
void Debug::vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInstance& instance) {
// This function is quite useful, we first populate the debug create info structure, all the parameters dictating how the debug messenger will operate.
// The reason we populate the debug messenger so late is actually on purpose, we need to set the createInfo, which depends on the debugMessenger info,
// and if we set it before the creation of the instance, we cant debug vkCreateInstance or vkDestroyInstance! It's timed perfectly as of now.
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
if(Global::enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(Global::validationLayers.size());
createInfo.ppEnabledLayerNames = Global::validationLayers.data();
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
} else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = nullptr;
}
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
}
void Debug::checkUnavailableValidationLayers() {
// Check if we are trying to hook validation layers in without support.
if(Global::enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("Validation layers request, but not available! Are your SDK path variables set?");
}
}
bool Debug::checkValidationLayerSupport() {
// This function is used to check Validation Layer Support, validation layers are the debug trace tools in the Vulkan SDK.
// layerCount will be used as the var to keep track of the number of requested validation layer
// VkLayerProperties is a structure with data on the layername, desc, versions and etc.
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
for(const char* layerName : Global::validationLayers) {
bool layerFound = false;
for(const auto& layerProperties : availableLayers) {
if(strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if(!layerFound) {
return false;
}
}
return true;
}
VkResult CreateDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger) {
// This function builds out debug messenger structure!
// It's a little odd, we have to look up the address of the vkCreateDebugUtilsMessengerEXT ourselves because its an extension function,
// therefore, not auto-loaded.
auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void Debug::DestroyDebugUtilsMessengerEXT(VkInstance instance,
const VkAllocationCallbacks* pAllocator) {
// We are doing kind of the same thing as before in the create function, find the address of the DestroyDebugUtils function, and call it.
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if(func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
void Debug::setupDebugMessenger(VkInstance& vulkanInstance) {
// This is a pretty simple function! we just pass in the values to build the debug messenger, populate the structure with the data we want,
// and safely create it, covering for runtime errors as per usual, this is the first thing that will be called!
if(!Global::enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
if(CreateDebugUtilsMessengerEXT(vulkanInstance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("Failed to set up the Debug Messenger!");
}
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include <cstring>
#include "../global.h"
namespace debug_libs {
class Debug {
public:
static void vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInstance& instance);
static bool checkValidationLayerSupport();
static void checkUnavailableValidationLayers();
static void setupDebugMessenger(VkInstance& vulkanInstance);
static void DestroyDebugUtilsMessengerEXT(VkInstance instance, const VkAllocationCallbacks* pAllocator);
};
}

View File

@ -1,4 +1,5 @@
#include "devicelibrary.h"
#include <vulkan/vulkan_core.h>
namespace device_libs {
@ -7,7 +8,6 @@ namespace device_libs {
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
std::vector<VkImageView> swapChainImageViews;
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
@ -19,10 +19,12 @@ namespace device_libs {
};
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
// Swap chains are weird ngl, it's another one of those Vulkan platform agnosticity. The swapchain is basically a wrapper for GDI+, DXGI, X11, Wayland, etc.
// It lets us use the swap chain rather than create a different framebuffer handler for every targeted platform.
// Swap chains handle the ownership of buffers before sending them to the presentation engine.
// (still no fucking clue how it works though)
/* Swap chains are weird ngl, it's another one of those Vulkan platform agnosticity.
The swapchain is basically a wrapper for GDI+, DXGI, X11, Wayland, etc.
It lets us use the swap chain rather than create a different framebuffer
handler for every targeted platform. Swap chains handle the ownership
of buffers before sending them to the presentation engine. (still no
fucking clue how it works though) */
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, Global::surface, &details.capabilities);
@ -32,7 +34,7 @@ namespace device_libs {
if(formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, details.formats.data());
vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
@ -106,12 +108,10 @@ namespace device_libs {
// This is most similarly to standard V-Sync.
for(const auto& availablePresentMode : availablePresentModes) {
if(availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
if(Global::enableValidationLayers) std::cout << "Using Triple Buffering\n" << std::endl;
return availablePresentMode;
}
}
if(Global::enableValidationLayers) std::cout << "Using FIFO (V-Sync)\n" << std::endl;
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) {
@ -149,7 +149,6 @@ namespace device_libs {
for(const auto& device : devices) {
if(isDeviceSuitable(device)) {
if(Global::enableValidationLayers) std::cout << "Using device: " << deviceProperties.deviceName << std::endl;
//Once we have buttons or such, maybe ask the user or write a config file for which GPU to use?
Global::physicalDevice = device;
break;
@ -161,13 +160,11 @@ namespace device_libs {
}
void DeviceControl::destroySurface(VkInstance& instance) {
vkDestroySurfaceKHR(instance, Global::surface, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed surface safely\n" << std::endl;
}
void DeviceControl::createSurface(VkInstance& instance, GLFWwindow* window) {
if(glfwCreateWindowSurface(instance, window, nullptr, &Global::surface) != VK_SUCCESS) {
throw std::runtime_error("Failed to create window surface!!");
}
if(Global::enableValidationLayers) std::cout << "GLFW Window Surface created successfully\n" << std::endl;
}
void DeviceControl::createLogicalDevice() {
// Describe how many queues we want for a single family (1) here, right now we are solely interested in graphics capabilites,
@ -190,28 +187,33 @@ namespace device_libs {
queueCreateSingularInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateSingularInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
deviceFeatures.samplerAnisotropy = VK_TRUE;
VkPhysicalDeviceVulkan13Features features13 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = nullptr,
.dynamicRendering = true,
};
VkPhysicalDeviceFeatures featuresBase {
.samplerAnisotropy = true,
};
VkPhysicalDeviceFeatures2 deviceFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &features13,
.features = featuresBase,
};
VkDeviceCreateInfo createDeviceInfo = {};
createDeviceInfo.pNext = &deviceFeatures;
createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data();
createDeviceInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createDeviceInfo.pEnabledFeatures = &deviceFeatures;
createDeviceInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createDeviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
if(Global::enableValidationLayers) {
createDeviceInfo.enabledLayerCount = static_cast<uint32_t>(Global::validationLayers.size());
createDeviceInfo.ppEnabledLayerNames = Global::validationLayers.data();
} else {
createDeviceInfo.enabledLayerCount = 0;
}
if(vkCreateDevice(Global::physicalDevice, &createDeviceInfo, nullptr, &Global::device) != VK_SUCCESS) {
throw std::runtime_error("Failed to create logical device");
}
if(Global::enableValidationLayers) std::cout << "Created Logical device successfully!\n" << std::endl;
vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, &Global::graphicsQueue);
vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, &Global::presentQueue);
}
@ -272,7 +274,6 @@ namespace device_libs {
if(vkCreateSwapchainKHR(Global::device, &createSwapChainInfo, nullptr, &Global::swapChain) != VK_SUCCESS) {
throw std::runtime_error("Failed to create the swap chain!!");
}
if(Global::enableValidationLayers) std::cout << "Swap Chain created successfully\n" << std::endl;
vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
@ -283,7 +284,6 @@ namespace device_libs {
}
void DeviceControl::destroySwapChain() {
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed Swap Chain safely\n" << std::endl;
}
VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipLevels) {
// This defines the parameters of a newly created image object!
@ -307,27 +307,26 @@ namespace device_libs {
return imageView;
}
void DeviceControl::createImageViews() {
swapChainImageViews.resize(swapChainImages.size());
Global::swapChainImageViews.resize(swapChainImages.size());
for (uint32_t i = 0; i < swapChainImages.size(); i++) {
swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
Global::swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
}
}
void DeviceControl::destroyImageViews() {
for (auto imageView : swapChainImageViews) {
for (auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
if(Global::enableValidationLayers) std::cout << "Image destroyed safely\n" << std::endl;
}
// --------------------------------------- Getters & Setters ------------------------------------------ //
VkFormat DeviceControl::getImageFormat() {
return swapChainImageFormat;
}
std::vector<VkImageView> DeviceControl::getSwapChainImageViews() {
return swapChainImageViews;
VkFormat* DeviceControl::getImageFormat() {
return &swapChainImageFormat;
}
VkExtent2D DeviceControl::getSwapChainExtent() {
return swapChainExtent;
}
std::vector<VkImage> DeviceControl::getSwapChainImages() {
return swapChainImages;
}
}

View File

@ -21,9 +21,9 @@ class DeviceControl {
static void destroyCommandPool();
// ---------- Getters & Setters ----------- //
static VkFormat getImageFormat();
static std::vector<VkImageView> getSwapChainImageViews();
static VkFormat* getImageFormat();
static VkExtent2D getSwapChainExtent();
static std::vector<VkImage> getSwapChainImages();
static std::vector<VkFramebuffer> getSwapChainFramebuffers();
};
}

View File

@ -27,8 +27,7 @@ void initWindow() {
}
void createInstance() {
debug_libs::Debug::checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers.
// Set application info for the vulkan instance!
VkApplicationInfo appInfo{};
@ -39,28 +38,38 @@ void createInstance() {
appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version
appInfo.apiVersion = VK_API_VERSION_1_3; // Tell vulkan what the highest API version we will allow this program to run on
// This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed
// So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions!
// From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic!
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure
createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it.
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
if (vkCreateInstance(&createInfo, nullptr, &vulkaninstance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
debug_libs::Debug::vulkanDebugSetup(createInfo, vulkaninstance); // Handoff to the debug library to wrap the validation libs in! (And set the window up!)
}
void initVulkan() {
// Initialize vulkan and set up pipeline.
createInstance();
debug_libs::Debug::setupDebugMessenger(vulkaninstance);
device_libs::DeviceControl::createSurface(vulkaninstance, Global::window);
device_libs::DeviceControl::pickPhysicalDevice(vulkaninstance);
device_libs::DeviceControl::createLogicalDevice();
device_libs::DeviceControl::createSwapChain(Global::window);
device_libs::DeviceControl::createImageViews();
graphics_pipeline::Graphics::createRenderPass();
buffers_libs::Buffers::createDescriptorSetLayout();
graphics_pipeline::Graphics::createGraphicsPipeline();
graphics_pipeline::Graphics::createCommandPool();
texture_libs::Texture::createDepthResources();
graphics_pipeline::Graphics::createFramebuffers();
texture_libs::Texture::createTextureImage();
texture_libs::Texture::createTextureImageView();
texture_libs::Texture::createTextureSampler();
@ -85,7 +94,7 @@ void mainLoop() {
void cleanup() {
render_present::Render::cleanupSwapChain();
graphics_pipeline::Graphics::destroyGraphicsPipeline();
graphics_pipeline::Graphics::destroyRenderPass();
//graphics_pipeline::Graphics::destroyRenderPass();
buffers_libs::Buffers::destroyUniformBuffer();
buffers_libs::Buffers::destroyDescriptorPool();
texture_libs::Texture::destroyTextureSampler();
@ -96,9 +105,6 @@ void cleanup() {
graphics_pipeline::Graphics::destroyCommandPool();
vkDestroyDevice(Global::device, nullptr);
if(Global::enableValidationLayers) {
debug_libs::Debug::DestroyDebugUtilsMessengerEXT(vulkaninstance, nullptr);
}
device_libs::DeviceControl::destroySurface(vulkaninstance);
vkDestroyInstance(vulkaninstance, nullptr);
glfwDestroyWindow(Global::window);

View File

@ -1,4 +1,3 @@
#include "debug/vulkandebuglibs.h"
#include "graphics/graphicspipeline.h"
#include "graphics/render.h"
#include "global.h"

View File

@ -2,15 +2,6 @@
namespace Global {
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
#ifdef DEBUG
const bool enableValidationLayers = true;
#else
const bool enableValidationLayers = false;
#endif
VkSurfaceKHR surface;
VkDevice device;
VkPhysicalDevice physicalDevice;
@ -29,6 +20,7 @@ namespace Global {
VkImage depthImage;
VkDeviceMemory depthImageMemory;
std::vector<VkImageView> swapChainImageViews;
std::vector<Vertex> vertices;
// Index buffer definition, showing which points to reuse.
std::vector<uint32_t> indices;

View File

@ -4,113 +4,119 @@
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/detail/qualifier.hpp>
#include <glm/ext/vector_float2.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/fwd.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <array>
#include <cstdint>
#include <iostream>
#include <optional>
#include <ostream>
#include <vector>
#include <optional>
#include <array>
namespace Global {
// Global variables and includes we are going to use almost everywhere, validation layers hook into everything, and you need to check if they are enabled first,
// so that's one obvious global, as well as the glfw includes!
// Global variables and includes we are going to use almost everywhere,
// validation layers hook into everything, and you need to check if they are
// enabled first, so that's one obvious global, as well as the glfw includes!
extern const std::vector<const char*> validationLayers;
extern const bool enableValidationLayers;
extern VkDevice device;
extern VkCommandPool commandPool;
extern std::vector<VkCommandBuffer> commandBuffers;
extern VkQueue graphicsQueue;
extern VkQueue presentQueue;
const int MAX_FRAMES_IN_FLIGHT = 2;
extern GLFWwindow* window;
extern VkDescriptorSetLayout descriptorSetLayout;
extern uint32_t currentFrame;
extern std::vector<VkDescriptorSet> descriptorSets;
extern VkImageView textureImageView;
extern VkSampler textureSampler;
extern VkImageView depthImageView;
extern VkImage depthImage;
extern VkDeviceMemory depthImageMemory;
const std::string MODEL_PATH = "assets/models/StanfordDragon800k.obj";
const std::string TEXTURE_PATH = "assets/textures/checkermap.png";
extern VkPhysicalDevice physicalDevice;
extern VkDevice device;
extern VkCommandPool commandPool;
extern std::vector<VkCommandBuffer> commandBuffers;
extern VkQueue graphicsQueue;
extern VkQueue presentQueue;
struct UniformBufferObject {
float time;
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 proj;
};
struct Vertex {
// This defines what a vertex is!
// We control the position, color and texture coordinate here!
glm::vec3 pos;
glm::vec3 color;
glm::vec2 texCoord;
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
extern GLFWwindow *window;
extern VkSurfaceKHR surface;
extern uint32_t currentFrame;
return bindingDescription;
}
static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
extern std::vector<VkDescriptorSet> descriptorSets;
extern VkDescriptorSetLayout descriptorSetLayout;
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
extern VkImageView textureImageView;
extern VkSampler textureSampler;
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
extern VkImage depthImage;
extern VkImageView depthImageView;
extern VkDeviceMemory depthImageMemory;
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
return attributeDescriptions;
}
bool operator==(const Vertex& other) const {
return pos == other.pos && color == other.color && texCoord == other.texCoord;
}
};
extern VkSwapchainKHR swapChain;
extern std::vector<VkImageView> swapChainImageViews;
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
extern std::vector<Vertex> vertices;
// Index buffer definition, showing which points to reuse.
extern std::vector<uint32_t> indices;
struct QueueFamilyIndices {
// We need to check that the Queue families support graphics operations and window presentation, sometimes they can support one or the other,
// therefore, we take into account both for completion.
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
const std::string MODEL_PATH = "assets/models/teapot.obj";
const std::string TEXTURE_PATH = "assets/textures/checkermap.png";
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
const int MAX_FRAMES_IN_FLIGHT = 2;
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
extern VkSwapchainKHR swapChain;
extern VkSurfaceKHR surface;
extern VkPhysicalDevice physicalDevice;
Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
}
struct UniformBufferObject {
float time;
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 proj;
};
struct Vertex {
// This defines what a vertex is!
// We control the position, color and texture coordinate here!
glm::vec3 pos;
glm::vec3 color;
glm::vec2 texCoord;
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescription;
}
static std::array<VkVertexInputAttributeDescription, 3>
getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
return attributeDescriptions;
}
bool operator==(const Vertex &other) const {
return pos == other.pos && color == other.color &&
texCoord == other.texCoord;
}
};
extern std::vector<Vertex> vertices;
// Index buffer definition, showing which points to reuse.
extern std::vector<uint32_t> indices;
struct QueueFamilyIndices {
// We need to check that the Queue families support graphics operations and
// window presentation, sometimes they can support one or the other,
// therefore, we take into account both for completion.
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
} // namespace Global

View File

@ -113,7 +113,7 @@ namespace buffers_libs {
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
void* data;
vkMapMemory(Global::device, stagingBufferMemory, 0, bufferSize, 0, &data);
@ -285,4 +285,3 @@ namespace buffers_libs {
vkDestroyDescriptorPool(Global::device, descriptorPool, nullptr);
}
}

View File

@ -1,362 +1,355 @@
#include "graphicspipeline.h"
#include "texture.h"
#include <vulkan/vulkan_core.h>
namespace graphics_pipeline {
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR};
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
static std::vector<char> readFile(const std::string &filename) {
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<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
VkShaderModule createShaderModule(const std::vector<char> &code,
VkDevice &device) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t *>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) !=
VK_SUCCESS) {
throw std::runtime_error("failed to create shader module!");
}
return shaderModule;
}
void Graphics::destroyGraphicsPipeline() {
vkDestroyPipeline(Global::device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(Global::device, pipelineLayout, nullptr);
}
void Graphics::createGraphicsPipeline() {
// Note to self, for some reason the working directory is not where a read
// file is called from, but the project folder!
auto vertShaderCode = readFile("src/shaders/vertex.spv");
auto fragShaderCode = readFile("src/shaders/fragment.spv");
VkShaderModule vertShaderModule =
createShaderModule(vertShaderCode, Global::device);
VkShaderModule fragShaderModule =
createShaderModule(fragShaderCode, Global::device);
// ------------------ STAGE 1 - INPUT ASSEMBLER ---------------- //
// This can get a little complicated, normally, vertices are loaded in
// sequential order, with an element buffer however, you can specify the
// indices yourself! Using an element buffer means you can reuse vertices,
// which can lead to optimizations. If you set PrimRestart to TRUE, you can
// utilize the _STRIP modes with special indices
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType =
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// ------------------ STAGE 2 - VERTEX SHADER ------------------ //
// this will be revisited, right now we are hardcoding shader data, so we tell
// it to not load anything, but that will change.
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";
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType =
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
auto bindingDescription = Global::Vertex::getBindingDescription();
auto attributeDescriptions = Global::Vertex::getAttributeDescriptions();
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
// ------------------- STAGE 5 - RASTERIZATION ----------------- //
// Take Vertex shader vertices and fragmentize them for the frament shader.
// The rasterizer also can perform depth testing, face culling, and scissor
// testing. In addition, it can also be configured for wireframe rendering.
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
// Render regardless of the near and far planes, useful for shadow maps,
// requires GPU feature *depthClamp*
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
// MODE_FILL, fill polygons, MODE_LINE, draw wireframe, MODE_POINT, draw
// vertices. Anything other than fill requires GPU feature *fillModeNonSolid*
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
// How to cull the faces, right here we cull the back faces and tell the
// rasterizer front facing vertices are ordered clockwise.
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
// Whether or not to add depth values. e.x. for shadow maps.
rasterizer.depthBiasEnable = VK_FALSE;
// ------------------ STAGE 6 - FRAGMENT SHADER ---------------- //
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};
// ------------------ STAGE 7 - COLOR BLENDING ----------------- //
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_FALSE;
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;
// ---------------------- STATE CONTROLS ---------------------- //
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
// Again, this will be revisited, multisampling can be very useful for
// anti-aliasing, since it is fast, but we won't implement it for now.
// Requires GPU feature UNKNOWN eanbled.
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType =
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// TODO: Document!
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType =
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.stencilTestEnable = VK_FALSE;
// Most of the graphics pipeline is set in stone, some of the pipeline state
// can be modified without recreating it at runtime though! There are TONS of
// settings, this would be another TODO to see what else we can mess with
// dynamically, but right now we just allow dynamic size of the viewport and
// dynamic scissor states. Scissors are pretty straightforward, they are
// basically pixel masks for the rasterizer. Scissors describe what regions
// pixels will be stored, it doesnt cut them after being rendered, it stops
// them from ever being rendered in that area in the first place.
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &Global::descriptorSetLayout;
if (vkCreatePipelineLayout(Global::device, &pipelineLayoutInfo, nullptr,
&pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
VkPipelineRenderingCreateInfo pipelineRenderingCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = device_libs::DeviceControl::getImageFormat(),
.depthAttachmentFormat = texture_libs::Texture::findDepthFormat(),
};
VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
std::vector<VkFramebuffer> swapChainFramebuffers;
// Here we combine all of the structures we created to make the final
// pipeline!
VkGraphicsPipelineCreateInfo pipelineInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &pipelineRenderingCreateInfo,
.stageCount = 2,
.pStages = shaderStages,
.pVertexInputState = &vertexInputInfo,
.pInputAssemblyState = &inputAssembly,
.pViewportState = &viewportState,
.pRasterizationState = &rasterizer,
.pMultisampleState = &multisampling,
.pDepthStencilState = &depthStencil,
.pColorBlendState = &colorBlending,
.pDynamicState = &dynamicState,
.layout = pipelineLayout,
.renderPass = nullptr,
.subpass = 0,
};
static std::vector<char> readFile(const std::string& filename) {
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<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
if (vkCreateGraphicsPipelines(Global::device, VK_NULL_HANDLE, 1,
&pipelineInfo, nullptr,
&graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
VkShaderModule createShaderModule(const std::vector<char>& code, VkDevice& device) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create shader module!");
}
return shaderModule;
}
void Graphics::destroyGraphicsPipeline() {
vkDestroyPipeline(Global::device, graphicsPipeline, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed Graphics Pipeline safely\n" << std::endl;
vkDestroyPipelineLayout(Global::device, pipelineLayout, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed Layout Pipeline safely\n" << std::endl;
}
void Graphics::createGraphicsPipeline() {
// Note to self, for some reason the working directory is not where a read file is called from, but the project folder!
auto vertShaderCode = readFile("src/shaders/vertex.spv");
auto fragShaderCode = readFile("src/shaders/fragment.spv");
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode, Global::device);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode, Global::device);
// ------------------ STAGE 1 - INPUT ASSEMBLER ---------------- //
// This can get a little complicated, normally, vertices are loaded in sequential order, with an element buffer however, you can specify the indices yourself!
// Using an element buffer means you can reuse vertices, which can lead to optimizations. If you set PrimRestart to TRUE, you can utilize the _STRIP modes with special indices
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// ------------------ STAGE 2 - VERTEX SHADER ------------------ //
// this will be revisited, right now we are hardcoding shader data, so we tell it to not load anything, but that will change.
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";
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
auto bindingDescription = Global::Vertex::getBindingDescription();
auto attributeDescriptions = Global::Vertex::getAttributeDescriptions();
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
// ------------------- STAGE 5 - RASTERIZATION ----------------- //
// Take Vertex shader vertices and fragmentize them for the frament shader. The rasterizer also can perform depth testing, face culling, and scissor testing.
// In addition, it can also be configured for wireframe rendering.
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
// Render regardless of the near and far planes, useful for shadow maps, requires GPU feature *depthClamp*
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
// MODE_FILL, fill polygons, MODE_LINE, draw wireframe, MODE_POINT, draw vertices. Anything other than fill requires GPU feature *fillModeNonSolid*
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
// How to cull the faces, right here we cull the back faces and tell the rasterizer front facing vertices are ordered clockwise.
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
// Whether or not to add depth values. e.x. for shadow maps.
rasterizer.depthBiasEnable = VK_FALSE;
// ------------------ STAGE 6 - FRAGMENT SHADER ---------------- //
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};
// ------------------ STAGE 7 - COLOR BLENDING ----------------- //
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_FALSE;
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;
// ---------------------- STATE CONTROLS ---------------------- //
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
// Again, this will be revisited, multisampling can be very useful for anti-aliasing, since it is fast, but we won't implement it for now.
// Requires GPU feature UNKNOWN eanbled.
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// TODO: Document!
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.stencilTestEnable = VK_FALSE;
// Most of the graphics pipeline is set in stone, some of the pipeline state can be modified without recreating it at runtime though!
// There are TONS of settings, this would be another TODO to see what else we can mess with dynamically, but right now we just allow dynamic size of the viewport
// and dynamic scissor states. Scissors are pretty straightforward, they are basically pixel masks for the rasterizer.
// Scissors describe what regions pixels will be stored, it doesnt cut them after being rendered, it stops them from ever being rendered in that area in the first place.
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &Global::descriptorSetLayout;
if (vkCreatePipelineLayout(Global::device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
// Here we combine all of the structures we created to make the final pipeline!
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.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.pDepthStencilState = &depthStencil;
if (vkCreateGraphicsPipelines(Global::device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
vkDestroyShaderModule(Global::device, fragShaderModule, nullptr);
vkDestroyShaderModule(Global::device, vertShaderModule, nullptr);
if(Global::enableValidationLayers) std::cout << "Pipeline Layout created successfully\n" << std::endl;
}
void Graphics::createRenderPass() {
VkAttachmentDescription colorAttachment{};
colorAttachment.format = device_libs::DeviceControl::getImageFormat();
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;
VkAttachmentDescription depthAttachment{};
depthAttachment.format = texture_libs::Texture::findDepthFormat();
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
if (vkCreateRenderPass(Global::device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create render pass!");
}
if(Global::enableValidationLayers) std::cout << "Render pass created successfully\n" << std::endl;
}
void Graphics::destroyRenderPass() {
vkDestroyRenderPass(Global::device, renderPass, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed Render Pass Safely\n" << std::endl;
}
void Graphics::createFramebuffers() {
// Resize the container to hold all the framebuffers.
int framebuffersSize = device_libs::DeviceControl::getSwapChainImageViews().size();
swapChainFramebuffers.resize(framebuffersSize);
for(size_t i = 0; i < framebuffersSize; i++) {
std::array<VkImageView, 2> attachments = {
device_libs::DeviceControl::getSwapChainImageViews()[i],
Global::depthImageView
};
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
framebufferInfo.pAttachments = attachments.data();
framebufferInfo.width = device_libs::DeviceControl::getSwapChainExtent().width;
framebufferInfo.height = device_libs::DeviceControl::getSwapChainExtent().height;
framebufferInfo.layers = 1;
if(vkCreateFramebuffer(Global::device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("Failed to create framebuffer!");
}
}
}
void Graphics::createCommandPool() {
// Commands in Vulkan are not executed using function calls, you have to record the ops you wish to perform
// to command buffers, pools manage the memory used by the buffer!
Global::QueueFamilyIndices queueFamilyIndices = Global::findQueueFamilies(Global::physicalDevice);
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
if(vkCreateCommandPool(Global::device, &poolInfo, nullptr, &Global::commandPool) != VK_SUCCESS) {
throw std::runtime_error("Failed to create command pool!");
}
if(Global::enableValidationLayers) std::cout << "Command pool created successfully\n" << std::endl;
}
void Graphics::destroyCommandPool() {
vkDestroyCommandPool(Global::device, Global::commandPool, nullptr);
}
void Graphics::createCommandBuffer() {
Global::commandBuffers.resize(Global::MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = Global::commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t) Global::commandBuffers.size();
if(vkAllocateCommandBuffers(Global::device, &allocInfo, Global::commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers");
}
if(Global::enableValidationLayers) std::cout << "Allocated command buffers successfully\n" << std::endl;
}
void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = device_libs::DeviceControl::getSwapChainExtent();
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) device_libs::DeviceControl::getSwapChainExtent().width;
viewport.height = (float) device_libs::DeviceControl::getSwapChainExtent().height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = device_libs::DeviceControl::getSwapChainExtent();
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
VkBuffer vertexBuffers[] = {buffers_libs::Buffers::getVertexBuffer()};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, buffers_libs::Buffers::getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &Global::descriptorSets[Global::currentFrame], 0, nullptr);
vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(Global::indices.size()), 1, 0, 0, 0);
vkCmdEndRenderPass(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
}
std::vector<VkFramebuffer> Graphics::getSwapChainFramebuffers() {
return swapChainFramebuffers;
}
vkDestroyShaderModule(Global::device, fragShaderModule, nullptr);
vkDestroyShaderModule(Global::device, vertShaderModule, nullptr);
}
void Graphics::createCommandPool() {
// Commands in Vulkan are not executed using function calls, you have to
// record the ops you wish to perform to command buffers, pools manage the
// memory used by the buffer!
Global::QueueFamilyIndices queueFamilyIndices =
Global::findQueueFamilies(Global::physicalDevice);
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
if (vkCreateCommandPool(Global::device, &poolInfo, nullptr,
&Global::commandPool) != VK_SUCCESS) {
throw std::runtime_error("Failed to create command pool!");
}
}
void Graphics::destroyCommandPool() {
vkDestroyCommandPool(Global::device, Global::commandPool, nullptr);
}
void Graphics::createCommandBuffer() {
Global::commandBuffers.resize(Global::MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = Global::commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)Global::commandBuffers.size();
if (vkAllocateCommandBuffers(Global::device, &allocInfo,
Global::commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers");
}
}
void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer,
uint32_t imageIndex) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
const VkImageMemoryBarrier imageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.image = device_libs::DeviceControl::getSwapChainImages()[imageIndex],
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = texture_libs::Texture::getMipLevels(),
.baseArrayLayer = 0,
.layerCount = 1,
}};
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imageMemoryBarrier
);
// ------------------- DYNAMIC RENDER INFO ---------------------- //
const VkRenderingAttachmentInfo colorAttachmentInfo{
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = Global::swapChainImageViews[imageIndex],
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.color = {0.0f, 0.0f, 0.0f, 1.0f}},
};
const VkRenderingAttachmentInfo depthAttachmentInfo{
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = Global::depthImageView,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.depthStencil = {1.0f, 0}},
};
const VkRenderingInfo renderInfo{
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {.offset = {0, 0},
.extent =
device_libs::DeviceControl::getSwapChainExtent()},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachmentInfo,
.pDepthAttachment = &depthAttachmentInfo,
};
vkCmdBeginRendering(commandBuffer, &renderInfo);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
graphicsPipeline);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width =
(float)device_libs::DeviceControl::getSwapChainExtent().width;
viewport.height =
(float)device_libs::DeviceControl::getSwapChainExtent().height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = device_libs::DeviceControl::getSwapChainExtent();
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
VkBuffer vertexBuffers[] = {buffers_libs::Buffers::getVertexBuffer()};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, buffers_libs::Buffers::getIndexBuffer(),
0, VK_INDEX_TYPE_UINT32);
vkCmdBindDescriptorSets(
commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1,
&Global::descriptorSets[Global::currentFrame], 0, nullptr);
vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(Global::indices.size()),
1, 0, 0, 0);
vkCmdEndRendering(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
}
} // namespace graphics_pipeline

View File

@ -10,14 +10,11 @@ namespace graphics_pipeline {
public:
static void createGraphicsPipeline();
static void destroyGraphicsPipeline();
static void createRenderPass();
static void destroyRenderPass();
static void createFramebuffers();
static void destroyFramebuffers();
static void createCommandPool();
static void destroyCommandPool();
static void createCommandBuffer();
static void recordCommandBuffer(VkCommandBuffer cmndBuffer, uint32_t imageIndex);
static std::vector<VkFramebuffer> getSwapChainFramebuffers();
};
}

View File

@ -18,10 +18,8 @@ namespace render_present {
}
vkDeviceWaitIdle(Global::device);
// Don't really wanna do this but I also don't want to create an extra class instance just to call the cleanup function.
for(auto framebuffer : graphics_pipeline::Graphics::getSwapChainFramebuffers()) {
vkDestroyFramebuffer(Global::device, framebuffer, nullptr);
}
for(auto imageView : device_libs::DeviceControl::getSwapChainImageViews()) {
for(auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
@ -29,7 +27,6 @@ namespace render_present {
device_libs::DeviceControl::createSwapChain(Global::window);
device_libs::DeviceControl::createImageViews();
texture_libs::Texture::createDepthResources();
graphics_pipeline::Graphics::createFramebuffers();
}
// At a high level, rendering in Vulkan consists of 5 steps:
// Wait for the previous frame, acquire a image from the swap chain
@ -155,10 +152,8 @@ namespace render_present {
vkDestroyImageView(Global::device, Global::depthImageView, nullptr);
vkDestroyImage(Global::device, Global::depthImage, nullptr);
vkFreeMemory(Global::device, Global::depthImageMemory, nullptr);
for(auto framebuffer : graphics_pipeline::Graphics::getSwapChainFramebuffers()) {
vkDestroyFramebuffer(Global::device, framebuffer, nullptr);
}
for(auto imageView : device_libs::DeviceControl::getSwapChainImageViews()) {
for(auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);

View File

@ -10,4 +10,3 @@ int main() {
}
return EXIT_SUCCESS;
}