Implement a Vulkan Renderer module (#2)

* Starting work on the Vlkx renderer

* Fix renderer implementation

* Move GLM to FetchContent
This commit is contained in:
Curle 2022-07-19 19:41:44 +01:00 committed by GitHub
parent 3bf44e8985
commit a370f28f14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 21913 additions and 22 deletions

View File

@ -19,6 +19,20 @@ FetchContent_Declare(
FetchContent_MakeAvailable(Catch2)
# Fetch GLM for the renderer
FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 0.9.9.2
)
FetchContent_GetProperties(glm)
if(NOT glm_POPULATED)
FetchContent_Populate(glm)
set(GLM_TEST_ENABLE OFF CACHE BOOL "" FORCE)
add_subdirectory(${glm_SOURCE_DIR} ${glm_BINARY_DIR})
endif()
# Import some find files
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
@ -36,5 +50,8 @@ add_subdirectory(projs/shadow/shadow-reflection)
# Core engine
add_subdirectory(projs/shadow/shadow-engine)
# Renderer
add_subdirectory(projs/shadow/shadow-renderer)
# Runtime executable
add_subdirectory(projs/shadow/shadow-runtime)

View File

@ -9,4 +9,4 @@ FILE(GLOB_RECURSE HEADERS src/*.h)
add_library(shadow-engine ${SOURCES})
target_include_directories(shadow-engine PRIVATE ${SDL2_INCLUDE_DIRS} PUBLIC ${HEADERS})
target_link_libraries(shadow-engine PRIVATE Vulkan::Vulkan PUBLIC SDL2::Core shadow-reflect shadow-asset)
target_link_libraries(shadow-engine PRIVATE Vulkan::Vulkan PUBLIC SDL2::Core shadow-reflect shadow-asset PUBLIC renderer)

View File

@ -1,6 +1,9 @@
#include "ShadowApplication.h"
#include "Time.h"
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/render/Camera.h>
#include <vlkx/render/geometry/SingleRenderer.h>
namespace ShadowEngine {
@ -46,6 +49,8 @@ namespace ShadowEngine {
window_ = new ShadowWindow(800,450);
VulkanManager::getInstance()->initVulkan(window_->sdlWindowPtr);
/*
moduleManager.PushModule(new Log());
moduleManager.PushModule(new EventSystem::ShadowEventManager());
@ -70,6 +75,15 @@ namespace ShadowEngine {
void ShadowApplication::Start()
{
// Create the camera
Camera camera {};
camera.init(45, 1280, 720, 0.1, 10000);
camera.setPosition(glm::vec3(0, 0, 4));
// Create the renderer
SingleRenderer object;
object.createSingleRenderer(Geo::MeshType::Cube, glm::vec3(-1, 0, -1), glm::vec3(0.5));
SDL_Event event;
while (running)
{
@ -78,6 +92,16 @@ namespace ShadowEngine {
running = false;
}
object.setRotation(glm::rotate(object.getRotation(), (float) 0.5, glm::vec3(1, 0, 0)));
VulkanManager::getInstance()->startDraw();
object.updateUniforms(camera);
object.draw();
VulkanManager::getInstance()->endDraw();
Time::UpdateTime();
}
}

View File

@ -4,7 +4,7 @@
ShadowWindow::ShadowWindow(int W, int H) : Height(H), Width(W)
{
// Create our window
sdlWindowPtr = SDL_CreateWindow( "Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_SHOWN );
sdlWindowPtr = SDL_CreateWindow( "Candlefire", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN );
// Make sure creating the window succeeded
if ( !sdlWindowPtr ) {
@ -12,24 +12,6 @@ ShadowWindow::ShadowWindow(int W, int H) : Height(H), Width(W)
//std::cout << "Error creating window: " << SDL_GetError() << std::endl;
}
// Get the surface from the window
sdlSurface = SDL_GetWindowSurface( sdlWindowPtr );
// Make sure getting the surface succeeded
if ( !sdlSurface ) {
//Raise an error in the log
//std::cout << "Error getting surface: " << SDL_GetError() << std::endl;
}
// Create window
/*
this->winPtr = SDL_CreateWindow("Hello World!", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W, H,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
SH_CORE_ASSERT(winPtr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());
context = ShadowEngine::Rendering::GraphicsContext::Create(this);
context->Init();
*/
}
ShadowWindow::~ShadowWindow()

View File

@ -0,0 +1,10 @@
set(CMAKE_CXX_STANDARD 20)
find_package(Vulkan REQUIRED)
find_package(SDL2 REQUIRED)
FILE(GLOB_RECURSE SOURCES src/*.cpp inc/*.h)
add_library(renderer ${SOURCES})
target_include_directories(renderer PRIVATE ${SDL2_INCLUDE_DIRS} PUBLIC inc ${glm_SOURCE_DIR})
target_link_libraries(renderer PRIVATE SDL2::Main Vulkan::Vulkan)

View File

@ -0,0 +1,23 @@
#pragma once
#define GLM_FORCE_RADIAN
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
class Camera {
public:
// Set up the camera.
void init(float fov, float width, float height, float near, float far);
// Move the camera.
void setPosition(glm::vec3 positionIn);
glm::mat4 getViewMatrix() { return viewMatrix; };
glm::mat4 getProjectionMatrix() { return projectionMatrix; }
private:
glm::mat4 projectionMatrix;
glm::mat4 viewMatrix;
glm::vec3 position;
};

View File

@ -0,0 +1,94 @@
#pragma once
#include <vulkan/vulkan.h>
#include <array>
#include <vector>
#define GLM_FORCE_RADIAN
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
// Global namespace for all classes to do with geometry in a level.
namespace Geo {
// The core components of a given mesh.
enum MeshType {
Triangle, // A construction of triangles
Quad, // A construction of quads
Cube, // A single Cube.
Sphere // A single Sphere.
};
// Contains standard uniforms for shader files.
struct UniformBufferObject {
glm::mat4 model; // Model transform matrix.
glm::mat4 view; // View matrix.
glm::mat4 proj; // Projection matrix.
};
// All of the metadata involved with a vertex.
struct Vertex {
glm::vec3 position; // XYZ coordinates of the vertex's position.
glm::vec3 normal; // Unit vector pointing away from the outer surface of the vertex.
glm::vec3 color; // The color of the vertex.
glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture.
// How fast should vertex data be read from RAM?
static VkVertexInputBindingDescription getBindingDesc() {
VkVertexInputBindingDescription desc = {};
desc.binding = 0;
desc.stride = sizeof(Vertex);
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return desc;
}
// How should vertexes be handled?
static std::array<VkVertexInputAttributeDescription, 4> getAttributeDesc() {
std::array<VkVertexInputAttributeDescription, 4> descs = {};
// Attribute 0; position. Location 0, 3x 32-bit float.
descs[0].binding = 0;
descs[0].location = 0;
descs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
descs[0].offset = offsetof(Vertex, position);
// Attribute 1; normal. Location 1, 3x 32-bit float.
descs[1].binding = 0;
descs[1].location = 1;
descs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
descs[1].offset = offsetof(Vertex, normal);
// Attribute 2; color. Location 2, 3x 32-bit float.
descs[2].binding = 0;
descs[2].location = 2;
descs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
descs[2].offset = offsetof(Vertex, color);
// Attribute 3; texture. Location 3, 2x 32-bit float.
descs[3].binding = 0;
descs[3].location = 3;
descs[3].format = VK_FORMAT_R32G32_SFLOAT;
descs[3].offset = offsetof(Vertex, texture);
return descs;
}
};
// Contains data about a given Mesh.
class Mesh {
public:
// Pre-load the data for a triangle into the given buffers.
static void setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
// Pre-load the data for a quad into the given buffers.
static void setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
// Pre-load the data for a cube into the given buffers.
static void setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
// Pre-load the data for a sphere into the given buffers.
static void setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
};
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
class RenderPass {
public:
RenderPass();
~RenderPass();
VkRenderPass pass;
void createVertexRenderPass(VkFormat format);
void createRTRenderPass(VkFormat format);
void createRTPhysicsPass(VkFormat format);
void beginRenderPass(std::vector<VkClearValue> clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent);
void endRenderPass(VkCommandBuffer commands);
void destroy();
};

View File

@ -0,0 +1,33 @@
#pragma once
#include <vlkx/render/shader/Pipeline.h>
#include <vlkx/render/shader/GeoBuffers.h>
#include <vlkx/render/shader/Descriptor.h>
#include <vlkx/render/Camera.h>
// Renders a single object.
class SingleRenderer {
public:
void createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn);
void updateUniforms(Camera camera);
void draw();
void destroy();
void setPosition(glm::vec3 newPos) { position = newPos; }
void setRotation(glm::mat4 newRot) { rotation = newRot; }
glm::mat4 getRotation() { return rotation; }
private:
Pipeline pipeline;
GeoBuffers buffers;
Descriptor descriptor;
glm::vec3 position;
glm::vec3 scale;
glm::mat4 rotation;
};

View File

@ -0,0 +1,30 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
class Descriptor {
public:
Descriptor();
~Descriptor();
// Global descriptor bindings
VkDescriptorSetLayout layout;
VkDescriptorPool pool;
VkDescriptorSet set;
// Allocate and prepare the descriptors for a simple (uniform buffer only) descriptor.
void createAndAllocateSimple(uint32_t imageCount);
// Fill the Descriptors with the given uniforms
void populate(uint32_t imageCount, VkBuffer uniforms, size_t bufferSize);
void destroy();
private:
// Set up the layout for a single UBO
void createSimpleLayout();
// Setup the pool for a single UBO
void createSimplePool(uint32_t imageCount);
};

View File

@ -0,0 +1,37 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <vlkx/render/Geometry.h>
#include <vlkx/vulkan/Tools.h>
// Contains memory and objects required to store information about geometry.
class GeoBuffers {
public:
GeoBuffers();
~GeoBuffers();
// Program and virtual memory for vertex data.
std::vector<Geo::Vertex> vertices;
VkTools::ManagedBuffer vertexBuffer;
// Program and virtual memory for indices data.
std::vector<uint32_t> indices;
VkTools::ManagedBuffer indexBuffer;
// Virtual memory for uniforms - translation matrices, etc.
VkTools::ManagedBuffer uniformBuffer;
void createBuffers(Geo::MeshType type);
void destroy();
private:
void createVertexBuffer();
void createIndexBuffer();
void createUniformBuffer();
};

View File

@ -0,0 +1,31 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <fstream>
class Pipeline {
public:
Pipeline();
~Pipeline();
// The active Graphics Pipeline layout.
VkPipelineLayout layout;
// The active Graphics Pipeline instance.
VkPipeline pipeline;
// Create the layout and pipeline for a vertex renderer
void create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass);
void destroy();
private:
std::vector<char> readFile(const std::string& filename);
VkShaderModule createShaderModule(const std::vector<char>& code);
void createPipelineLayout(VkDescriptorSetLayout set);
// For rendering objects that use traditional vertex-based mesh geometry.
// See Geo::Vertex for the uniform bindings.
void createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass);
};

View File

@ -0,0 +1,46 @@
#pragma once
#include <vulkan/vulkan.h>
#include <array>
#include <vector>
class RenderTexture {
public:
// Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass.
virtual void createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) = 0;
// Create views for swapChainImages with the given format.
virtual void createViews(VkFormat format) = 0;
// Create a framebuffer for the swapChainImages, for the given pass.
virtual void createFramebuffer(VkExtent2D extent, VkRenderPass pass) = 0;
virtual VkFramebuffer getFramebuffer(int ID) = 0;
virtual void destroy() = 0;
};
class SingleRenderTexture : public RenderTexture {
public:
SingleRenderTexture();
~SingleRenderTexture();
// A list of all images involved with this texture (color, normals, etc)
std::vector<VkImage> swapChainImages;
// The sizes of all attached images.
VkExtent2D swapChainImageExtent;
// Views - mipmaps, portions, crops, etc of the attached images.
std::vector<VkImageView> swapChainImageViews;
// Framebuffers containing the images that can be bound and rendered from.
std::vector<VkFramebuffer> swapChainFramebuffers;
VkFramebuffer getFramebuffer(int ID) override { return swapChainFramebuffers.at(ID); }
// Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass.
void createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) override;
// Create views for swapChainImages with the given format.
void createViews(VkFormat format) override;
// Create a framebuffer for the swapChainImages, for the given pass.
void createFramebuffer(VkExtent2D extent, VkRenderPass pass) override;
void destroy() override;
};

View File

@ -0,0 +1,23 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
class CommandBuffer {
public:
CommandBuffer();
~CommandBuffer();
VkCommandPool commands;
std::vector<VkCommandBuffer> buffers;
void createCommandPoolAndBuffers(size_t images);
void beginCommandBuffer(VkCommandBuffer buffer);
void endCommandBuffer(VkCommandBuffer buffer);
void createCommandPool();
void allocateCommandBuffers(size_t size);
void destroy();
};

View File

@ -0,0 +1,25 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <set>
#include <algorithm>
class SwapChain {
public:
SwapChain();
~SwapChain();
VkSwapchainKHR swapChain;
VkFormat format;
VkExtent2D extent;
std::vector<VkImage> images;
VkSurfaceFormatKHR chooseFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
VkPresentModeKHR chooseMode(const std::vector<VkPresentModeKHR>& availableModes);
VkExtent2D chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities);
void create(VkSurfaceKHR surface);
void destroy();
};

View File

@ -0,0 +1,221 @@
#pragma once
#include <stdexcept>
#include <functional>
#include <vulkan/vulkan.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vulkan/vk_mem_alloc.h>
namespace VkTools {
extern VmaAllocator g_allocator;
extern VkInstance g_Instance;
extern VkPhysicalDevice g_PhysicalDevice;
extern VkDevice g_Device;
extern uint32_t g_QueueFamily;
extern VkQueue g_Queue;
extern VkDebugReportCallbackEXT g_DebugReport;
struct ManagedImage {
VkImage image;
VmaAllocation allocation;
};
struct ManagedBuffer {
VkBuffer buffer;
VmaAllocation allocation;
};
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device);
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode);
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device);
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice);
uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice);
VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical);
void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice);
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex);
#ifdef VKTOOLS_IMPLEMENTATION
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device) {
// Set up image metadata
VkImageCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.pNext = nullptr;
info.format = format;
info.extent = extent;
info.mipLevels = 1;
info.arrayLayers = 1;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = flags;
// Prepare the managed image
ManagedImage image {};
// Set up image allocation
VmaAllocationCreateInfo allocateInfo = {};
allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
// Allocate + create the image
vmaCreateImage(g_allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr);
return image;
}
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode) {
VkSamplerCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
info.pNext = nullptr;
info.magFilter = filters;
info.minFilter = filters;
info.addressModeU = mode;
info.addressModeV = mode;
info.addressModeW = mode;
VkSampler sampler;
vkCreateSampler(g_Device, &info, nullptr, &sampler);
return sampler;
}
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device) {
// Raw information about the image
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
// Information about the things we want to create - size, mip levels.
viewInfo.subresourceRange.aspectMask = flags;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
VkImageView imageView;
if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS)
throw std::runtime_error("Failed to create texture image view.");
return imageView;
}
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice) {
// Prepare for creation of a buffer
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
ManagedBuffer buffer;
VmaAllocationCreateInfo vmaInfo = {};
vmaInfo.usage = VMA_MEMORY_USAGE_AUTO;
vmaInfo.requiredFlags = properties;
vmaInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
// Create the buffer.
if (vmaCreateBuffer(g_allocator, &bufferInfo, &vmaInfo, &buffer.buffer, &buffer.allocation, nullptr) != VK_SUCCESS)
throw std::runtime_error("Unable to create GPU buffer");
return buffer;
}
uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) {
// Get the physical properties of the device.
VkPhysicalDeviceMemoryProperties physProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physProperties);
// Iterate the device and search for a suitable index
for (uint32_t i = 0; i < physProperties.memoryTypeCount; i++)
// If the type matches, and the properties are what we desire, then ship it.
if ((type & (1 << i)) && ((physProperties.memoryTypes[i].propertyFlags & properties) == properties))
return i;
throw std::runtime_error("Unable to find a suitable memory type on the physical device.");
}
VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical) {
// Prepare to allocate a command buffer
VkCommandBufferAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandPool = pool;
allocateInfo.commandBufferCount = 1;
// Allocate the buffer
VkCommandBuffer buffer;
vkAllocateCommandBuffers(logical, &allocateInfo, &buffer);
// Prepare to begin the new buffer.
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
// Begin listening on the new buffer.
vkBeginCommandBuffer(buffer, &beginInfo);
return buffer;
}
void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice) {
// Stop listening on the buffer
vkEndCommandBuffer(buffer);
// Prepare to execute the commands in the buffer
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &buffer;
// Submit the commands to be executed
vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
// Wait for the GPU to finish executing
vkQueueWaitIdle(queue);
// Delete the now unusable buffers
vkFreeCommandBuffers(logicalDevice, pool, 1, &buffer);
}
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex) {
// Prepare to create a temporary command pool.
VkCommandPool pool;
VkCommandPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolCreateInfo.queueFamilyIndex = queueIndex;
poolCreateInfo.flags = 0;
// Create the pool
if (vkCreateCommandPool(logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS)
throw std::runtime_error("Unable to allocate a temporary command pool");
// Allocate a buffer
VkCommandBuffer commands = createTempCommandBuffer(pool, logical);
// ------ Commands are saved into the commands field ------ //
// Prepare to copy the data between buffers
VkBufferCopy copyInfo = {};
copyInfo.srcOffset = 0;
copyInfo.dstOffset = 0;
copyInfo.size = length;
// Copy the data.
vkCmdCopyBuffer(commands, source, dest, 1, &copyInfo);
// ------ Commands are no longer saved into the commands field ------ //
executeAndDeleteTempBuffer(commands, pool, graphicsQueue, logical);
// Cleanup the temporary buffer and pool we created
vkDestroyCommandPool(logical, pool, nullptr);
}
#endif
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <iostream>
#include <SDL.h>
class ValidationAndExtension {
public:
ValidationAndExtension();
~ValidationAndExtension();
const std::vector<const char*> requiredValidations = {
"VK_LAYER_KHRONOS_validation"
};
VkDebugReportCallbackEXT callback;
bool checkValidationSupport();
std::vector<const char*> getRequiredExtensions(SDL_Window* window, bool validationsRequired);
void setupDebugCallback(bool validationsRequired, VkInstance vulkan);
void destroy(bool validationsRequired, VkInstance vulkan);
VkResult createDebugReportCallbackEXT(
VkInstance vulkan,
const VkDebugReportCallbackCreateInfoEXT* info,
const VkAllocationCallbacks* allocator,
VkDebugReportCallbackEXT* callback) {
auto func = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(vulkan, "vkCreateDebugReportCallbackEXT");
if (func != nullptr) {
return func(vulkan, info, allocator, callback);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void destroyDebugReportCallbackEXT(
VkInstance vulkan,
const VkDebugReportCallbackEXT callback,
const VkAllocationCallbacks* allocator) {
auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(vulkan, "vkDestroyDebugReportCallbackEXT");
if (func != nullptr) {
func(vulkan, callback, allocator);
}
}
};

View File

@ -0,0 +1,56 @@
#pragma once
#include <vulkan/vulkan.h>
#include <stdexcept>
#include <iostream>
#include <vector>
#include <set>
#include <vlkx/vulkan/ValidationAndExtension.h>
struct SwapChainMeta {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> modes;
};
struct QueueFamilies {
int graphics = -1;
int presentation = -1;
bool present() {
return graphics >= 0 && presentation >= 0;
}
};
class VulkanDevice {
public:
VulkanDevice();
~VulkanDevice();
/** Physical Devices **/
VkPhysicalDevice physical;
SwapChainMeta swapChain;
QueueFamilies queueData;
std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
void choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface);
bool isSuitable(VkPhysicalDevice device, VkSurfaceKHR surface);
bool isSupported(VkPhysicalDevice device);
SwapChainMeta checkSwapchain(VkPhysicalDevice device, VkSurfaceKHR surface);
QueueFamilies checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface);
QueueFamilies getQueues() { return queueData; }
/** Logical Devices **/
VkDevice logical;
VkQueue graphicsQueue;
VkQueue presentationQueue;
void createLogicalDevice(VkSurfaceKHR surface, bool validationRequired, ValidationAndExtension* validators);
void destroy();
};

