very crude first triangle pre version

This commit is contained in:
Blue 2023-09-24 13:16:10 -03:00
parent 649fdb795f
commit 8d5f6e8a3e
Signed by: blue
GPG Key ID: 9B203B252A63EE38
22 changed files with 1789 additions and 5 deletions

View File

@ -1,7 +1,24 @@
cmake_minimum_required(VERSION 3.0)
project(stories)
project(stories VERSION 0.0.1 LANGUAGES CXX)
cmake_policy(SET CMP0076 NEW)
cmake_policy(SET CMP0079 NEW)
set(CMAKE_CXX_STANDARD 17)
find_package(SDL2 REQUIRED)
find_package(Vulkan REQUIRED)
add_executable(stories main.cpp)
target_include_directories(stories PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(stories
engine
SDL2::SDL2
Vulkan::Vulkan
)
add_subdirectory(engine)
add_subdirectory(shaders)
install(TARGETS stories RUNTIME DESTINATION bin)

25
engine/CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
set(SOURCES
instance.cpp
engine.cpp
utils.cpp
window.cpp
surface.cpp
physicaldevice.cpp
logicaldevice.cpp
swapchain.cpp
)
set(HEADERS
instance.h
engine.h
utils.h
window.h
surface.h
physicaldevice.h
logicaldevice.h
swapchain.h
)
add_library(engine STATIC ${SOURCES})
target_include_directories(engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

138
engine/engine.cpp Normal file
View File

@ -0,0 +1,138 @@
#include "engine.h"
#include "instance.h"
constexpr const char* validationLayerName("VK_LAYER_KHRONOS_validation");
Engine::Engine::Engine() :
initialized(false),
window(new Window()),
instance(nullptr),
surface(nullptr),
physicalDevice(nullptr),
logicalDevice(nullptr),
layerNames(),
instanceExtensionNames(),
deviceExtensionNames(),
layers(),
instanceExtensions(),
deviceExtensions()
{
addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
Engine::Engine::~Engine() {
delete window;
}
bool Engine::Engine::enableValidationLayers() const {
return instanceExtensionNames.count(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) > 0 &&
instanceExtensionNames.count(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) > 0 &&
layerNames.count(validationLayerName) > 0;
}
void Engine::Engine::addLayer(const std::string& layerName) {
if (initialized)
throw std::runtime_error("Adding layers to an initialized engine is not supported yet");
layerNames.insert(layerName);
}
void Engine::Engine::addInstanceExtension(const std::string& extensionName) {
if (initialized)
throw std::runtime_error("Adding extension to an initialized engine is not supported yet");
std::pair<std::set<std::string>::const_iterator, bool> pair = instanceExtensionNames.insert(extensionName);
if (pair.second)
instanceExtensions.push_back(pair.first->c_str());
}
void Engine::Engine::addDeviceExtension(const std::string& extensionName) {
if (initialized)
throw std::runtime_error("Adding extension to an initialized engine is not supported yet");
std::pair<std::set<std::string>::const_iterator, bool> pair = deviceExtensionNames.insert(extensionName);
if (pair.second)
deviceExtensions.push_back(pair.first->c_str());
}
void Engine::Engine::enableDebug() {
addLayer(validationLayerName);
addInstanceExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
void Engine::Engine::initVulkan() {
instance = new Instance(this);
surface = new Surface(instance, window);
pickPhysicalDevice();
logicalDevice = new LogicalDevice(physicalDevice, surface, deviceExtensions, layers);
logicalDevice->createGraphicsPipeline("shaders/shader.vert.spv", "shaders/shader.frag.spv");
logicalDevice->createSwapChain();
}
void Engine::Engine::cleanup() {
delete logicalDevice;
logicalDevice = nullptr;
delete physicalDevice;
physicalDevice = nullptr;
delete surface;
surface = nullptr;
delete instance;
instance = nullptr;
}
void Engine::Engine::pickPhysicalDevice() {
std::vector<VkPhysicalDevice> devices = instance->enumeratePhysicalDevices();
if (devices.size() == 0)
throw std::runtime_error("failed to find GPUs with Vulkan support!");
for (const auto& device : devices) {
if (PhysicalDevice::checkDeviceExtensionSupport(device, deviceExtensionNames)) {
QueueFamilyIndices indices = surface->findQueueFamilies(device);
if (indices.isComplete()) {
SwapChainSupportDetails scsd = surface->querySwapChainSupport(device);
if (scsd.adequate()) {
physicalDevice = new PhysicalDevice(device, indices, scsd);
break;
}
}
}
}
if (physicalDevice == nullptr)
throw std::runtime_error("failed to find a suitable GPU!");
}
std::vector<const char *> Engine::Engine::getRequiredVulkanExtensions() const {
return window->getRequiredVulkanExtensions();
}
void Engine::Engine::run() {
initVulkan();
mainLoop();
cleanup();
}
void Engine::Engine::mainLoop() {
SDL_Event e;
bool bQuit = false;
//main loop
while (!bQuit) {
//Handle events on queue
while (SDL_PollEvent(&e) != 0) {
//close the window when user clicks the X button or alt-f4s
if (e.type == SDL_QUIT)
bQuit = true;
}
logicalDevice->drawFrame();
}
logicalDevice->waitIdle();
}

70
engine/engine.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <stdint.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#include <vector>
#include <set>
#include <string>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <limits>
#include <optional>
#include <set>
#include "window.h"
#include "surface.h"
#include "physicaldevice.h"
#include "logicaldevice.h"
namespace Engine {
const int MAX_FRAMES_IN_FLIGHT = 2;
class Instance;
class Engine {
friend class Instance;
public:
Engine();
~Engine();
void run();
bool enableValidationLayers() const;
void addLayer(const std::string& layerName);
void addInstanceExtension(const std::string& extensionName);
void addDeviceExtension(const std::string& extensionName);
void enableDebug();
std::vector<const char *> getRequiredVulkanExtensions() const;
private:
bool initialized;
Window* window;
Instance* instance;
Surface* surface;
PhysicalDevice* physicalDevice;
LogicalDevice* logicalDevice;
std::set<std::string> layerNames;
std::set<std::string> instanceExtensionNames;
std::set<std::string> deviceExtensionNames;
std::vector<const char*> layers;
std::vector<const char*> instanceExtensions;
std::vector<const char*> deviceExtensions;
void initVulkan();
void mainLoop();
void cleanup();
void pickPhysicalDevice();
};
}

110
engine/instance.cpp Normal file
View File

@ -0,0 +1,110 @@
#include "instance.h"
#include "engine.h"
Engine::Instance::Instance(Engine* eng) :
validationLayersEnabledAndSupported(false),
engine(eng),
vk(),
debugMessenger()
{
VkApplicationInfo appInfo {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 1);
appInfo.pEngineName = "Stories";
appInfo.engineVersion = VK_MAKE_VERSION(0, 0, 1);
appInfo.apiVersion = VK_API_VERSION_1_0;
appInfo.pNext = nullptr;
//TODO handle exception
Support exts = getVulkanExtensions(engine->instanceExtensionNames, engine->getRequiredVulkanExtensions());
if (exts.unsupportedCritical.size() > 0) {
std::string error("Unable to create Vulkan instance, following critical extensions are unsupported:\n");
for (const char* ext : exts.unsupportedCritical) {
error += "\t" + std::string(ext) + "\n";
}
throw std::runtime_error(error);
}
//TODO log unsupported;
exts.logSupported("Applying extensions:");
//TODO handle exception
Support layers = getVulkanLayers(engine->layerNames);
//TODO log unsupported
layers.logSupported("Applying layers:");
VkInstanceCreateInfo createInfo {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
createInfo.flags = 0;
createInfo.enabledExtensionCount = static_cast<uint32_t>(exts.supported.size());
createInfo.ppEnabledExtensionNames = exts.supported.data();
createInfo.enabledLayerCount = static_cast<uint32_t>(layers.supported.size());
createInfo.ppEnabledLayerNames = layers.supported.data();
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo {};
if (engine->enableValidationLayers()) {
validationLayersEnabledAndSupported = true; //TODO make sure it's actually supported, this shows that they are just enabled;
populateDebugMessengerCreateInfo(debugCreateInfo, debugCallback);
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
}
VkResult res = vkCreateInstance(&createInfo, nullptr, &vk);
switch (res) {
case VK_SUCCESS:
break;
case VK_ERROR_INCOMPATIBLE_DRIVER:
throw std::runtime_error("unable to create vulkan instance, cannot find a compatible Vulkan ICD");
case VK_ERROR_OUT_OF_HOST_MEMORY:
throw std::runtime_error("unable to create vulkan instance, out of host memory");
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
throw std::runtime_error("unable to create vulkan instance, out of device memory");
case VK_ERROR_INITIALIZATION_FAILED:
throw std::runtime_error("unable to create vulkan instance, initialization failed");
case VK_ERROR_LAYER_NOT_PRESENT:
throw std::runtime_error("unable to create vulkan instance, layer not present");
case VK_ERROR_EXTENSION_NOT_PRESENT:
throw std::runtime_error("unable to create vulkan instance, extension not present");
default:
throw std::runtime_error("unable to create Vulkan instance: unknown error");
}
if (validationLayersEnabledAndSupported)
setupDebugMessenger();
}
Engine::Instance::~Instance() {
if (validationLayersEnabledAndSupported)
destroyDebugUtilsMessengerEXT(vk, debugMessenger, nullptr);
vkDestroyInstance(vk, nullptr);
}
void Engine::Instance::setupDebugMessenger() {
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
populateDebugMessengerCreateInfo(createInfo, debugCallback);
VkResult res = createDebugUtilsMessengerEXT(vk, &createInfo, nullptr, &debugMessenger);
switch (res) {
case VK_SUCCESS:
break;
case VK_ERROR_EXTENSION_NOT_PRESENT:
throw std::runtime_error("failed to set up debug messenger: extension not present");
break;
default:
throw std::runtime_error("failed to set up debug messenger");
}
}
std::vector<VkPhysicalDevice> Engine::Instance::enumeratePhysicalDevices() const {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(vk, &deviceCount, nullptr);
std::vector<VkPhysicalDevice> devices(deviceCount);
if (deviceCount > 0)
vkEnumeratePhysicalDevices(vk, &deviceCount, devices.data());
return devices;
}

33
engine/instance.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include "utils.h"
namespace Engine {
class Engine;
class Window;
class Surface;
class Instance {
friend class Engine;
friend class Surface;
public:
Instance(Engine* engine);
~Instance();
std::vector<VkPhysicalDevice> enumeratePhysicalDevices() const;
private:
void setupDebugMessenger();
private:
bool validationLayersEnabledAndSupported;
Engine* engine;
VkInstance vk;
VkDebugUtilsMessengerEXT debugMessenger;
};
}

568
engine/logicaldevice.cpp Normal file
View File

@ -0,0 +1,568 @@
#include "logicaldevice.h"
#include "swapchain.h"
constexpr int MAX_FRAMES_IN_FLIGHT = 2;
constexpr float queuePriority = 1.0f;
Engine::LogicalDevice::LogicalDevice(
PhysicalDevice* physicalDevice,
Surface* surface,
const std::vector<const char*>& extensions,
const std::vector<const char*> layers
):
phys(physicalDevice),
vk(),
graphicsQueue(),
presentQueue(),
renderPass(),
pipelineLayout(),
graphicsPipeline(),
commandPool(),
commandBuffers(),
imageAvailableSemaphores(),
renderFinishedSemaphores(),
inFlightFences(),
currentFrame(0),
framebufferResized(false),
hasPipeline(false),
surface(surface),
surfaceFormat(surface->chooseSwapSurfaceFormat(phys->swapChainSupport)),
swapChain(nullptr)
{
const QueueFamilyIndices& indices = phys->indices;
createDevice(phys->vk, indices, extensions, layers);
createQueues(indices.graphicsFamily.value(), indices.presentFamily.value());
createCommandPool(phys->indices.graphicsFamily.value());
createCommandBuffers();
createSyncObjects();
createRenderPass(surfaceFormat.format);
}
Engine::LogicalDevice::~LogicalDevice() {
clearSwapChain();
if (hasPipeline) {
vkDestroyPipeline(vk, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(vk, pipelineLayout, nullptr);
}
vkDestroyRenderPass(vk, renderPass, nullptr);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(vk, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(vk, imageAvailableSemaphores[i], nullptr);
vkDestroyFence(vk, inFlightFences[i], nullptr);
}
vkFreeCommandBuffers(vk, commandPool, MAX_FRAMES_IN_FLIGHT, commandBuffers.data());
vkDestroyCommandPool(vk, commandPool, nullptr);
vkDestroyDevice(vk, nullptr);
}
void Engine::LogicalDevice::createSwapChain() {
clearSwapChain();
swapChain = new SwapChain(this);
}
void Engine::LogicalDevice::clearSwapChain() {
if (swapChain != nullptr)
delete swapChain;
swapChain = nullptr;
}
VkResult Engine::LogicalDevice::waitIdle() {
return vkDeviceWaitIdle(vk);
}
VkResult Engine::LogicalDevice::waitForFence(const VkFence& fence) {
return vkWaitForFences(vk, 1, &fence, VK_TRUE, UINT64_MAX);
}
VkResult Engine::LogicalDevice::resetFence(const VkFence& fence) {
return vkResetFences(vk, 1, &fence);
}
VkResult Engine::LogicalDevice::queueSubmitGraphics(
const VkSemaphore& wait,
const VkCommandBuffer& buffer,
const VkSemaphore& signal,
const VkFence& fence
) {
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &wait;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &buffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &signal;
return vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence);
}
VkResult Engine::LogicalDevice::queuePresent(const VkSemaphore& signal, uint32_t index) {
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &signal;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain->vk;
presentInfo.pImageIndices = &index;
return vkQueuePresentKHR(presentQueue, &presentInfo);
}
VkShaderModule Engine::LogicalDevice::createShaderModule(const std::string& path) {
std::vector<char> code = readFile(path);
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(vk, &createInfo, nullptr, &shaderModule) != VK_SUCCESS)
throw std::runtime_error("failed to create shader module!");
return shaderModule;
}
void Engine::LogicalDevice::destroyShaderModule(VkShaderModule shaderModule) {
vkDestroyShaderModule(vk, shaderModule, nullptr);
}
void Engine::LogicalDevice::createDevice(
VkPhysicalDevice physicalDevice,
const QueueFamilyIndices& indices,
const std::vector<const char*>& extensions,
const std::vector<const char*> layers
) {
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
createInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());
createInfo.ppEnabledLayerNames = layers.data();
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &vk) != VK_SUCCESS)
throw std::runtime_error("failed to create logical device!");
}
void Engine::LogicalDevice::createQueues(uint32_t graphicsFamilyIndex, uint32_t presenFamilyIndex) {
vkGetDeviceQueue(vk, graphicsFamilyIndex, 0, &graphicsQueue);
vkGetDeviceQueue(vk, presenFamilyIndex, 0, &presentQueue);
}
void Engine::LogicalDevice::createSyncObjects() {
imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
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_IN_FLIGHT; i++) {
if (vkCreateSemaphore(vk, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(vk, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(vk, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create synchronization objects for a frame!");
}
}
}
void Engine::LogicalDevice::createCommandPool(uint32_t queueFamilyIndex) {
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamilyIndex;
if (vkCreateCommandPool(vk, &poolInfo, nullptr, &commandPool) != VK_SUCCESS)
throw std::runtime_error("failed to create command pool!");
}
void Engine::LogicalDevice::createCommandBuffers() {
commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();
if (vkAllocateCommandBuffers(vk, &allocInfo, commandBuffers.data()) != VK_SUCCESS)
throw std::runtime_error("failed to allocate command buffers!");
}
void Engine::LogicalDevice::drawFrame() {
waitForFence(inFlightFences[currentFrame]);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(vk, swapChain->vk, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}
resetFence(inFlightFences[currentFrame]);
vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
if (queueSubmitGraphics(
imageAvailableSemaphores[currentFrame],
commandBuffers[currentFrame],
renderFinishedSemaphores[currentFrame],
inFlightFences[currentFrame]) != VK_SUCCESS
)
throw std::runtime_error("failed to submit draw command buffer!");
result = queuePresent(renderFinishedSemaphores[currentFrame], imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
framebufferResized = false;
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("failed to present swap chain image!");
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
VkExtent2D Engine::LogicalDevice::chooseSwapExtent() const {
return surface->chooseSwapExtent(phys->swapChainSupport.capabilities);
}
VkResult Engine::LogicalDevice::createVkSwapChain(const VkExtent2D& extent, VkSwapchainKHR& out) {
const SwapChainSupportDetails& swapChainSupport = phys->swapChainSupport;
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount;
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface->vk;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
createInfo.imageExtent = extent;
createInfo.minImageCount = imageCount;
const QueueFamilyIndices& indices = phys->indices;
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};
if (indices.graphicsFamily != indices.presentFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
createInfo.clipped = VK_TRUE;
return vkCreateSwapchainKHR(vk, &createInfo, nullptr, &out);
}
void Engine::LogicalDevice::destroyVkSwapChain(VkSwapchainKHR& swapChain) {
vkDestroySwapchainKHR(vk, swapChain, nullptr);
}
VkResult Engine::LogicalDevice::getVkSwapChainImages(VkSwapchainKHR& swapChain, std::vector<VkImage>& out) {
uint32_t imageCount;
VkResult result = vkGetSwapchainImagesKHR(vk, swapChain, &imageCount, nullptr);
if (result != VK_SUCCESS)
return result;
out.resize(imageCount);
return vkGetSwapchainImagesKHR(vk, swapChain, &imageCount, out.data());
}
VkResult Engine::LogicalDevice::createVkImageView(const VkImage& image, VkImageView& out) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = image;
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = surfaceFormat.format;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
return vkCreateImageView(vk, &createInfo, nullptr, &out);
}
void Engine::LogicalDevice::destroyVkImageView(VkImageView& view) {
vkDestroyImageView(vk, view, nullptr);
}
VkResult Engine::LogicalDevice::createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out) {
VkImageView attachments[] = {imageView};
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = extent.width;
framebufferInfo.height = extent.height;
framebufferInfo.layers = 1;
return vkCreateFramebuffer(vk, &framebufferInfo, nullptr, &out);
}
void Engine::LogicalDevice::destroyVkFrameBuffer(VkFramebuffer& buffer) {
vkDestroyFramebuffer(vk, buffer, nullptr);
}
void Engine::LogicalDevice::createRenderPass(VkFormat format) {
VkAttachmentDescription colorAttachment{};
colorAttachment.format = format;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
if (vkCreateRenderPass(vk, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS)
throw std::runtime_error("failed to create render pass!");
}
void Engine::LogicalDevice::createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath) {
if (hasPipeline)
throw std::runtime_error("an attempt to create graphics pipeline for the second time");
VkShaderModule vertShaderModule = createShaderModule(vertexShaderPath);
VkShaderModule fragShaderModule = createShaderModule(fragmentShaderPath);
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.vertexAttributeDescriptionCount = 0;
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
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;
colorBlending.blendConstants[0] = 0.0f;
colorBlending.blendConstants[1] = 0.0f;
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
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 = 0;
pipelineLayoutInfo.pushConstantRangeCount = 0;
if (vkCreatePipelineLayout(vk, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
throw std::runtime_error("failed to create pipeline layout!");
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
if (vkCreateGraphicsPipelines(vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
throw std::runtime_error("failed to create graphics pipeline!");
destroyShaderModule(fragShaderModule);
destroyShaderModule(vertShaderModule);
hasPipeline = true;
}
void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
VkExtent2D extent = swapChain->getExtent();
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 = swapChain->getFrameBuffer(imageIndex);
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = extent;
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
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) extent.width;
viewport.height = (float) extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = extent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
vkCmdEndRenderPass(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
throw std::runtime_error("failed to record command buffer!");
}
void Engine::LogicalDevice::recreateSwapChain() {
surface->waitForResize();
waitIdle();
createSwapChain();
}

92
engine/logicaldevice.h Normal file
View File

@ -0,0 +1,92 @@
#pragma once
#include <vector>
#include <string>
#include <vulkan/vulkan.h>
#include "utils.h"
#include "physicaldevice.h"
#include "surface.h"
namespace Engine {
class Engine;
class SwapChain;
class LogicalDevice {
friend class Engine;
public:
LogicalDevice(
PhysicalDevice* physicalDevice,
Surface* surface,
const std::vector<const char*>& extensions,
const std::vector<const char*> layers
);
~LogicalDevice();
void createSwapChain();
void clearSwapChain();
void createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath);
void recreateSwapChain();
void drawFrame();
VkResult waitIdle();
VkResult waitForFence(const VkFence& fence);
VkResult resetFence(const VkFence& fence);
VkExtent2D chooseSwapExtent() const;
VkResult createVkSwapChain(const VkExtent2D& extent, VkSwapchainKHR& out);
void destroyVkSwapChain(VkSwapchainKHR& swapChain);
VkResult getVkSwapChainImages(VkSwapchainKHR& swapChain, std::vector<VkImage>& out);
VkResult createVkImageView(const VkImage& image, VkImageView& out);
void destroyVkImageView(VkImageView& view);
VkResult createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out);
void destroyVkFrameBuffer(VkFramebuffer& buffer);
private:
VkShaderModule createShaderModule(const std::string& path);
void destroyShaderModule(VkShaderModule shaderModule);
void createDevice(
VkPhysicalDevice physicalDevice,
const QueueFamilyIndices& indices,
const std::vector<const char*>& extensions,
const std::vector<const char*> layers
);
void createQueues(uint32_t graphicsFamilyIndex, uint32_t presenFamilyIndex);
void createCommandPool(uint32_t queueFamilyIndex);
void createCommandBuffers();
void createSyncObjects();
void createRenderPass(VkFormat format);
VkResult queueSubmitGraphics(
const VkSemaphore& wait,
const VkCommandBuffer& buffer,
const VkSemaphore& signal,
const VkFence& fence
);
VkResult queuePresent(const VkSemaphore& signal, uint32_t index);
void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex);
private:
PhysicalDevice* phys;
VkDevice vk;
VkQueue graphicsQueue;
VkQueue presentQueue;
VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
VkCommandPool commandPool;
std::vector<VkCommandBuffer> commandBuffers;
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
uint32_t currentFrame;
bool framebufferResized;
bool hasPipeline;
Surface* surface;
VkSurfaceFormatKHR surfaceFormat;
SwapChain* swapChain;
};
}

34
engine/physicaldevice.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "physicaldevice.h"
Engine::PhysicalDevice::PhysicalDevice(
VkPhysicalDevice raw,
const QueueFamilyIndices& indices,
const SwapChainSupportDetails& swapChainSupport):
indices(indices),
swapChainSupport(swapChainSupport),
vk(raw)
{}
bool Engine::PhysicalDevice::checkDeviceExtensionSupport(VkPhysicalDevice device, std::set<std::string> requiredExtensions) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
for (const auto& extension : availableExtensions)
requiredExtensions.erase(extension.extensionName);
if (!requiredExtensions.empty()) {
// std::cout << "Available Extensions: " << std::endl;
// for (const auto& ext : availableExtensions)
// std::cout << ext.extensionName << std::endl;
std::cout << "Missing Extensions: " << std::endl;
for (const auto& ext : requiredExtensions)
std::cout << ext << std::endl;
}
return requiredExtensions.empty();
}

30
engine/physicaldevice.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include <vulkan/vulkan.h>
#include "utils.h"
namespace Engine {
class Engine;
class LogicalDevice;
class PhysicalDevice {
friend class Engine;
friend class LogicalDevice;
public:
PhysicalDevice(VkPhysicalDevice raw, const QueueFamilyIndices& indices, const SwapChainSupportDetails& swapChainSupport);
static bool checkDeviceExtensionSupport(VkPhysicalDevice device, std::set<std::string> requiredExtensions);
const QueueFamilyIndices indices;
const SwapChainSupportDetails swapChainSupport;
private:
VkPhysicalDevice vk;
};
}

88
engine/surface.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "surface.h"
#include "window.h"
#include "instance.h"
Engine::Surface::Surface(const Instance* instance, const Window* window):
vk(),
instance(instance),
window(window)
{
window->createSurface(instance->vk, &vk);
}
Engine::Surface::~Surface() {
vkDestroySurfaceKHR(instance->vk, vk, nullptr);
}
bool Engine::Surface::isDeviceSutable(VkPhysicalDevice device) const {
QueueFamilyIndices indices = findQueueFamilies(device);
if (!indices.isComplete())
return false;
return querySwapChainSupport(device).adequate();
}
Engine::SwapChainSupportDetails Engine::Surface::querySwapChainSupport(VkPhysicalDevice device) const {
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, vk, &details.capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, vk, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, vk, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, vk, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, vk, &presentModeCount, details.presentModes.data());
}
return details;
}
Engine::QueueFamilyIndices Engine::Surface::findQueueFamilies(VkPhysicalDevice device) const {
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
for (std::vector<VkQueueFamilyProperties>::size_type i = 0; i < queueFamilyCount; ++i) {
const VkQueueFamilyProperties& queueFamily = queueFamilies[i];
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)
indices.graphicsFamily = i;
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, vk, &presentSupport);
if (presentSupport)
indices.presentFamily = i;
if (indices.isComplete())
break;
}
return indices;
}
VkExtent2D Engine::Surface::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const {
return window->chooseSwapExtent(capabilities);
}
VkSurfaceFormatKHR Engine::Surface::chooseSwapSurfaceFormat(const SwapChainSupportDetails& swapChainSupport) const {
return ::Engine::chooseSwapSurfaceFormat(swapChainSupport.formats);
}
VkExtent2D Engine::Surface::waitForResize() const {
return window->waitForResize();
}