View File

@ -0,0 +1,95 @@
#pragma once
#include <vlkx/vulkan/ValidationAndExtension.h>
#include <vlkx/vulkan/VulkanDevice.h>
#include <vlkx/vulkan/SwapChain.h>
#include <vlkx/render/framebuffer/RenderPass.h>
#include <vlkx/render/texture/RenderTexture.h>
#include <vlkx/vulkan/CommandBuffer.h>
#include <vulkan/vk_mem_alloc.h>
#include <vulkan/vulkan.h>
#include <vlkx/vulkan/Tools.h>
#include <SDL_vulkan.h>
class VulkanManager {
public:
VulkanManager();
~VulkanManager();
#ifdef _DEBUG
static const bool validationRequired = true;
#else
static const bool validationRequired = false;
#endif
// VulkanManager is a singleton class.
static VulkanManager* instance;
static VulkanManager* getInstance();
// Initialize all Vulkan context and prepare validations in debug mode.
void initVulkan(SDL_Window* window);
void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations);
// Start and end a frame render.
void startDraw();
void endDraw();
// Cleanup after the application has closed.
void cleanup();
void useRayTrace() { rayTraceMode = true; }
VkInstance getVulkan() { return vulkan; }
VulkanDevice* getDevice() { return device; }
SwapChain* getSwapchain() { return swapchain; }
RenderPass* getRenderPass() { return renderPass; }
VkCommandBuffer getCurrentCommandBuffer() { return currentCommandBuffer; }
VmaAllocator getAllocator() { return allocator; }
RenderTexture* getRenderTarget() { return renderTexture; }
private:
// To keep track of the window during... stuff
SDL_Window* wnd;
// To handle the validation of Vulkan API usage (because fuck you, it's your problem now)
ValidationAndExtension* validators{};
// To manage interaction with the hardware
VulkanDevice* device{};
// To handle the framebuffers
SwapChain* swapchain{};
// To handle the timing of rendering
RenderPass* renderPass{};
// To handle automatic management of memory.
VmaAllocator allocator{};
// The default RenderTexture, mirroring the SwapChain to the viewport.
RenderTexture* renderTexture;
// The command buffers used when telling the firmware to do things for us.
CommandBuffer* buffers{};
// To manage the Vulkan context that was passed to us by the API
VkInstance vulkan{};
// To manage the canvas that was given to us by GLFW
VkSurfaceKHR surface{};
// The index of the texture that is currently being used by the GPU.
uint32_t imageIndex = 0;
// The command buffer currently being used by the GPU.
VkCommandBuffer currentCommandBuffer{};
// The maximum number of frames that can be dealt with at a time.
const int MAX_FRAMES = 2; // Double-buffering requires two frames in memory
// Raised when a new image is available
VkSemaphore newImageSem{};
// Raised when a render is finished
VkSemaphore renderDoneSem{};
// Stores fences for frames that are currently "in flight".
std::vector<VkFence> inFlight;
bool rayTraceMode;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
#include <vlkx/render/Camera.h>
void Camera::init(float fov, float width, float height, float near, float far) {
// Initialise members
position = glm::vec3(0, 0, 4);
glm::vec3 cameraFront = glm::vec3(0, 0, 0);
glm::vec3 cameraUp = glm::vec3(0, 1, 0);
viewMatrix = glm::mat4(1);
projectionMatrix = glm::mat4(1);
projectionMatrix = glm::perspective(fov, width / height, near, far);
viewMatrix = glm::lookAt(position, cameraFront, cameraUp);
}
void Camera::setPosition(glm::vec3 newPosition) {
position = newPosition;
}

View File

@ -0,0 +1,156 @@
#include <vlkx/render/Geometry.h>
using namespace Geo;
void Mesh::setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices = {
{ { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } },
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } },
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
};
std::vector<uint32_t> Indices = {
0, 1, 2,
};
vertices.clear(); indices.clear();
vertices = Vertices;
indices = Indices;
}
void Mesh::setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices = {
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } },
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } },
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
{ { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
};
std::vector<uint32_t> Indices = {
0, 1, 2,
0, 2, 3
};
vertices.clear(); indices.clear();
vertices = Vertices;
indices = Indices;
}
void Mesh::setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices = {
// Front
{ { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 0
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 1
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 2
{ { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3
// Back
{ { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 4
{ { 1.0f, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0f, 1.0f, 1.0 },{ 0.0, 0.0 } }, // 5
{ { -1.0, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0f, 1.0f, 1.0 },{ 1.0, 0.0 } }, // 6
{ { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 7
// Left
{ { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 8
{ { -1.0f, 1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 9
{ { -1.0, 1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 10
{ { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 11
// Right
{ { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 12
{ { 1.0f, 1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 13
{ { 1.0, 1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 14
{ { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 15
// Top
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 16
{ { -1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 17
{ { 1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 18
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 19
// Bottom
{ { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 20
{ { -1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 21
{ { 1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 22
{ { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 23
};
std::vector<uint32_t> Indices = {
0, 1, 2,
2, 3, 0,
4, 5, 6,
4, 6, 7,
8, 9, 10,
8, 10, 11,
12, 13, 14,
12, 14, 15,
16, 17, 18,
16, 18, 19,
20, 21, 22,
20, 22, 23
};
vertices.clear(); indices.clear();
vertices = Vertices;
indices = Indices;
}
void Mesh::setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices;
std::vector<uint32_t> Indices;
float latitudeBands = 20.0f;
float longitudeBands = 20.0f;
float radius = 1.0f;
for (float latNumber = 0; latNumber <= latitudeBands; latNumber++) {
float theta = latNumber * 3.14 / latitudeBands;
float sinTheta = sin(theta);
float cosTheta = cos(theta);
for (float longNumber = 0; longNumber <= longitudeBands; longNumber++) {
float phi = longNumber * 2 * 3.147 / longitudeBands;
float sinPhi = sin(phi);
float cosPhi = cos(phi);
Vertex vs;
vs.texture.x = (longNumber / longitudeBands); // u
vs.texture.y = (latNumber / latitudeBands); // v
vs.normal.x = cosPhi * sinTheta; // normal x
vs.normal.y = cosTheta; // normal y
vs.normal.z = sinPhi * sinTheta; // normal z
vs.position.x = radius * vs.normal.x; // x
vs.position.y = radius * vs.normal.y; // y
vs.position.z = radius * vs.normal.z; // z
Vertices.push_back(vs);
}
}
for (uint32_t latNumber = 0; latNumber < latitudeBands; latNumber++) {
for (uint32_t longNumber = 0; longNumber < longitudeBands; longNumber++) {
uint32_t first = (latNumber * (longitudeBands + 1)) + longNumber;
uint32_t second = first + longitudeBands + 1;
Indices.push_back(first);
Indices.push_back(second);
Indices.push_back(first + 1);
Indices.push_back(second);
Indices.push_back(second + 1);
Indices.push_back(first + 1);
}
}
vertices.clear(); indices.clear();
vertices = Vertices;
indices = Indices;
}

View File

@ -0,0 +1,68 @@
#include <vlkx/render/framebuffer/RenderPass.h>
#include <vlkx/vulkan/VulkanManager.h>
RenderPass::RenderPass() {}
RenderPass::~RenderPass() {}
void RenderPass::createVertexRenderPass(VkFormat format) {
// Set up color metadata
VkAttachmentDescription color = {};
color.format = format;
color.samples = VK_SAMPLE_COUNT_1_BIT;
color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Set up subattachments
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference;
// Prepare the Render Pass for creation
std::array<VkAttachmentDescription, 1> attachments = { color };
VkRenderPassCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
createInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
createInfo.pAttachments = attachments.data();
createInfo.subpassCount = 1;
createInfo.pSubpasses = &subpass;
// Create the Render Pass
if (vkCreateRenderPass(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass))
throw std::runtime_error("Unable to create Render Pass 1");
}
void RenderPass::beginRenderPass(std::vector<VkClearValue> clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent) {
// Prepare the Render Pass for start
VkRenderPassBeginInfo info = {};
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
info.renderPass = pass;
info.framebuffer = framebuffer;
info.renderArea.offset = { 0, 0 };
info.renderArea.extent = extent;
info.pClearValues = clearValues.data();
info.clearValueCount = static_cast<uint32_t>(clearValues.size());
// Attempt to begin the pass
vkCmdBeginRenderPass(commands, &info, VK_SUBPASS_CONTENTS_INLINE);
}
void RenderPass::endRenderPass(VkCommandBuffer commands) {
vkCmdEndRenderPass(commands);
}
void RenderPass::destroy() {
vkDestroyRenderPass(VulkanManager::getInstance()->getDevice()->logical, pass, nullptr);
}

View File

@ -0,0 +1,85 @@
#include <vlkx/render/geometry/SingleRenderer.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <glm/gtc/quaternion.hpp>
void SingleRenderer::createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn) {
// Collect metadata about the swap chain
uint32_t imageCount = VulkanManager::getInstance()->getSwapchain()->images.size();
VkExtent2D imageExtent = VulkanManager::getInstance()->getSwapchain()->extent;
// Create the buffers
buffers.createBuffers(type);
// Create the descriptor layout
descriptor.createAndAllocateSimple(imageCount);
descriptor.populate(imageCount, buffers.uniformBuffer.buffer, sizeof(Geo::UniformBufferObject));
// Create the pipeline
pipeline.create(imageExtent, descriptor.layout, VulkanManager::getInstance()->getRenderPass()->pass);
// Set locals
position = posIn;
scale = scaleIn;
rotation = glm::mat4_cast(glm::quat(0, 0, 0, 0));
}
void SingleRenderer::updateUniforms(Camera camera) {
// Prepare data to go into the buffers
Geo::UniformBufferObject ubo = {};
glm::mat4 scaleMatrix = glm::mat4(1.0f);
glm::mat4 rotationMatrix = getRotation();
glm::mat4 translationMatrix = glm::mat4(1.0f);
scaleMatrix = glm::scale(scaleMatrix, scale);
translationMatrix = glm::translate(translationMatrix, position);
ubo.model = translationMatrix * rotationMatrix * scaleMatrix;
ubo.view = camera.getViewMatrix();
ubo.proj = camera.getProjectionMatrix();
ubo.proj[1][1] *= -1;
// Copy the buffers into the GPU
void* data;
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation, &data);
memcpy(data, &ubo, sizeof(ubo));
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation);
}
void SingleRenderer::draw() {
// Fetch the buffer we're going to insert commands to
VkCommandBuffer commands = VulkanManager::getInstance()->getCurrentCommandBuffer();
// Bind our pipeline
vkCmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
// Bind the vertex buffer
VkBuffer vertexBuffers[] = {
buffers.vertexBuffer.buffer
};
VkDeviceSize offsets[] = {
0
};
vkCmdBindVertexBuffers(commands, 0, 1, vertexBuffers, offsets);
// Bind the index buffer
vkCmdBindIndexBuffer(commands, buffers.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
// Bind the uniform buffer
vkCmdBindDescriptorSets(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, &descriptor.set, 0, nullptr);
// Draw the buffered geometry
vkCmdDrawIndexed(commands, static_cast<uint32_t>(buffers.indices.size()), 1, 0, 0, 0);
}
void SingleRenderer::destroy() {
pipeline.destroy();
descriptor.destroy();
buffers.destroy();
}

View File

@ -0,0 +1,137 @@
#include <vlkx/render/shader/Descriptor.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/render/Geometry.h>
#include <array>
Descriptor::Descriptor() {}
Descriptor::~Descriptor() {}
void Descriptor::createAndAllocateSimple(uint32_t images) {
createSimpleLayout();
createSimplePool(images);
}
void Descriptor::createSimpleLayout() {
// Set up a binding
VkDescriptorSetLayoutBinding binding = {};
binding.binding = 0;
binding.descriptorCount = 1;
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
// Prepare to create the layout
std::array<VkDescriptorSetLayoutBinding, 1> bindings = {
binding
};
VkDescriptorSetLayoutCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.bindingCount = static_cast<uint32_t>(bindings.size());
info.pBindings = bindings.data();
// Create the layout
if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
throw std::runtime_error("Unable to create Descriptor layout");
}
void Descriptor::createSimplePool(uint32_t images) {
// Set the size of the pool we want
VkDescriptorPoolSize size = {};
size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
size.descriptorCount = images;
std::array<VkDescriptorPoolSize, 1> sizes = {
size
};
// Prepare to create the pool
VkDescriptorPoolCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
createInfo.poolSizeCount = static_cast<uint32_t>(sizes.size());
createInfo.pPoolSizes = sizes.data();
createInfo.maxSets = images;
// Create the pool
if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS)
throw std::runtime_error("Unable to create Descriptor pool");
// Prepare to allocate the set
std::vector<VkDescriptorSetLayout> layouts(images, layout);
VkDescriptorSetAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocateInfo.descriptorPool = pool;
allocateInfo.descriptorSetCount = images;
allocateInfo.pSetLayouts = layouts.data();
// Allocate the Set
if (vkAllocateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, &allocateInfo, &set) != VK_SUCCESS)
throw std::runtime_error("Unable to allocate Descriptor set");
}
void Descriptor::populate(uint32_t images, VkBuffer uniforms, size_t bufferSize) {
// Iterate images
for (size_t i = 0; i < images; i++) {
// Set up the uniform buffer
VkDescriptorBufferInfo buffer = {};
buffer.buffer = uniforms;
buffer.offset = 0;
buffer.range = bufferSize;
// Prepare to write the buffer into the Descriptor set
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.pNext = nullptr;
write.dstSet = set;
write.dstBinding = 0;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
write.descriptorCount = 1;
write.pBufferInfo = &buffer;
write.pImageInfo = nullptr;
write.pTexelBufferView = nullptr;
std::array<VkWriteDescriptorSet, 1> writes = {
write
};
// Write the buffer into the descriptor
vkUpdateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
}
void Descriptor::destroy() {
vkDestroyDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, pool, nullptr);
vkDestroyDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr);
}
VkDescriptorSetLayout createBindingWrapper(std::vector<VkDescriptorSetLayoutBinding> bindings) {
VkDescriptorSetLayoutCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.bindingCount = static_cast<uint32_t>(bindings.size());
info.pBindings = bindings.data();
VkDescriptorSetLayout layout;
// Create the layout
if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
throw std::runtime_error("Unable to create Descriptor layout");
return layout;
}
VkDescriptorPool createPoolWrapper(std::vector<VkDescriptorPoolSize> sizes, uint32_t images) {
// Prepare to create the pool
VkDescriptorPoolCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
createInfo.poolSizeCount = static_cast<uint32_t>(sizes.size());
createInfo.pPoolSizes = sizes.data();
createInfo.maxSets = images;
VkDescriptorPool pool;
// Create the pool
if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS)
throw std::runtime_error("Unable to create Descriptor pool");
return pool;
}

View File

@ -0,0 +1,101 @@
#include <vlkx/render/shader/GeoBuffers.h>
#include <vlkx/vulkan/Tools.h>
#include <vlkx/vulkan/VulkanManager.h>
GeoBuffers::GeoBuffers() {}
GeoBuffers::~GeoBuffers() {}
void GeoBuffers::createBuffers(Geo::MeshType type) {
// Set vertex and indice data
switch (type) {
case Geo::MeshType::Triangle:
Geo::Mesh::setTriData(vertices, indices);
break;
case Geo::MeshType::Quad:
Geo::Mesh::setQuadData(vertices, indices);
break;
case Geo::MeshType::Cube:
Geo::Mesh::setCubeData(vertices, indices);
break;
case Geo::MeshType::Sphere:
Geo::Mesh::setSphereData(vertices, indices);
break;
}
// Create buffers
createVertexBuffer();
createIndexBuffer();
createUniformBuffer();
}
void GeoBuffers::createVertexBuffer() {
// Gather the size of the buffer we need..
VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
// Create and bind a temporary buffer
VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
// Get ready to write into our GPU buffer
void* data;
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data);
// Copy vertex data into the buffer
memcpy(data, vertices.data(), (size_t)bufferSize);
// Unmap the buffer from stack space
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation);
// Prepare an area in the GPU that we cannot see, that the vertex data can live permanently.
vertexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
// Copy our data from the staging buffer into the new permanent buffer
VkTools::copyGPUBuffer(staging.buffer, vertexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics);
// Cleanup the staging area.
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation);
}
void GeoBuffers::createIndexBuffer() {
// Gather the size of the buffer we need..
VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();
// Create and bind a temporary buffer
VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
// Get ready to write into our GPU buffer
void* data;
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data);
// Copy index data into the buffer
memcpy(data, indices.data(), (size_t)bufferSize);
// Unmap the buffer from stack space
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation);
// Prepare an area in the GPU that we cannot see, that the vertex data can live permanently.
indexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
// Copy our data from the staging buffer into the new permanent buffer
VkTools::copyGPUBuffer(staging.buffer, indexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics);
// Cleanup the staging area.
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation);
}
void GeoBuffers::createUniformBuffer() {
// Allocate a buffer of the right size - we don't need to copy uniforms.
VkDeviceSize bufferSize = sizeof(Geo::UniformBufferObject);
uniformBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
}
void GeoBuffers::destroy() {
// Cleanup uniform buffers
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), uniformBuffer.buffer, uniformBuffer.allocation);
// Cleanup index buffers
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), indexBuffer.buffer, indexBuffer.allocation);
// Cleanup vertex buffers
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), vertexBuffer.buffer, vertexBuffer.allocation);
}

View File

@ -0,0 +1,200 @@
#include <vlkx/render/shader/Pipeline.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/render/Geometry.h>
#include <string>
#include <map>
Pipeline::Pipeline() {}
Pipeline::~Pipeline() {}
void Pipeline::create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass) {
createPipelineLayout(set);
createVertexPipeline(extent, renderPass);
}
void Pipeline::createPipelineLayout(VkDescriptorSetLayout set) {
// Prepare to create the pipeline layout
VkPipelineLayoutCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
info.setLayoutCount = 1;
info.pSetLayouts = &set;
// Create the layout
if (vkCreatePipelineLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
throw std::runtime_error("Unable to create Pipeline Layout.");
}
std::vector<char> Pipeline::readFile(const std::string& filename) {
// Prepare the stream for reading the file
std::ifstream file(filename, std::ios::ate | std::ios::binary);
if (!file.is_open())
throw std::runtime_error(std::string("Unable to open shader file: ").append(filename));
size_t fileSize = (size_t)file.tellg();
// Read the file into a vector
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
VkShaderModule Pipeline::createShaderModule(const std::vector<char>& code) {
// Prepare to create the shader module
VkShaderModuleCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
info.codeSize = code.size();
info.pCode = reinterpret_cast<const uint32_t*>(code.data());
// Create the module
VkShaderModule shaderModule;
if (vkCreateShaderModule(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &shaderModule) != VK_SUCCESS)
throw std::runtime_error("Unable to create Shader module from SPIR-V binary");
return shaderModule;
}
void Pipeline::createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass) {
// ------- Stage: Shader ------- //
// Read the vertex shader
auto vertexShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.vert.spv");
VkShaderModule vertexShaderModule = createShaderModule(vertexShaderCode);
// Prepare to create the Shader Stage
VkPipelineShaderStageCreateInfo vertexShaderCreateInfo = {};
vertexShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertexShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertexShaderCreateInfo.module = vertexShaderModule;
vertexShaderCreateInfo.pName = "main";
// Read the fragment shader
auto fragmentShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.frag.spv");
VkShaderModule fragmentShaderModule = createShaderModule(fragmentShaderCode);
// Prepare to create the Shader Stage
VkPipelineShaderStageCreateInfo fragmentShaderCreateInfo = {};
fragmentShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragmentShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragmentShaderCreateInfo.module = fragmentShaderModule;
fragmentShaderCreateInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {
vertexShaderCreateInfo,
fragmentShaderCreateInfo
};
// ------- Stage: Shader Input ------- //
auto bindingDescription = Geo::Vertex::getBindingDesc();
auto attributeDescription = Geo::Vertex::getAttributeDesc();
VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {};
vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputCreateInfo.vertexBindingDescriptionCount = 1;
vertexInputCreateInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputCreateInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescription.size());
vertexInputCreateInfo.pVertexAttributeDescriptions = attributeDescription.data();
// ------- Stage: Input Assembly ------- //
VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {};
inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE;
// ------- Stage: Rasterization ------- //
VkPipelineRasterizationStateCreateInfo rasterCreateInfo = {};
rasterCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterCreateInfo.depthClampEnable = VK_FALSE;
rasterCreateInfo.rasterizerDiscardEnable = VK_FALSE;
rasterCreateInfo.polygonMode = VK_POLYGON_MODE_FILL;
rasterCreateInfo.lineWidth = 1.0f;
rasterCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT;
rasterCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterCreateInfo.depthBiasEnable = VK_FALSE;
rasterCreateInfo.depthBiasConstantFactor = 0.0f;
rasterCreateInfo.depthBiasClamp = 0.0f;
rasterCreateInfo.depthBiasSlopeFactor = 0.0f;
// ------- Stage: MultiSample ------- //
VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {};
multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampleCreateInfo.sampleShadingEnable = VK_FALSE;
multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// ------- Stage: Color Blending ------- //
VkPipelineColorBlendAttachmentState colorBlendAttach = {};
colorBlendAttach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttach.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {};
colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlendCreateInfo.attachmentCount = 1;
colorBlendCreateInfo.pAttachments = &colorBlendAttach;
// ------- Stage: Viewport ------- //
VkViewport viewport = {};
viewport.x = 0;
viewport.y = 0;
viewport.width = (float)extent.width;
viewport.height = (float)extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = extent;
VkPipelineViewportStateCreateInfo viewportCreateInfo = {};
viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportCreateInfo.viewportCount = 1;
viewportCreateInfo.pViewports = &viewport;
viewportCreateInfo.scissorCount = 1;
viewportCreateInfo.pScissors = &scissor;
// ------- Create the Pipeline ------- //
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputCreateInfo;
pipelineInfo.pInputAssemblyState = &inputAssemblyCreateInfo;
pipelineInfo.pRasterizationState = &rasterCreateInfo;
pipelineInfo.pMultisampleState = &multisampleCreateInfo;
pipelineInfo.pDepthStencilState = nullptr;
pipelineInfo.pColorBlendState = &colorBlendCreateInfo;
pipelineInfo.pDynamicState = nullptr;
pipelineInfo.pViewportState = &viewportCreateInfo;
pipelineInfo.layout = layout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
if (vkCreateGraphicsPipelines(VulkanManager::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) != VK_SUCCESS)
throw std::runtime_error("Unable to create a Vertex rendering Pipeline.");
// ------- Cleanup temporary allocations ------- //
vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, vertexShaderModule, nullptr);
vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, fragmentShaderModule, nullptr);
}
void Pipeline::destroy() {
vkDestroyPipeline(VulkanManager::getInstance()->getDevice()->logical, pipeline, nullptr);
vkDestroyPipelineLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr);
}

View File

@ -0,0 +1,66 @@
#include <vlkx/render/texture/RenderTexture.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/vulkan/Tools.h>
SingleRenderTexture::SingleRenderTexture() {}
SingleRenderTexture::~SingleRenderTexture() {}
void SingleRenderTexture::createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) {
// Initialize members
this->swapChainImages = images;
this->swapChainImageExtent = extent;
createViews(format);
createFramebuffer(extent, pass);
}
void SingleRenderTexture::createViews(VkFormat format) {
// Prepare maximum size
swapChainImageViews.resize(swapChainImages.size());
// Iterate images and create views for each.
for (size_t i = 0; i < swapChainImages.size(); i++) {
swapChainImageViews[i] = VkTools::createImageView(swapChainImages[i], format, VK_IMAGE_ASPECT_COLOR_BIT, VulkanManager::getInstance()->getDevice()->logical);
}
}
void SingleRenderTexture::createFramebuffer(VkExtent2D extent, VkRenderPass pass) {
// Prepare maximum size
swapChainFramebuffers.resize(swapChainImageViews.size());
// Iterate views and create a framebuffer for each.
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
// Create an array with the image view as an attachment.
std::array<VkImageView, 1> attachments = {
swapChainImageViews[i]
};
// Prepare the creation of a new framebuffer.
// One attachment, width and height from the extent, and one layer (one viewport)
VkFramebufferCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
info.renderPass = pass;
info.attachmentCount = static_cast<uint32_t>(attachments.size());
info.pAttachments = attachments.data();
info.width = extent.width;
info.height = extent.height;
info.layers = 1;
// Create the framebuffer
if (vkCreateFramebuffer(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS)
throw std::runtime_error("Unable to create framebuffer for a texture.");
}
}
void SingleRenderTexture::destroy() {
// Destroy Image Views first
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(VulkanManager::getInstance()->getDevice()->logical, imageView, nullptr);
}
// Framebuffers
for (auto framebuffer : swapChainFramebuffers) {
vkDestroyFramebuffer(VulkanManager::getInstance()->getDevice()->logical, framebuffer, nullptr);
}
}

View File

@ -0,0 +1,60 @@
#include <vlkx/vulkan/CommandBuffer.h>
#include <vlkx/vulkan/VulkanManager.h>
CommandBuffer::CommandBuffer() {}
CommandBuffer::~CommandBuffer() {}
void CommandBuffer::createCommandPoolAndBuffers(size_t size) {
createCommandPool();
allocateCommandBuffers(size);
}
void CommandBuffer::createCommandPool() {
// Prepare queues
QueueFamilies families = VulkanManager::getInstance()->getDevice()->getQueues();
// Prepare command pool creation data
VkCommandPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
info.queueFamilyIndex = families.graphics;
info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
// Attempt creation
if (vkCreateCommandPool(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &commands) != VK_SUCCESS)
throw std::runtime_error("Unable to create a command pool.");
}
void CommandBuffer::allocateCommandBuffers(size_t size) {
// Prepare allocation info
buffers.resize(size);
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = commands;
info.commandBufferCount = (uint32_t)buffers.size();
// Attempt allocation
if (vkAllocateCommandBuffers(VulkanManager::getInstance()->getDevice()->logical, &info, buffers.data()) != VK_SUCCESS)
throw std::runtime_error("Unable to allocate command buffer");
}
void CommandBuffer::beginCommandBuffer(VkCommandBuffer buffer) {
// Prepare to begin listening on this buffer
VkCommandBufferBeginInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; // Allow writing to this buffer while another is being read.
// Attempt to begin listening
if (vkBeginCommandBuffer(buffer, &info) != VK_SUCCESS)
throw std::runtime_error("Unable to begin a command buffer");
}
void CommandBuffer::endCommandBuffer(VkCommandBuffer buffer) {
// Attempt to end listening
if (vkEndCommandBuffer(buffer) != VK_SUCCESS)
throw std::runtime_error("Unable to end listening for a command buffer");
}
void CommandBuffer::destroy() {
vkDestroyCommandPool(VulkanManager::getInstance()->getDevice()->logical, commands, nullptr);
}

View File

@ -0,0 +1,103 @@
#include <vlkx/vulkan/SwapChain.h>
#include <vlkx/vulkan/VulkanManager.h>
SwapChain::SwapChain() {}
SwapChain::~SwapChain() {}
VkSurfaceFormatKHR SwapChain::chooseFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
// Check if we don't have any options
if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED)
// Default to BGRA, sRGB
return { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
for (const auto& format : availableFormats) {
// Prefer BGRA sRGB if it's available
if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
return format;
}
// If BGRA sRGB isn't an option, choose whatever Vulkan thinks is the best option.
return availableFormats[0];
}
VkPresentModeKHR SwapChain::chooseMode(const std::vector<VkPresentModeKHR>& availableModes) {
// We want Mailbox, Immediate or FIFO, in that order.
VkPresentModeKHR mode = availableModes[0];
if (mode == VK_PRESENT_MODE_MAILBOX_KHR || mode == VK_PRESENT_MODE_IMMEDIATE_KHR)
return mode;
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
// If our extent is valid, use it
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max())
return capabilities.currentExtent;
else {
// Create a new 1280x720 extent and use that
VkExtent2D size = { 1280, 720 };
size.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, size.width));
size.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, size.height));
return size;
}
}
void SwapChain::create(VkSurfaceKHR surface) {
SwapChainMeta info = VulkanManager::getInstance()->getDevice()->swapChain;
VkSurfaceFormatKHR chosenFormat = chooseFormat(info.formats);
VkPresentModeKHR chosenMode = chooseMode(info.modes);
VkExtent2D chosenExtent = chooseExtent(info.capabilities);
// use the max if it's set, otherwise the minimum
uint32_t imageCount = std::max(info.capabilities.minImageCount, (uint32_t) 2);
std::cout << "SwapChain has " << imageCount << " images." << std::endl;
// Prepare the creation data
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = chosenFormat.format;
createInfo.imageColorSpace = chosenFormat.colorSpace;
createInfo.imageExtent = chosenExtent;
createInfo.imageArrayLayers = 1; // 2 for VR
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
QueueFamilies queues = VulkanManager::getInstance()->getDevice()->getQueues();
uint32_t indices[] = { static_cast<uint32_t>(queues.graphics), static_cast<uint32_t>(queues.presentation) };
if (queues.graphics != queues.presentation) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = indices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}
createInfo.preTransform = info.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = chosenMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
// Create the swap-chain
if (vkCreateSwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &swapChain))
throw std::runtime_error("Failed to create swap-chain");
// Fetch our images from the swapchain
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, nullptr);
images.resize(imageCount);
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, images.data());
// Set gloabls
format = chosenFormat.format;
extent = chosenExtent;
}
void SwapChain::destroy() {
vkDestroySwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, nullptr);
}