35
engine/surface.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <vulkan/vulkan.h>
#include "utils.h"
namespace Engine {
class Window;
class Instance;
class Engine;
class LogicalDevice;
class Surface {
friend class Engine;
friend class LogicalDevice;
public:
Surface(const Instance* instance, const Window* window);
~Surface();
VkExtent2D waitForResize() const;
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const;
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const SwapChainSupportDetails& swapChainSupport) const;
bool isDeviceSutable(VkPhysicalDevice device) const;
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) const;
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) const;
private:
VkSurfaceKHR vk;
const Instance* instance;
const Window* window;
};
}

51
engine/swapchain.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "swapchain.h"
#include "logicaldevice.h"
Engine::SwapChain::SwapChain(LogicalDevice* logical):
logical(logical),
vk(),
images(),
extent(logical->chooseSwapExtent()),
imageViews(),
frameBuffers()
{
VkResult result = logical->createVkSwapChain(extent, vk);
if (result != VK_SUCCESS)
throw std::runtime_error("failed to create swap chain!");
logical->getVkSwapChainImages(vk, images);
imageViews.resize(images.size());
frameBuffers.resize(images.size());
for (size_t i = 0; i < images.size(); i++) {
if (logical->createVkImageView(images[i], imageViews[i]) != VK_SUCCESS)
throw std::runtime_error("failed to create image views!");
}
for (size_t i = 0; i < images.size(); i++) {
if (logical->createVkFrameBuffer(imageViews[i], extent, frameBuffers[i]) != VK_SUCCESS)
throw std::runtime_error("failed to create framebuffer!");
}
}
Engine::SwapChain::~SwapChain() {
for (VkFramebuffer& buffer : frameBuffers)
logical->destroyVkFrameBuffer(buffer);
for (VkImageView& imageView : imageViews)
logical->destroyVkImageView(imageView);
//images are also indirectly freed by this operation
logical->destroyVkSwapChain(vk);
}
VkExtent2D Engine::SwapChain::getExtent() const {
return extent;
}
VkFramebuffer Engine::SwapChain::getFrameBuffer(uint32_t index) {
return frameBuffers[index];
}