View File

@ -0,0 +1,90 @@
#include <vlkx/vulkan/ValidationAndExtension.h>
#include <SDL.h>
#include <SDL_vulkan.h>
ValidationAndExtension::ValidationAndExtension() {}
ValidationAndExtension::~ValidationAndExtension() {}
bool ValidationAndExtension::checkValidationSupport() {
// Get number of properties
uint32_t layers;
vkEnumerateInstanceLayerProperties(&layers, nullptr);
// Instantiate a vector of proper size and retrieve data
std::vector<VkLayerProperties> availableProperties(layers);
vkEnumerateInstanceLayerProperties(&layers, availableProperties.data());
// Iterate the validation layers we require
for (const char* layer : requiredValidations) {
bool layerFound = false;
// Search for a match with the ones we have
for (const auto& property : availableProperties) {
if (strcmp(layer, property.layerName) == 0) {
layerFound = true;
break;
}
}
// If any are not found, then we don't support what we need
if (!layerFound)
return false;
}
// If all are found, then we can continue
return true;
}
std::vector<const char*> ValidationAndExtension::getRequiredExtensions(SDL_Window* window, bool validationsRequired) {
unsigned int count;
SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr);
std::vector<const char*> extensions = {
};
size_t additional_extension_count = extensions.size();
extensions.resize(additional_extension_count + count);
SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data() + additional_extension_count);
if (validationsRequired) {
extensions.push_back("VK_EXT_debug_report"); // Add debug report if we want to validate
}
// Return the new list
return extensions;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objExt,
size_t obj,
size_t location,
int32_t code,
const char* layer,
const char* message,
void* user) {
std::cerr << "Validation from layer " << layer << ": " << message << std::endl;
return false;
}
void ValidationAndExtension::setupDebugCallback(bool validationRequired, VkInstance vulkan) {
if (!validationRequired)
return;
VkDebugReportCallbackCreateInfoEXT info = {};
info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
info.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
info.pfnCallback = debugCallback;
if (createDebugReportCallbackEXT(vulkan, &info, nullptr, &callback) != VK_SUCCESS) {
throw std::runtime_error("Failed to create log dumper.");
}
}
void ValidationAndExtension::destroy(bool validationRequired, VkInstance vulkan) {
if (validationRequired)
destroyDebugReportCallbackEXT(vulkan, callback, nullptr);
}

View File

@ -0,0 +1,191 @@
#include <vlkx/vulkan/VulkanDevice.h>
VulkanDevice::VulkanDevice() : physical(VK_NULL_HANDLE), logical(VK_NULL_HANDLE), graphicsQueue(VK_NULL_HANDLE), presentationQueue(VK_NULL_HANDLE) {}
VulkanDevice::~VulkanDevice() = default;
void VulkanDevice::choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface) {
// Count devices
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(*vulkan, &deviceCount, nullptr);
// Sanity check
if (deviceCount == 0) {
throw std::runtime_error("Vulkan not supported on this system. No Devices available");
}
std::cout << "Found " << deviceCount << " devices that are Vulkan-compatible." << std::endl;
// Gather devices
std::vector<VkPhysicalDevice> physicals(deviceCount);
vkEnumeratePhysicalDevices(*vulkan, &deviceCount, physicals.data());
// Enumerate devices
std::string finalDeviceName;
std::cout << std::endl;
std::cout << "Device List" << std::endl;
for (const auto& device : physicals) {
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(device, &props);
std::cout << "Device: " << props.deviceName << std::endl;
if (physical == VK_NULL_HANDLE && isSuitable(device, surface) ) {
finalDeviceName = props.deviceName;
physical = device;
}
}
std::cout << std::endl << "Using device " << finalDeviceName << "." << std::endl;
// Sanity check that at least one was found.
if (physical == VK_NULL_HANDLE)
throw std::runtime_error("No suitable GPU found");
}
bool VulkanDevice::isSuitable(VkPhysicalDevice device, VkSurfaceKHR surface) {
// Find queues
QueueFamilies families = checkQueues(device, surface);
bool supported = isSupported(device);
bool swapChainWorks = false;
if (supported) {
swapChain = checkSwapchain(device, surface);
swapChainWorks = !swapChain.formats.empty() && !swapChain.modes.empty();
}
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
return families.present() && supported && swapChainWorks && supportedFeatures.samplerAnisotropy;
}
QueueFamilies VulkanDevice::checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface) {
QueueFamilies families;
// Enumerate queueues
uint32_t queueCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, nullptr);
std::vector<VkQueueFamilyProperties> queues(queueCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, queues.data());
// Find a valid graphics (drawing) and presentation (display) queue
int i = 0;
for (const auto& family : queues) {
// If the graphics bit is set, this is a valid graphics queue
if (family.queueCount > 0 && family.queueFlags & VK_QUEUE_GRAPHICS_BIT)
families.graphics = i;
// Ask Vulkan if this family suppots displaying to the surface from this device
VkBool32 presentationSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentationSupport);
if (family.queueCount > 0 && presentationSupport)
families.presentation = i;
// If we have a valid graphics and presentation queue, we can stop searching
if (families.present())
break;
i++;
}
return families;
}
bool VulkanDevice::isSupported(VkPhysicalDevice device) {
// Enumerate extensions
uint32_t extensionCount = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data());
// Filter for the ones we have
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : extensions) {
// Remove the ones we have from the ones we need
requiredExtensions.erase(extension.extensionName);
}
// If we have every needed extension, then we're good to go
return requiredExtensions.empty();
}
SwapChainMeta VulkanDevice::checkSwapchain(VkPhysicalDevice device, VkSurfaceKHR surface) {
SwapChainMeta meta;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &meta.capabilities);
// Check display formats (ARGB, RBGA, etc)
uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
meta.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, meta.formats.data());
}
// Check Presentation modes (bit depth, etc)
uint32_t modeCount = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &modeCount, nullptr);
if (modeCount != 0) {
meta.modes.resize(modeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &modeCount, meta.modes.data());
}
return meta;
}
void VulkanDevice::createLogicalDevice(VkSurfaceKHR surface, bool validationRequired, ValidationAndExtension* validator) {
// Get the queue data, prepare it for the logical device
QueueFamilies families = checkQueues(physical, surface);
std::vector<VkDeviceQueueCreateInfo> queueCreation;
std::set<int> queues = { families.graphics, families.presentation };
float priority = 1;
for (int family : queues) {
VkDeviceQueueCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
createInfo.queueFamilyIndex = family;
createInfo.queueCount = 1;
createInfo.pQueuePriorities = &priority;
queueCreation.push_back(createInfo);
}
// Prepare the characteristics of the device we want
VkPhysicalDeviceFeatures features = {};
features.samplerAnisotropy = VK_TRUE;
// Prepare the device for construction
VkDeviceCreateInfo device = {};
device.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device.pQueueCreateInfos = queueCreation.data();
device.queueCreateInfoCount = static_cast<uint32_t>(queueCreation.size());
device.pEnabledFeatures = &features;
device.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
device.ppEnabledExtensionNames = deviceExtensions.data();
if (validationRequired) {
device.enabledLayerCount = static_cast<uint32_t>(validator->requiredValidations.size());
device.ppEnabledLayerNames = validator->requiredValidations.data();
} else {
device.enabledLayerCount = 0;
}
// Create the device
if (vkCreateDevice(physical, &device, nullptr, &logical) != VK_SUCCESS) {
throw std::runtime_error("Failed to create logical device");
}
// Fetch the proper queue metadata from the GPU
queueData = families;
vkGetDeviceQueue(logical, families.graphics, 0, &graphicsQueue);
vkGetDeviceQueue(logical, families.presentation, 0, &presentationQueue);
}
void VulkanDevice::destroy() {
vkDestroyDevice(logical, nullptr);
}