29
engine/swapchain.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include <vulkan/vulkan.h>
namespace Engine {
class LogicalDevice;
class SwapChain {
friend class LogicalDevice;
public:
SwapChain(LogicalDevice* logical);
~SwapChain();
VkExtent2D getExtent() const;
VkFramebuffer getFrameBuffer(uint32_t index);
private:
LogicalDevice* logical;
VkSwapchainKHR vk;
std::vector<VkImage> images;
VkExtent2D extent;
std::vector<VkImageView> imageViews;
std::vector<VkFramebuffer> frameBuffers;
};
}

194
engine/utils.cpp Normal file
View File

@ -0,0 +1,194 @@
#include "utils.h"
bool discoveredLayers = false;
bool discoveredExtensions = false;
std::map<std::string, VkLayerProperties> availableLayers;
std::map<std::string, VkExtensionProperties> availableExtensions;
Engine::Support Engine::getVulkanLayers(const std::set<std::string>& requestedLayers)
{
Engine::Support result;
if (!discoveredLayers) {
std::vector<VkLayerProperties> props = getAvailableVulkanLayers();
for (const VkLayerProperties& prop : props) {
availableLayers.insert(std::make_pair(prop.layerName, prop));
}
discoveredLayers = true;
}
for (const std::string& ext : requestedLayers) {
if (availableLayers.count(ext) > 0) {
result.supported.push_back(ext.c_str());
} else {
result.unsupported.push_back(ext.c_str());
}
}
return result;
}
Engine::Support Engine::getVulkanExtensions(
const std::set<std::string>& requestedExtensions,
const std::vector<const char*>& criticalExtensions
) {
Support result;
if (!discoveredExtensions) {
std::vector<VkExtensionProperties> props = getAvailableVulkanExtensions();
for (const VkExtensionProperties& prop : props) {
availableExtensions.insert(std::make_pair(prop.extensionName, prop));
}
discoveredExtensions = true;
}
for (const char* ext : criticalExtensions) {
if (availableExtensions.count(ext) > 0) {
result.supported.push_back(ext);
} else {
result.unsupportedCritical.push_back(ext);
}
}
for (const std::string& ext : requestedExtensions) {
if (availableExtensions.count(ext) > 0) {
result.supported.push_back(ext.c_str());
} else {
result.unsupported.push_back(ext.c_str());
}
}
return result;
}
std::vector<VkExtensionProperties> Engine::getAvailableVulkanExtensions() {
unsigned int extCount = 0;
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr);
if (res != VK_SUCCESS) {
throw std::runtime_error("unable to query vulkan extension property count");
}
std::vector<VkExtensionProperties> extensions(extCount);
res = vkEnumerateInstanceExtensionProperties(nullptr, &extCount, extensions.data());
if (res != VK_SUCCESS) {
throw std::runtime_error("unable to retrieve vulkan instance layer names");
}
return extensions;
}
void Engine::populateDebugMessengerCreateInfo(
VkDebugUtilsMessengerCreateInfoEXT& createInfo,
PFN_vkDebugUtilsMessengerCallbackEXT debugCallback)
{
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_INFO_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
}
std::vector<VkLayerProperties> Engine::getAvailableVulkanLayers() {
unsigned int layerCount = 0;
VkResult res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
if (res != VK_SUCCESS) {
throw std::runtime_error("unable to query vulkan instance layer property count");
}
std::vector<VkLayerProperties> layers(layerCount);
res = vkEnumerateInstanceLayerProperties(&layerCount, layers.data());
if (res != VK_SUCCESS) {
throw std::runtime_error("unable to retrieve vulkan instance layer names");
}
return layers;
}
VkResult Engine::createDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger)
{
PFN_vkVoidFunction vdfunc = vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)(vdfunc);
if (func != nullptr)
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
else
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
void Engine::destroyDebugUtilsMessengerEXT(
VkInstance instance,
VkDebugUtilsMessengerEXT debugMessenger,
const VkAllocationCallbacks* pAllocator)
{
PFN_vkVoidFunction vdfunc = vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)(vdfunc);
if (func != nullptr)
func(instance, debugMessenger, pAllocator);
}
Engine::Support::Support():
supported(),
unsupported(),
unsupportedCritical()
{}
void Engine::Support::logSupported(const std::string& header) {
if (!supported.empty()) {
std::cout << header << std::endl;
for (const char* element : supported)
std::cout << "\t" << element << std::endl;
}
}
bool Engine::SwapChainSupportDetails::adequate() const {
return !formats.empty() && !presentModes.empty();
}
bool Engine::QueueFamilyIndices::isComplete() const {
return graphicsFamily.has_value() && presentFamily.has_value();
}
std::vector<char> Engine::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;
}
VkSurfaceFormatKHR Engine::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
for (const VkSurfaceFormatKHR& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
return availableFormats[0];
}
VkPresentModeKHR Engine::chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
for (const auto& availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}

81
engine/utils.h Normal file
View File

@ -0,0 +1,81 @@
#pragma once
#include <vector>
#include <vulkan/vulkan.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <optional>
#include <fstream>
namespace Engine {
struct Support {
Support();
void logSupported(const std::string& header);
std::vector<const char*> supported;
std::vector<const char*> unsupported;
std::vector<const char*> unsupportedCritical;
};
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
bool adequate() const;
};
struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete() const;
};
Support getVulkanLayers(const std::set<std::string>& requestedLayers);
Support getVulkanExtensions(const std::set<std::string>& requestedExtensions, const std::vector<const char*>& criticalExtensions);
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
std::vector<VkExtensionProperties> getAvailableVulkanExtensions();
std::vector<VkLayerProperties> getAvailableVulkanLayers();
void populateDebugMessengerCreateInfo(
VkDebugUtilsMessengerCreateInfoEXT& createInfo,
PFN_vkDebugUtilsMessengerCallbackEXT debugCallback
);
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData,
void* userData
)
{
std::cerr << "validation layer: " << callbackData->pMessage << std::endl;
return true;
}
VkResult createDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger
);
void destroyDebugUtilsMessengerEXT(
VkInstance instance,
VkDebugUtilsMessengerEXT debugMessenger,
const VkAllocationCallbacks* pAllocator
);
std::vector<char> readFile(const std::string& filename);
}