View File

@ -0,0 +1,227 @@
#define VMA_IMPLEMENTATION
#include <vulkan/vk_mem_alloc.h>
#define VKTOOLS_IMPLEMENTATION
#include <vlkx/vulkan/Tools.h>
#include <vlkx\vulkan\VulkanManager.h>
VulkanManager::VulkanManager() { rayTraceMode = false; }
VulkanManager::~VulkanManager() = default;
VulkanManager* VulkanManager::instance = nullptr;
VmaAllocator VkTools::g_allocator;
VkInstance VkTools::g_Instance = VK_NULL_HANDLE;
VkPhysicalDevice VkTools::g_PhysicalDevice = VK_NULL_HANDLE;
VkDevice VkTools::g_Device = VK_NULL_HANDLE;
uint32_t VkTools::g_QueueFamily = (uint32_t)-1;
VkQueue VkTools::g_Queue = VK_NULL_HANDLE;
VkDebugReportCallbackEXT VkTools::g_DebugReport = VK_NULL_HANDLE;
VulkanManager* VulkanManager::getInstance() {
return VulkanManager::instance != nullptr ? VulkanManager::instance
: (VulkanManager::instance = new VulkanManager());
}
void VulkanManager::createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations) {
VkApplicationInfo info = {};
info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
info.pApplicationName = "Sup";
info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
info.pEngineName = "Infinity Drive";
info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
info.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instanceInfo = {};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.pApplicationInfo = &info;
auto extensions = validations->getRequiredExtensions(wnd, true);
instanceInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
instanceInfo.ppEnabledExtensionNames = &extensions[0];
auto layers = validations->requiredValidations;
if (enableValidation) {
instanceInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());
instanceInfo.ppEnabledLayerNames = &layers[0];
} else {
instanceInfo.enabledLayerCount = 0;
}
auto status = vkCreateInstance(&instanceInfo, nullptr, &vulkan);
if (status != VK_SUCCESS) {
throw std::runtime_error("Failed to initialize Vulkan: " + std::to_string(status));
}
}
void VulkanManager::initVulkan(SDL_Window* window) {
wnd = window;
validators = new ValidationAndExtension();
if (!validators->checkValidationSupport())
throw std::runtime_error("Validation not available");
createAppAndVulkanInstance(validationRequired, validators);
validators->setupDebugCallback(validationRequired, vulkan);
if (SDL_Vulkan_CreateSurface(window, vulkan, &surface) != SDL_TRUE)
throw std::runtime_error("Unable to create Vulkan Surface");
this->device = new VulkanDevice();
this->device->choosePhysicalDevice(&vulkan, surface);
this->device->createLogicalDevice(surface, validationRequired, validators);
VmaAllocatorCreateInfo allocatorInfo = {};
allocatorInfo.physicalDevice = this->device->physical;
allocatorInfo.device = this->device->logical;
allocatorInfo.instance = this->vulkan;
vmaCreateAllocator(&allocatorInfo, &this->allocator);
VkTools::g_allocator = this->allocator;
VkTools::g_PhysicalDevice = this->device->physical;
VkTools::g_Device = this->device->logical;
VkTools::g_Instance = this->vulkan;
this->swapchain = new SwapChain();
this->swapchain->create(surface);
this->renderPass = new RenderPass();
// Set up for vertex rendering
this->renderPass->createVertexRenderPass(swapchain->format);
this->renderTexture = new SingleRenderTexture();
this->renderTexture->createViewsAndFramebuffer(swapchain->images, swapchain->format,
swapchain->extent,renderPass->pass
);
this->buffers = new CommandBuffer();
this->buffers->createCommandPoolAndBuffers(swapchain->images.size());
// Create semaphores for render events
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &newImageSem);
vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &renderDoneSem);
// Create fences for the frames
inFlight.resize(MAX_FRAMES);
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < MAX_FRAMES; i++) {
if (vkCreateFence(device->logical, &fenceInfo, nullptr, &inFlight[i]) != VK_SUCCESS)
throw std::runtime_error("Unable to create fence for a frame");
}
std::cout << "Vulkan Initialization Finished" << std::endl;
}
void VulkanManager::startDraw() {
// Prepare for a new frame to start
vkAcquireNextImageKHR(device->logical, swapchain->swapChain,
std::numeric_limits<uint64_t>::max(), newImageSem,VK_NULL_HANDLE, &imageIndex
);
vkWaitForFences(device->logical, 1, &inFlight[imageIndex], VK_TRUE,
std::numeric_limits<uint64_t>::max()
);
vkResetFences(device->logical, 1, &inFlight[imageIndex]);
// Fetch the next command buffer
currentCommandBuffer = buffers->buffers[imageIndex];
buffers->beginCommandBuffer(currentCommandBuffer);
// Setup render pass; setup clear color
VkClearValue clearColor = { 1.0f, 0.0f, 0.0f, 1.0f }; // Red
// Execute render pass
renderPass->beginRenderPass({ clearColor }, currentCommandBuffer, dynamic_cast<SingleRenderTexture*>(renderTexture)->swapChainFramebuffers[imageIndex], dynamic_cast<SingleRenderTexture*>(renderTexture)->swapChainImageExtent);
}
void VulkanManager::endDraw() {
// End command buffer first
renderPass->endRenderPass(currentCommandBuffer);
buffers->endCommandBuffer(currentCommandBuffer);
// Prepare to submit all draw commands to the GPU
VkPipelineStageFlags waitStages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
};
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &currentCommandBuffer;
submitInfo.pWaitDstStageMask = waitStages;
// Wait for the New Image semaphore, and signal the Render Done semaphore when finished
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &newImageSem;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderDoneSem;
// Submit.
vkQueueSubmit(VulkanManager::getInstance()->getDevice()->graphicsQueue, 1, &submitInfo, inFlight[imageIndex]);
// Prepare to show the drawn frame on the screen.
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapchain->swapChain;
presentInfo.pImageIndices = &imageIndex;
// Wait until render is finished before presenting.
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &renderDoneSem;
// Show.
vkQueuePresentKHR(VulkanManager::getInstance()->getDevice()->presentationQueue, &presentInfo);
// Wait for the GPU to catch up
vkQueueWaitIdle(VulkanManager::getInstance()->getDevice()->presentationQueue);
}
void VulkanManager::cleanup() {
// Wait for the GPU to not be busy
vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical);
// Destroy our own data
vkDestroySemaphore(device->logical, renderDoneSem, nullptr);
vkDestroySemaphore(device->logical, newImageSem, nullptr);
for (size_t i = 0; i < MAX_FRAMES; i++) {
vkDestroyFence(device->logical, inFlight[i], nullptr);
}
buffers->destroy();
renderTexture->destroy();
renderPass->destroy();
swapchain->destroy();
// Destroy the Vulkan Device
VulkanManager::getInstance()->getDevice()->destroy();
// Delete the layer validators.
validators->destroy(validationRequired, vulkan);
// Delete the surface and Vulkan instance.
vkDestroySurfaceKHR(vulkan, surface, nullptr);
vkDestroyInstance(vulkan, nullptr);
vmaDestroyAllocator(allocator);
// Delete allocated memory for our own data.
delete buffers;
delete renderTexture;
delete renderPass;
delete swapchain;
delete device;
delete validators;
}

View File

@ -1,7 +1,9 @@
set(CMAKE_CXX_STANDARD 20)
find_package(SDL2 REQUIRED)
add_executable(shadow-runtime src/main.cpp)
FILE(GLOB_RECURSE SOURCES src/*.cpp src/*.h)
add_executable(shadow-runtime ${SOURCES})
target_include_directories(shadow-runtime PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(shadow-runtime PRIVATE SDL2::Main shadow-engine)