90
engine/window.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "window.h"
Engine::Window::Window(uint32_t width, uint32_t height) :
sdl(createWindow(width, height)),
extent({width, height})
{
//glfwSetWindowUserPointer(window, this);
//glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
}
// static void framebufferResizeCallback(SDL_Window* window, int width, int height) {
// auto app = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(window));
// app->framebufferResized = true;
// }
Engine::Window::~Window() {
SDL_DestroyWindow(sdl);
SDL_Quit();
}
SDL_Window* Engine::Window::createWindow(uint32_t width, uint32_t height) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* wnd = SDL_CreateWindow(
"Stories", //TODO parametrize window title
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
width,
height,
SDL_WINDOW_VULKAN
);
return wnd;
}
std::vector<const char *> Engine::Window::getRequiredVulkanExtensions() const {
unsigned int extCount = 0;
if (!SDL_Vulkan_GetInstanceExtensions(sdl, &extCount, nullptr)) {
throw std::runtime_error("Unable to query the number of Vulkan instance extensions");
}
std::vector<const char*> extract(extCount);
if (!SDL_Vulkan_GetInstanceExtensions(sdl, &extCount, extract.data())) {
throw std::runtime_error("Unable to query the number of Vulkan instance extension names");
}
return extract;
}
VkExtent2D Engine::Window::getDrawableSize() const {
int width, height;
SDL_Vulkan_GetDrawableSize(sdl, &width, &height);
VkExtent2D actualExtent = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height)
};
return actualExtent;
}
void Engine::Window::createSurface(VkInstance instance, VkSurfaceKHR* out) const {
if (SDL_Vulkan_CreateSurface(sdl, instance, out) != SDL_TRUE) {
throw std::runtime_error("failed to create window surface!");
}
}
VkExtent2D Engine::Window::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const {
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
} else {
VkExtent2D actualExtent = getDrawableSize();
actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
return actualExtent;
}
}
VkExtent2D Engine::Window::waitForResize() const {
VkExtent2D extent = getDrawableSize();
while (extent.width == 0 || extent.height == 0) {
extent = getDrawableSize();
SDL_WaitEvent(nullptr);
}
return extent;
}

36
engine/window.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#include <stdexcept>
#include <vector>
#include <algorithm>
namespace Engine {
class Engine;
class Window {
friend class Engine;
public:
Window(uint32_t width = 800, uint32_t height = 600);
~Window();
std::vector<const char *> getRequiredVulkanExtensions() const;
VkExtent2D waitForResize() const;
VkExtent2D getDrawableSize() const;
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const;
void createSurface(VkInstance instance, VkSurfaceKHR* out) const;
private:
static SDL_Window* createWindow(uint32_t width = 800, uint32_t height = 600);
private:
SDL_Window* sdl;
VkExtent2D extent;
};
}

View File

@ -1,6 +1,16 @@
#include <iostream>
#include "engine/engine.h"
int main(int argc, char **argv) {
std::cout << "Hello, world!" << std::endl;
return 0;
int main() {
Engine::Engine app;
app.enableDebug();
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

24
shaders/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
set(GLSL_VALIDATOR "glslangValidator")
file(GLOB_RECURSE GLSL_SOURCE_FILES
"*.frag"
"*.vert"
)
foreach(GLSL ${GLSL_SOURCE_FILES})
get_filename_component(FILE_NAME ${GLSL} NAME)
set(SPIRV "${PROJECT_BINARY_DIR}/shaders/${FILE_NAME}.spv")
add_custom_command(
OUTPUT ${SPIRV}
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/shaders/"
COMMAND ${GLSL_VALIDATOR} -V ${GLSL} -o ${SPIRV}
DEPENDS ${GLSL})
list(APPEND SPIRV_BINARY_FILES ${SPIRV})
endforeach(GLSL)
add_custom_target(
Shaders
DEPENDS ${SPIRV_BINARY_FILES}
)
add_dependencies(stories Shaders)

9
shaders/shader.frag Normal file
View File

@ -0,0 +1,9 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

20
shaders/shader.vert Normal file
View File

@ -0,0 +1,20 @@
#version 450
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}