first release

This commit is contained in:
2019-10-14 23:02:51 +02:00
commit 542ef348ee
72 changed files with 5990 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
#pragma once
#include "../Base/ICloseable.hpp"
#include "Device.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
/**
* \brief A not managed buffer. This should be used rarely.
*/
struct Buffer : virtual public ICloseable
{
vk::Device device;
vk::DeviceMemory memory;
vk::DeviceSize size = 0, alignment = 0, allocSize = 0;
vk::MemoryPropertyFlags memoryPropertyFlags;
void* mapped = nullptr;
/**
* \brief Maps the buffer into the memory of the host.
* \tparam T The type of the buffers data.
* \param offset The offset from where to map the buffer.
* \param size The size to be mapped. VK_WHOLE_SIZE to map the whole buffer.
* \return The pointer to the mapped buffer.
*/
template <typename T = void>
T * Map(size_t offset = 0, VkDeviceSize size = VK_WHOLE_SIZE)
{
mapped = device.mapMemory(memory, offset, size, vk::MemoryMapFlags());
return static_cast<T*>(mapped);
}
/**
* \brief Un-maps the buffer from the host.
*/
void UnMap()
{
device.unmapMemory(memory);
mapped = nullptr;
}
/**
* \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped!
* \param size The size of the data to copy.
* \param data The data to copy
* \param offset The offset for where to copy the data to in the buffer.
*/
void Copy(size_t size, const void* data, VkDeviceSize offset = 0) const
{
if (!mapped) return;
memcpy(static_cast<uint8_t*>(mapped) + offset, data, size);
}
/**
* \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped!
* \param data The data to copy.
* \param offset The offset for where to copy the data to in the buffer.
*/
template <typename T>
void Copy(const T& data, VkDeviceSize offset = 0) const
{
Copy(sizeof(T), &data, offset);
}
/**
* \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped!
* \param data The data to copy.
* \param offset The offset for where to copy the data to in the buffer.
*/
template <typename T>
void Copy(const std::vector<T>& data, VkDeviceSize offset = 0) const
{
copy(sizeof(T) * data.size(), data.data(), offset);
}
/**
* \brief Flushes the memory region of the buffer to the device. This should be only necessary for non coherent memory.
* \param size The amount to flush. VK_WHOLE_SIZE flushes the entire buffer.
* \param offset The offset from where to start the flush.
*/
void Flush(vk::DeviceSize size = VK_WHOLE_SIZE, vk::DeviceSize offset = 0) const
{
device.flushMappedMemoryRanges(vk::MappedMemoryRange(memory, offset, size));
}
/**
* \brief Invalidates the memory region of the buffer to allow access from the host. This should be only necessary for non coherent memory.
* \param size The amount to make available. VK_WHOLE_SIZE invalidate the entire buffer.
* \param offset The offset from where to make the memory available.
*/
void Invalidate(vk::DeviceSize size = VK_WHOLE_SIZE, vk::DeviceSize offset = 0) const
{
device.invalidateMappedMemoryRanges(vk::MappedMemoryRange(memory, offset, size));
}
void Close() override
{
if (mapped) UnMap();
if(memory)
{
device.free(memory);
memory = vk::DeviceMemory();
}
}
};
}
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "../Base/ICloseable.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct CommandHelper : virtual ICloseable
{
vk::Device device;
vk::CommandPool cmdPool;
vk::CommandBuffer cmdBuffer;
vk::CommandBufferLevel level;
CommandHelper() = default;
~CommandHelper() { if (cmdPool) CommandHelper::Close(); }
void Init(vk::Device device, uint32_t queueIndex, vk::CommandBufferLevel level = vk::CommandBufferLevel::eSecondary)
{
this->level = level;
this->device = device;
cmdPool = device.createCommandPool(vk::CommandPoolCreateInfo({}, queueIndex));
vk::CommandBufferAllocateInfo bufferAllocInfo = { cmdPool, level, 1 };
cmdBuffer = device.allocateCommandBuffers(bufferAllocInfo)[0];
}
void Reset() const
{
device.resetCommandPool(cmdPool, {});
}
vk::CommandBufferLevel GetLevel() const
{
return level;
}
void Close() override
{
device.freeCommandBuffers(cmdPool, 1, &cmdBuffer);
device.destroyCommandPool(cmdPool);
}
};
}
}

View File

@@ -0,0 +1,119 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "../Base/IGraphicsApp.hpp"
#include "../Base/IGraphicsAppManager.hpp"
#include "../Base/EngineConstants.hpp"
#include "../Base/Utils.hpp"
#include "Debuging/ValidationLayer.hpp"
#include "DeviceManager.hpp"
#include "SwapChain.hpp"
#include "RenderPass.hpp"
#include "Pipeline.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class Context : virtual public ICloseable
{
bool enableValidationLayer, initialized;
std::set<std::string> requiredExtensions;
public:
DeviceManager deviceManager;
vk::Instance instance; // Vulkan instance
vk::DispatchLoaderDynamic dynamicDispatch; // for access to features not available in statically linked Vulkan lib
vk::SurfaceKHR surface; // Vulkan surface to display framebuffer on
Device* device = nullptr;
SwapChain swapChain;
RenderPass swapChainRenderPass;
IVulkanWindow* window = nullptr;
IGraphicsAppManager* graphicsAppManager = nullptr;
Pipeline pipeline;
Context() : initialized(false)
{
#ifdef DEBUG
enableValidationLayer = true;
#else
enableValidationLayer = false;
#endif
}
virtual ~Context()
{
if (initialized) Close();
}
void Init(IGraphicsAppManager* graphicsAppManager, IVulkanWindow* window)
{
if (initialized) throw std::runtime_error("The context is already initialized");
this->graphicsAppManager = graphicsAppManager;
this->window = window;
// Get the extensions required to display on the window
for (const auto& requiredExtension : window->GetRequiredInstanceExtensions()) { RequireExtension(requiredExtension.c_str()); }
CreateInstance(); // Create the vulkan instance
surface = window->CreateSurface(instance); // Create the surface from the window
CreateDevice();
swapChain.Init(device, surface, window);
swapChainRenderPass.Init(device, &swapChain);
pipeline.Init(device->device);
initialized = true;
}
void Close() override
{
if (!initialized) return;
device->WaitIdle();
pipeline.Close();
swapChainRenderPass.Close();
swapChain.Close();
deviceManager.Close();
//TODO
if (enableValidationLayer) Debug::CloseValidationLayers(instance);
initialized = false;
}
void Resize(const uint32_t newWidth, const uint32_t newHeight)
{
device->WaitIdle();
swapChain.Resize(newWidth, newHeight);
}
void RequireExtension(const char* extension)
{
requiredExtensions.emplace(extension);
}
private:
void CreateInstance()
{
if (enableValidationLayer) RequireExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
vk::ApplicationInfo appInfo(graphicsAppManager->GetGraphicsApp()->GetAppName().c_str(), graphicsAppManager->GetGraphicsApp()->GetAppVersionAsInt(), ENGINE_NAME, ENGINE_VERSION.intVersion, VK_MAKE_VERSION(1, 1, 0));
std::vector<const char*> extensions = Utils::toCString(requiredExtensions), layers = Debug::GetValidationLayers();
const vk::InstanceCreateInfo createInfo(vk::InstanceCreateFlags(), &appInfo, enableValidationLayer ? layers.size() : 0,
layers.data(), extensions.size(), extensions.data());
instance = vk::createInstance(createInfo);
if (enableValidationLayer) Debug::SetupValidationLayers(instance, vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning /*| vk::DebugReportFlagBitsEXT::eInformation | vk::DebugReportFlagBitsEXT::eDebug*/);
dynamicDispatch.init(instance, &vkGetInstanceProcAddr);
}
void CreateDevice()
{
deviceManager.Init(instance);
device = deviceManager.GetCompatibleDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME });
device->PrepareDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, surface);
dynamicDispatch.init(instance, &vkGetInstanceProcAddr, device->device, &vkGetDeviceProcAddr);
Logger::RENDER->info("Found device: {0}", device->GetDeviceName());;
}
};
}
}

View File

@@ -0,0 +1,97 @@
#pragma once
#include <set>
#include <vulkan/vulkan.hpp>
#define RENDER_DOC
namespace openVulkanoCpp
{
namespace Vulkan
{
namespace Debug
{
std::list<std::string> activeValidationLayerNames = {
"VK_LAYER_LUNARG_assistant_layer",
"VK_LAYER_LUNARG_standard_validation",
//"VK_EXT_debug_marker",
#ifdef RENDER_DOC
"VK_LAYER_RENDERDOC_Capture", // RenderDoc must be open for this layer to work!
#endif
};
static std::set<std::string> GetAvailableValidationLayers()
{
auto layers = vk::enumerateInstanceLayerProperties();
std::set<std::string> layersVector;
std::string layerList = "";
for(const auto& layer : layers)
{
std::string name = layer.layerName;
layersVector.insert(name);
if (layerList.length() > 0) layerList += ", ";
layerList += name;
}
Logger::RENDER->debug("Available Vulkan Validation Layers: {0}", layerList);
return layersVector;
}
static std::vector<const char*> GetValidationLayers()
{
std::set<std::string> availableLayers = GetAvailableValidationLayers();
std::vector<const char*> layers;
std::string layerList = "";
for (const auto& name : activeValidationLayerNames)
{
if (availableLayers.count(name) != 0)
{
layers.push_back(name.c_str());
if (layerList.length() > 0) layerList += ", ";
layerList += name;
}
}
Logger::RENDER->debug("Active Vulkan Validation Layers: {0}", layerList);
return layers;
}
static std::once_flag dispatcherInitFlag;
vk::DispatchLoaderDynamic dispatcher;
vk::DebugReportCallbackEXT msgCallback;
inline VkBool32 ValidationLayerCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode, const char* layerPrefix,
const char* msg, void* pUserData)
{
std::string prefix = "VK_DEBUG:";
spdlog::level::level_enum level = spdlog::level::info;
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) level = spdlog::level::err;
else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) level = spdlog::level::warn;
else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
{
level = spdlog::level::warn;
prefix = "[PERF] " + prefix;
}
else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) level = spdlog::level::debug;
Logger::RENDER->log(level, "{0} [{1}] Code {2}: {3}", prefix, layerPrefix, msgCode, msg);
return false;
}
static void SetupValidationLayers(const vk::Instance& instance, const vk::DebugReportFlagsEXT& flags)
{
Logger::RENDER->info("Setting up Vulkan Validation Layer");
std::call_once(dispatcherInitFlag, [&] { dispatcher.init(instance, &vkGetInstanceProcAddr); });
vk::DebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
dbgCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)ValidationLayerCallback;
dbgCreateInfo.flags = flags;
msgCallback = instance.createDebugReportCallbackEXT(dbgCreateInfo, nullptr, dispatcher);
Logger::RENDER->info("Vulkan Validation Layer setup");
}
static void CloseValidationLayers(const vk::Instance& instance) {
std::call_once(dispatcherInitFlag, [&] { dispatcher.init(instance, &vkGetInstanceProcAddr); });
instance.destroyDebugReportCallbackEXT(msgCallback, nullptr, dispatcher);
}
};
}
}

View File

@@ -0,0 +1,279 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include <set>
#include <functional>
#include <fstream>
namespace openVulkanoCpp
{
namespace Vulkan
{
class DeviceQueueCreateInfoBuilder
{
std::vector<vk::DeviceQueueCreateInfo> createInfos;
std::vector<std::vector<float>> prioritiesVector;
public:
DeviceQueueCreateInfoBuilder() = default;
~DeviceQueueCreateInfoBuilder() = default;
void AddQueueFamily(const uint32_t queueFamilyIndex, const std::vector<float>& priorities)
{
prioritiesVector.push_back(priorities);
createInfos.emplace_back(vk::DeviceQueueCreateFlags(), queueFamilyIndex, priorities.size(), prioritiesVector[prioritiesVector.size()-1].data());
}
void AddQueueFamily(uint32_t queueFamilyIndex, uint32_t count = 1)
{
std::vector<float> priorities;
priorities.resize(count);
std::fill(priorities.begin(), priorities.end(), 0.0f);
AddQueueFamily(queueFamilyIndex, priorities);
}
std::vector<vk::DeviceQueueCreateInfo>& GetDeviceQueueCreateInfos()
{
return createInfos;
}
};
class Device : virtual public ICloseable
{
public:
vk::PhysicalDevice physicalDevice;
std::vector<vk::QueueFamilyProperties> queueFamilyProperties; // Queue family properties
vk::PhysicalDeviceProperties properties; // Physical device properties (for e.g. checking device limits)
vk::PhysicalDeviceFeatures features; // Physical device features (for e.g. checking if a feature is available)
vk::PhysicalDeviceMemoryProperties memoryProperties; // available memory properties
vk::Device device; // Logical device, application's view of the physical device (GPU)
vk::PipelineCache pipelineCache;
vk::CommandPool graphicsCommandPool;
std::set<std::string> supportedExtensions;
vk::Queue graphicsQueue;
struct QueueIndices {
uint32_t graphics = VK_QUEUE_FAMILY_IGNORED;
uint32_t compute = VK_QUEUE_FAMILY_IGNORED;
uint32_t transfer = VK_QUEUE_FAMILY_IGNORED;
uint32_t GetGraphics() const { return graphics; }
uint32_t GetCompute() const { return compute != graphics ? compute : VK_QUEUE_FAMILY_IGNORED; }
uint32_t GetTransfer() const { return (transfer != graphics && transfer != compute) ? transfer : VK_QUEUE_FAMILY_IGNORED; }
} queueIndices;
bool useDebugMarkers;
public:
Device(vk::PhysicalDevice& physicalDevice)
{
this->physicalDevice = physicalDevice;
useDebugMarkers = false;
QueryDevice();
}
std::string GetDeviceName() const
{
return properties.deviceName;
}
void PrepareDevice(const vk::ArrayProxy<const std::string>& requestedExtensions, const vk::SurfaceKHR& surface)
{
queueIndices.graphics = FindBestQueue(vk::QueueFlagBits::eGraphics, surface); // Make sure that the graphics queue supports the surface
BuildDevice(requestedExtensions);
//TODO setup debug marker
pipelineCache = device.createPipelineCache(vk::PipelineCacheCreateInfo());
graphicsQueue = device.getQueue(queueIndices.graphics, 0);
graphicsCommandPool = device.createCommandPool({ vk::CommandPoolCreateFlagBits::eResetCommandBuffer, queueIndices.graphics, });
}
std::set<std::string> GetExtensions() const
{
return supportedExtensions;
}
bool IsExtensionAvailable(const vk::ArrayProxy<const std::string>& extensions) const
{
for(const auto& extension : extensions)
{
if (supportedExtensions.count(extension) == 0) return false;
}
return true;
}
void WaitIdle() const
{ //TODO wait all queues idle
graphicsQueue.waitIdle();
device.waitIdle();
}
vk::CommandBuffer CreateCommandBuffer(vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary) const
{
const vk::CommandBufferAllocateInfo cmdBufferAllocInfo(graphicsCommandPool, level, 1);
return device.allocateCommandBuffers(cmdBufferAllocInfo)[0];
}
void FlushCommandBuffer(vk::CommandBuffer& cmdBuffer) const
{
graphicsQueue.submit(vk::SubmitInfo{ 0, nullptr, nullptr, 1, &cmdBuffer }, vk::Fence());
WaitIdle();
}
void ExecuteNow(const std::function<void(const vk::CommandBuffer& commandBuffer)>& function) const
{
vk::CommandBuffer commandBuffer = CreateCommandBuffer();
commandBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
function(commandBuffer);
commandBuffer.end();
FlushCommandBuffer(commandBuffer);
device.freeCommandBuffers(graphicsCommandPool, commandBuffer);
}
vk::ShaderModule CreateShaderModule(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 shader file!");
const size_t fileSize = static_cast<size_t>(file.tellg());
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
vk::ShaderModuleCreateInfo smci = { {}, buffer.size(), reinterpret_cast<const uint32_t*>(buffer.data()) };
return CreateShaderModule(smci);
}
vk::ShaderModule CreateShaderModule(vk::ShaderModuleCreateInfo& createInfo) const
{
return device.createShaderModule(createInfo);
}
private:
void QueryDevice()
{
// Query device features
queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
properties = physicalDevice.getProperties();
features = physicalDevice.getFeatures();
for (auto& ext : physicalDevice.enumerateDeviceExtensionProperties()) { supportedExtensions.insert(ext.extensionName); }
// Query device memory properties
memoryProperties = physicalDevice.getMemoryProperties();
queueIndices.graphics = FindBestQueue(vk::QueueFlagBits::eGraphics);
queueIndices.compute = FindBestQueue(vk::QueueFlagBits::eCompute);
queueIndices.transfer = FindBestQueue(vk::QueueFlagBits::eTransfer);
}
void BuildDevice(const vk::ArrayProxy<const std::string>& requestedExtensions)
{
vk::DeviceCreateInfo deviceCreateInfo;
deviceCreateInfo.pEnabledFeatures = &features; //TODO add option to disable not needed features
DeviceQueueCreateInfoBuilder deviceQueueCreateInfoBuilder;
deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetGraphics(), queueFamilyProperties[queueIndices.GetGraphics()].queueCount);
if (queueIndices.GetCompute() != VK_QUEUE_FAMILY_IGNORED)
deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetCompute(), queueFamilyProperties[queueIndices.GetCompute()].queueCount);
if (queueIndices.GetTransfer() != VK_QUEUE_FAMILY_IGNORED)
deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetTransfer(), queueFamilyProperties[queueIndices.GetTransfer()].queueCount);
const std::vector<vk::DeviceQueueCreateInfo> deviceQueueCreateInfos = deviceQueueCreateInfoBuilder.GetDeviceQueueCreateInfos();
deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(deviceQueueCreateInfos.size());
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfos.data();
std::vector<const char*> enabledExtensions;
for (const auto& extension : requestedExtensions)
{
enabledExtensions.push_back(extension.c_str());
}
#ifdef DEBUG
if (IsExtensionAvailable({ VK_EXT_DEBUG_MARKER_EXTENSION_NAME }))
{ // Enable debug marker extension if available
enabledExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
useDebugMarkers = true;
}
#endif
deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(enabledExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
device = physicalDevice.createDevice(deviceCreateInfo);
}
uint32_t FindBestQueue(const vk::QueueFlags& desiredFlags, const vk::SurfaceKHR& surface = nullptr) const
{
uint32_t best = VK_QUEUE_FAMILY_IGNORED;
VkQueueFlags bestExtraFlagsCount = VK_QUEUE_FLAG_BITS_MAX_ENUM;
for (size_t i = 0; i < queueFamilyProperties.size(); i++)
{
vk::QueueFlags flags = queueFamilyProperties[i].queueFlags;
if (!(flags & desiredFlags)) continue; // Skip queue without desired flags
if (surface && VK_FALSE == physicalDevice.getSurfaceSupportKHR(i, surface)) continue;
const VkQueueFlags currentExtraFlags = (flags & ~desiredFlags).operator VkQueueFlags();
if (0 == currentExtraFlags) return i; // return exact match
if (best == VK_QUEUE_FAMILY_IGNORED || currentExtraFlags < bestExtraFlagsCount)
{
best = i;
bestExtraFlagsCount = currentExtraFlags;
}
}
return best;
}
public:
/**
* \brief Vulkan does not require does not define a depth buffer format that must be supported. This method checks for the first supported format from an given array of formats.
* \param depthFormats Array of depth formats
* \return The first format supported as a depth buffer
* \throws If no depth buffer format is supported
*/
vk::Format GetSupportedDepthFormat(const std::vector<vk::Format>& depthFormats = { vk::Format::eD32SfloatS8Uint, vk::Format::eD32Sfloat, vk::Format::eD24UnormS8Uint, vk::Format::eD16UnormS8Uint, vk::Format::eD16Unorm }) const
{
for (auto& format : depthFormats)
{
vk::FormatProperties formatProps;
formatProps = physicalDevice.getFormatProperties(format);
if (formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment)
{
return format;
}
}
throw std::runtime_error("No supported depth format");
}
vk::Bool32 GetMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties, uint32_t* typeIndex) const
{
for (uint32_t i = 0; i < 32; i++)
{
if ((typeBits & 1) == 1)
{
if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
*typeIndex = i;
return VK_TRUE;
}
}
typeBits >>= 1;
}
return VK_FALSE;
}
uint32_t GetMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties) const
{
uint32_t result = 0;
if (VK_FALSE == GetMemoryType(typeBits, properties, &result))
{
throw std::runtime_error("Unable to find memory type " + to_string(properties));
}
return result;
}
void Close() override
{
device.destroyCommandPool(graphicsCommandPool);
//TODO fill
}
};
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <stdexcept>
#include <vector>
#include "../Base/ICloseable.hpp"
#include "Device.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class DeviceManager : virtual public ICloseable
{
std::vector<Device> devices;
public:
void Init(const vk::Instance& instance)
{
devices = std::vector<Device>();
for (auto& physicalDevice : instance.enumeratePhysicalDevices())
{
devices.emplace_back(physicalDevice);
}
}
Device* GetCompatibleDevice(const std::vector<std::string>& deviceExtensions)
{
for (auto& device : devices)
{
if (device.IsExtensionAvailable(deviceExtensions)) return &device;
}
throw std::runtime_error("No device with required extensions found!");
}
void Close() override
{
for (auto& device : devices)
{
device.Close();
}
devices.clear();
}
};
}
}

View File

@@ -0,0 +1,76 @@
#include "FrameBuffer.hpp"
#include "RenderPass.hpp"
void openVulkanoCpp::Vulkan::FrameBuffer::Init(Device* device, vk::Extent3D size, bool useDepthBuffer)
{
this->size = size;
this->device = device;
this->useDepthBuffer = useDepthBuffer;
colorFormat = FindColorFormat();
if (useDepthBuffer)
{
depthBufferFormat = FindDepthFormat();
CreateDepthStencil();
}
}
void openVulkanoCpp::Vulkan::FrameBuffer::InitRenderPass(RenderPass* renderPass)
{
if (!device) throw std::
runtime_error("The frame buffer needs to be initialized before binding it to a render pass");
this->renderPass = renderPass;
CreateFrameBuffer();
}
void openVulkanoCpp::Vulkan::FrameBuffer::Resize(vk::Extent3D size)
{
this->size = size;
DestroyFrameBuffer();
if (depthBuffer) depthBuffer.Close();
if (useDepthBuffer) CreateDepthStencil();
CreateFrameBuffer();
renderPass->UpdateBeginInfo();
}
void openVulkanoCpp::Vulkan::FrameBuffer::CreateDepthStencil()
{
vk::ImageCreateInfo depthStencilCreateInfo({}, vk::ImageType::e2D, depthBufferFormat,
size, 1, 1);
depthStencilCreateInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::
eTransferSrc;
const vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
const vk::ImageViewCreateInfo depthStencilViewCreateInfo({}, {}, vk::ImageViewType::e2D, depthBufferFormat,
{}, vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1));
depthBuffer.Init(device, depthStencilCreateInfo, depthStencilViewCreateInfo);
device->ExecuteNow([&](auto commandBuffer)
{
depthBuffer.SetLayout(commandBuffer, aspectMask, vk::ImageLayout::eDepthStencilAttachmentOptimal);
});
}
void openVulkanoCpp::Vulkan::FrameBuffer::CreateFrameBuffer()
{
vk::ImageView attachments[2]; // First attachment is the color buffer, second (optional) the depth buffer
if (useDepthBuffer) attachments[1] = depthBuffer.view; //Depth buffer is the same for all frame buffers
const vk::FramebufferCreateInfo fbCreateInfo({}, renderPass->renderPass, useDepthBuffer ? 2 : 1, attachments, size.width,
size.height, 1);
auto images = GetImages();
frameBuffers.resize(images.size());
for (uint32_t i = 0; i < frameBuffers.size(); i++)
{
attachments[0] = images[i]->GetView();
frameBuffers[i] = device->device.createFramebuffer(fbCreateInfo);
}
}
void openVulkanoCpp::Vulkan::FrameBuffer::DestroyFrameBuffer()
{
for (const auto frameBuffer : frameBuffers)
{
device->device.destroyFramebuffer(frameBuffer);
}
frameBuffers.clear();
}

View File

@@ -0,0 +1,107 @@
#pragma once
#include <cstdint>
#include <vulkan/vulkan.hpp>
#include "../Base/ICloseable.hpp"
#include "Image.hpp"
#include "Device.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class RenderPass;
class FrameBuffer : ICloseable
{
Image depthBuffer;
std::vector<vk::Framebuffer> frameBuffers;
vk::Format depthBufferFormat = vk::Format::eUndefined, colorFormat = vk::Format::eUndefined;
vk::Extent3D size;
RenderPass* renderPass;
bool useDepthBuffer;
Device* device = nullptr;
protected:
uint32_t currentFrameBufferId = 0;
FrameBuffer() = default;
virtual ~FrameBuffer()
{
if (device) FrameBuffer::Close();
}
void Init(Device* device, vk::Extent3D size, bool useDepthBuffer = true);
void SetCurrentFrameId(uint32_t id)
{
currentFrameBufferId = id;
}
uint32_t GetCurrentFrameId() const
{
return currentFrameBufferId;
}
public:
void InitRenderPass(RenderPass* renderPass);
protected:
void Resize(vk::Extent3D size);
void Close() override
{
DestroyFrameBuffer();
if(depthBuffer) depthBuffer.Close();
device = nullptr;
}
protected:
virtual void CreateDepthStencil();
virtual void CreateFrameBuffer();
void DestroyFrameBuffer();
virtual vk::Format FindColorFormat() = 0;
virtual vk::Format FindDepthFormat()
{
return device->GetSupportedDepthFormat();
}
public:
virtual vk::Format GetColorFormat()
{
return colorFormat;
}
virtual vk::Format GetDepthFormat()
{
return depthBufferFormat;
}
virtual std::vector<IImage*> GetImages() = 0;
bool UseDepthBuffer() const
{
return useDepthBuffer;
}
vk::Extent3D GetSize3D() const
{
return size;
}
vk::Extent2D GetSize2D() const
{
return { size.width, size.height };
}
vk::Framebuffer& GetCurrentFrameBuffer()
{
return frameBuffers[currentFrameBufferId];
}
};
}
}

View File

@@ -0,0 +1,98 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "Buffer.hpp"
#include "VulkanUtils.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class IImage
{
public:
virtual ~IImage() = default;
virtual vk::Image GetImage() = 0;
virtual vk::ImageView GetView() = 0;
};
struct Image : public Buffer, virtual public IImage
{
vk::Image image;
vk::Extent3D extent;
vk::ImageView view;
vk::Sampler sampler;
vk::Format format = vk::Format::eUndefined;
/**
* \brief
* \param device
* \param imageCreateInfo
* \param imageViewCreateInfo The image will be set automatically after it's creation
* \param memoryPropertyFlags
*/
void Init(const Device* device, const vk::ImageCreateInfo& imageCreateInfo, vk::ImageViewCreateInfo imageViewCreateInfo, const vk::MemoryPropertyFlags& memoryPropertyFlags = vk::MemoryPropertyFlagBits::eDeviceLocal)
{
this->device = device->device;
image = device->device.createImage(imageCreateInfo);
format = imageCreateInfo.format;
extent = imageCreateInfo.extent;
const vk::MemoryRequirements memRequirements = device->device.getImageMemoryRequirements(image);
size = allocSize = memRequirements.size;
const vk::MemoryAllocateInfo memAllocInfo = { allocSize, device->GetMemoryType(memRequirements.memoryTypeBits, memoryPropertyFlags) };
memory = device->device.allocateMemory(memAllocInfo);
device->device.bindImageMemory(image, memory, 0);
imageViewCreateInfo.image = image;
view = device->device.createImageView(imageViewCreateInfo);
}
void SetLayout(vk::CommandBuffer& cmdBuffer, vk::ImageSubresourceRange subResourceRange, vk::ImageLayout newLayout, vk::ImageLayout oldLayout = vk::ImageLayout::eUndefined) const
{
const vk::ImageMemoryBarrier imgMemBarrier(VulkanUtils::GetAccessFlagsForLayout(oldLayout), VulkanUtils::GetAccessFlagsForLayout(newLayout), oldLayout,
newLayout, 0, 0, image, subResourceRange);
cmdBuffer.pipelineBarrier(VulkanUtils::GetPipelineStageForLayout(oldLayout), VulkanUtils::GetPipelineStageForLayout(newLayout),
{}, nullptr, nullptr, imgMemBarrier);
}
void SetLayout(vk::CommandBuffer& cmdBuffer, vk::ImageAspectFlags aspectMask, vk::ImageLayout newLayout, vk::ImageLayout oldLayout = vk::ImageLayout::eUndefined) const
{
SetLayout(cmdBuffer, vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1), newLayout, oldLayout);
}
void Close() override
{
if(sampler)
{
device.destroySampler(sampler);
sampler = vk::Sampler();
}
if(view)
{
device.destroyImageView(view);
view = vk::ImageView();
}
if(image)
{
device.destroyImage(image);
image = vk::Image();
}
Buffer::Close();
}
operator bool() const { return image.operator bool(); }
vk::Image GetImage() override
{
return image;
}
vk::ImageView GetView() override
{
return view;
}
};
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "../Base/ICloseable.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct Pipeline : virtual ICloseable
{
vk::Device device;
vk::DescriptorSetLayout descriptorSetLayout;
vk::PipelineLayout pipelineLayout;
vk::DescriptorPool descriptorPool;
void Init(vk::Device& device)
{
this->device = device;
CreatePipelineLayout();
}
void Close() override
{
device.destroyPipelineLayout(pipelineLayout);
device.destroyDescriptorSetLayout(descriptorSetLayout);
}
private:
void CreatePipelineLayout()
{
vk::PushConstantRange camPushConstantDesc = { vk::ShaderStageFlagBits::eVertex, 0, 64 };
vk::DescriptorSetLayoutBinding nodeLayoutBinding = { 0, vk::DescriptorType::eUniformBufferDynamic, 1, vk::ShaderStageFlagBits::eVertex };
std::array<vk::DescriptorSetLayoutBinding, 1> layoutBindings = { nodeLayoutBinding };
vk::DescriptorSetLayoutCreateInfo dslci = { {}, layoutBindings.size(), layoutBindings.data() };
descriptorSetLayout = device.createDescriptorSetLayout(dslci);
vk::PipelineLayoutCreateInfo plci = { {}, 1, &descriptorSetLayout, 1, &camPushConstantDesc };
pipelineLayout = this->device.createPipelineLayout(plci);
}
};
}
}

View File

@@ -0,0 +1,146 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "Device.hpp"
#include "FrameBuffer.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class RenderPass : virtual public ICloseable
{
protected:
vk::Device device;
vk::RenderPassBeginInfo beginInfo;
vk::ClearColorValue clearColor;
vk::ClearDepthStencilValue clearDepth;
std::vector<vk::ClearValue> clearValues;
FrameBuffer* frameBuffer = nullptr;
bool useClearColor = false, useClearDepth = true;
public:
vk::RenderPass renderPass;
RenderPass() = default;
~RenderPass()
{
if (frameBuffer) RenderPass::Close();
}
void Init(Device* device, FrameBuffer* frameBuffer)
{
this->device = device->device;
this->frameBuffer = frameBuffer;
CreateRenderPass();
frameBuffer->InitRenderPass(this);
SetClearColor();
}
void Close() override
{
device.destroy(renderPass);
frameBuffer = nullptr;
clearValues.clear();
}
void SetClearColor(vk::ClearColorValue clearColor = vk::ClearColorValue(std::array<float, 4>{ 0.39f, 0.58f, 0.93f, 1.0f }))
{
this->clearColor = clearColor;
useClearColor = true;
UpdateBeginInfo();
}
void DisableClearColor()
{
useClearColor = false;
UpdateBeginInfo();
}
void SetClearDepth(vk::ClearDepthStencilValue clearDepth = vk::ClearDepthStencilValue(1, 0))
{
this->clearDepth = clearDepth;
useClearDepth = true;
UpdateBeginInfo();
}
void DisableClearDepth()
{
useClearDepth = false;
}
virtual void UpdateBeginInfo()
{ //TODO allow to control the render rect size
clearValues.clear();
if (useClearColor) clearValues.emplace_back(clearColor);
if(frameBuffer->UseDepthBuffer() && useClearDepth) clearValues.emplace_back(clearDepth);
beginInfo = vk::RenderPassBeginInfo(renderPass, vk::Framebuffer(),
vk::Rect2D(vk::Offset2D(), frameBuffer->GetSize2D()),
clearValues.size(), clearValues.data());
}
virtual void Begin(vk::CommandBuffer& commandBuffer)
{
beginInfo.framebuffer = frameBuffer->GetCurrentFrameBuffer();
commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eSecondaryCommandBuffers);
}
virtual void End(vk::CommandBuffer& commandBuffer)
{
commandBuffer.endRenderPass();
}
FrameBuffer* GetFrameBuffer() const
{
return frameBuffer;
}
protected:
virtual void CreateRenderPass()
{
std::vector<vk::AttachmentDescription> attachments;
attachments.reserve(frameBuffer->UseDepthBuffer() ? 2 : 1);
// Color attachment
attachments.emplace_back(vk::AttachmentDescriptionFlags(), frameBuffer->GetColorFormat(), vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eLoad,
vk::AttachmentStoreOp::eStore, vk::ImageLayout::eUndefined, vk::ImageLayout::ePresentSrcKHR);
vk::AttachmentReference* depthReference = nullptr;;
if (frameBuffer->UseDepthBuffer())
{ // Depth attachment
attachments.emplace_back(vk::AttachmentDescriptionFlags(), frameBuffer->GetDepthFormat(), vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare, vk::AttachmentLoadOp::eClear,
vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal);
depthReference = new vk::AttachmentReference(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
std::vector<vk::AttachmentReference> colorAttachmentReferences;
colorAttachmentReferences.emplace_back(0, vk::ImageLayout::eColorAttachmentOptimal);
std::vector<vk::SubpassDescription> subPasses;
subPasses.emplace_back(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 0, nullptr,
colorAttachmentReferences.size(), colorAttachmentReferences.data(), nullptr, depthReference, 0, nullptr);
std::vector<vk::SubpassDependency> subPassDependencies;
subPassDependencies.emplace_back(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
vk::AccessFlagBits::eMemoryRead, vk::DependencyFlagBits::eByRegion);
subPassDependencies.emplace_back(VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eBottomOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlagBits::eMemoryRead,
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
vk::DependencyFlagBits::eByRegion);
const vk::RenderPassCreateInfo createInfo(vk::RenderPassCreateFlags(), attachments.size(), attachments.data(),
subPasses.size(), subPasses.data(), subPassDependencies.size(), subPassDependencies.data());
CreateRenderPass(createInfo);
delete depthReference;
}
void CreateRenderPass(const vk::RenderPassCreateInfo& renderPassCreateInfo)
{
renderPass = device.createRenderPass(renderPassCreateInfo);
}
};
}
}

View File

@@ -0,0 +1,220 @@
#pragma once
#include <stdexcept>
#include <iostream>
#include <fstream>
#include "../Base/Render/IRenderer.hpp"
#include "../Base/UI/IWindow.hpp"
#include "../Base/Logger.hpp"
#include "Context.hpp"
#include "Resources/ResourceManager.hpp"
#include "../Data/ReadOnlyAtomicArrayQueue.hpp"
#include "CommandHelper.hpp"
#include "../Base/EngineConfiguration.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct WaitSemaphores
{
std::vector<vk::Semaphore> renderReady, renderComplete;
};
class Renderer : public IRenderer
{
Context context;
std::shared_ptr<spdlog::logger> logger;
std::vector<WaitSemaphores> waitSemaphores;
Scene::Scene* scene = nullptr;
std::ofstream perfFile;
ResourceManager resourceManager;
uint32_t currentImageId = -1;
std::vector<std::thread> threadPool;
std::vector<std::vector<CommandHelper>> commands;
std::vector<std::vector<vk::CommandBuffer>> submitBuffers;
VulkanShader* shader;
public:
Renderer() = default;
virtual ~Renderer() = default;
void Init(IGraphicsAppManager* graphicsAppManager, IWindow* window) override
{
logger = Logger::RENDER;
logger->info("Initializing Vulkan renderer ...");
IVulkanWindow* vulkanWindow = window->GetVulkanWindow();
if (!vulkanWindow)
{
logger->error("The provided window is not compatible with Vulkan.");
throw std::runtime_error("The provided window is not compatible with Vulkan.");
}
context.Init(graphicsAppManager, vulkanWindow);
for (int i = 0; i < context.swapChain.GetImageCount(); i++)
{
waitSemaphores.emplace_back();
waitSemaphores[i].renderComplete.push_back(context.device->device.createSemaphore({}));
waitSemaphores[i].renderReady.resize(2);
}
resourceManager.Init(&context, context.swapChain.GetImageCount());
threadPool.resize(EngineConfiguration::GetEngineConfiguration()->GetNumThreads() - 1);
//Setup cmd pools and buffers
commands.resize(threadPool.size() + 2); // One extra cmd object for the primary buffer and one for the main thread
for(uint32_t i = 0; i < commands.size(); i++)
{
commands[i] = std::vector<CommandHelper>(context.swapChain.GetImageCount());
for(size_t j = 0; j < commands[i].size(); j++)
{
commands[i][j].Init(context.device->device, context.device->queueIndices.GetGraphics(),
(i == commands.size() - 1) ? vk::CommandBufferLevel::ePrimary : vk::CommandBufferLevel::eSecondary);
}
}
submitBuffers.resize(context.swapChain.GetImageCount());
for(uint32_t i = 0; i < submitBuffers.size(); i++)
{
submitBuffers[i].resize(commands.size() - 1);
for (size_t j = 0; j < submitBuffers[i].size(); j++)
{
submitBuffers[i][j] = commands[j][i].cmdBuffer;
}
}
shader = resourceManager.CreateShader(scene->shader);
perfFile.open("perf.csv");
perfFile << "sep=,\ntotal,fps\n";
logger->info("Vulkan renderer initialized");
}
void Tick() override
{
currentImageId = context.swapChain.AcquireNextImage();
auto tickStart= std::chrono::high_resolution_clock::now();
Render();
// Perf logging
auto tickDone = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::microseconds>(tickDone - tickStart).count();
perfFile << time << ',' << 1000000000.0 / time << '\n';
}
void Close() override
{
perfFile.close();
//context.Close();
}
std::string GetMainRenderDeviceName() override
{
return (context.device) ? context.device->GetDeviceName() : "Unknown";
}
void Resize(const uint32_t newWidth, const uint32_t newHeight) override
{
context.Resize(newWidth, newHeight);
resourceManager.Resize();
}
void SetScene(Scene::Scene* scene) override
{
this->scene = scene;
}
Scene::Scene* GetScene() override
{
return scene;
}
CommandHelper* GetCommandData(uint32_t poolId)
{
return &commands[poolId][currentImageId];
}
static void RunThread(Renderer* renderer, Data::ReadOnlyAtomicArrayQueue<Scene::Drawable*>* jobQueue, uint32_t id)
{
renderer->RecordSecondaryBuffer(jobQueue, id);
}
void StartThreads(Data::ReadOnlyAtomicArrayQueue<Scene::Drawable*>* jobQueue)
{
for(uint32_t i = 0; i < threadPool.size(); i++)
{
threadPool[i] = std::thread(RunThread, this, jobQueue, i);
}
}
void RecordPrimaryBuffer()
{
CommandHelper* cmdHelper = GetCommandData(commands.size() - 1);
cmdHelper->Reset();
cmdHelper->cmdBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
context.swapChainRenderPass.Begin(cmdHelper->cmdBuffer);
}
void Submit()
{
for (auto& thread : threadPool) { thread.join(); } // Wait till everything is recorded
CommandHelper* cmdHelper = GetCommandData(commands.size() - 1);
cmdHelper->cmdBuffer.executeCommands(submitBuffers[currentImageId].size(), submitBuffers[currentImageId].data());
context.swapChainRenderPass.End(cmdHelper->cmdBuffer);
cmdHelper->cmdBuffer.end();
std::array<vk::PipelineStageFlags, 2> stateFlags = { vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput), vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput) };
waitSemaphores[currentImageId].renderReady[0] = resourceManager.EndFrame();
waitSemaphores[currentImageId].renderReady[1] = context.swapChain.imageAvailableSemaphore;
vk::SubmitInfo si = vk::SubmitInfo(
waitSemaphores[currentImageId].renderReady.size(), waitSemaphores[currentImageId].renderReady.data(), stateFlags.data(),
1, &cmdHelper->cmdBuffer,
waitSemaphores[currentImageId].renderComplete.size(), waitSemaphores[currentImageId].renderComplete.data());
context.device->graphicsQueue.submit(1, &si, context.swapChain.GetCurrentSubmitFence());
context.swapChain.Present(context.device->graphicsQueue, waitSemaphores[currentImageId].renderComplete);
}
void Render()
{
resourceManager.StartFrame(currentImageId);
Data::ReadOnlyAtomicArrayQueue<Scene::Drawable*> jobQueue(scene->shapeList);
StartThreads(&jobQueue);
RecordPrimaryBuffer();
RecordSecondaryBuffer(&jobQueue, threadPool.size());
Submit();
}
void RecordSecondaryBuffer(Data::ReadOnlyAtomicArrayQueue<Scene::Drawable*>* jobQueue, uint32_t poolId)
{
Scene::Geometry* lastGeo = nullptr;
Scene::Node* lastNode = nullptr;
CommandHelper* cmdHelper = GetCommandData(poolId);
cmdHelper->Reset();
vk::CommandBufferInheritanceInfo inheritance = { context.swapChainRenderPass.renderPass, 0, context.swapChainRenderPass.GetFrameBuffer()->GetCurrentFrameBuffer() };
cmdHelper->cmdBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit | vk::CommandBufferUsageFlagBits::eRenderPassContinue, &inheritance });
shader->Record(cmdHelper->cmdBuffer, currentImageId);
cmdHelper->cmdBuffer.pushConstants(context.pipeline.pipelineLayout, vk::ShaderStageFlagBits::eVertex, 0, 64, scene->GetCamera()->GetViewProjectionMatrixPointer());
Scene::Drawable** drawablePointer;
while((drawablePointer = jobQueue->Pop()) != nullptr)
{
Scene::Drawable* drawable = *drawablePointer;
Scene::Geometry* mesh = drawable->mesh;
if (mesh != lastGeo)
{
if (!mesh->renderGeo) resourceManager.PrepareGeometry(mesh);
dynamic_cast<VulkanGeometry*>(mesh->renderGeo)->Record(cmdHelper->cmdBuffer, currentImageId);
lastGeo = mesh;
}
for(Scene::Node* node : drawable->nodes)
{
if (node != lastNode)
{
if (!node->renderNode) resourceManager.PrepareNode(node);
dynamic_cast<VulkanNode*>(node->renderNode)->Record(cmdHelper->cmdBuffer, currentImageId);
lastNode = node;
}
cmdHelper->cmdBuffer.drawIndexed(mesh->GetIndexCount(), 1, 0, 0, 0);
}
}
cmdHelper->cmdBuffer.end();
}
};
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "../Scene/VulkanShader.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct VulkanShader;
class IShaderOwner
{
public:
virtual void RemoveShader(VulkanShader* shader) = 0;
};
}
}

View File

@@ -0,0 +1,93 @@
#pragma once
#include <vulkan/vulkan.hpp>
namespace openVulkanoCpp
{
namespace Vulkan
{
struct MemoryAllocation
{
vk::DeviceMemory memory;
size_t used, size;
uint32_t type;
MemoryAllocation(size_t size, uint32_t type)
{
memory = nullptr;
this->size = size;
used = 0;
this->type = type;
}
size_t FreeSpace() const
{
return size - used;
}
};
struct ManagedBuffer
{
MemoryAllocation* allocation;
vk::DeviceSize offset, size;
vk::Buffer buffer;
vk::BufferUsageFlags usage;
vk::MemoryPropertyFlags properties;
vk::Device device;
void* mapped = nullptr;
bool IsLast()
{
return (offset + size == allocation->used);
}
/**
* \brief Maps the buffer into the memory of the host.
* \tparam T The type of the buffers data.
* \param offset The offset from where to map the buffer.
* \param size The size to be mapped. VK_WHOLE_SIZE to map the whole buffer.
* \return The pointer to the mapped buffer.
*/
template <typename T = void>
T* Map(size_t offset = 0, vk::DeviceSize size = VK_WHOLE_SIZE)
{
if (size == VK_WHOLE_SIZE) size = this->size;
mapped = device.mapMemory(allocation->memory, this->offset + offset, size, vk::MemoryMapFlags());
return static_cast<T*>(mapped);
}
/**
* \brief Un-maps the buffer from the host.
*/
void UnMap()
{
device.unmapMemory(allocation->memory);
mapped = nullptr;
}
void Copy(void* data) const
{
if(mapped)
{
memcpy(mapped, data, size);
}
else
{
void* dataMapped = device.mapMemory(allocation->memory, offset, size);
memcpy(dataMapped, data, size);
device.unmapMemory(allocation->memory);
}
}
void Copy(void* data, uint32_t size, uint32_t offset) const
{
if(mapped) memcpy(static_cast<char*>(mapped) + offset, data, size);
else
{
void* dataMapped = device.mapMemory(allocation->memory, this->offset + offset, size);
memcpy(dataMapped, data, size);
device.unmapMemory(allocation->memory);
}
}
};
}
}

View File

@@ -0,0 +1,258 @@
#pragma once
#include "vulkan/vulkan.hpp"
#include "../Device.hpp"
#include "../../Base/ICloseable.hpp"
#include "../Scene/VulkanShader.hpp"
#include "IShaderOwner.hpp"
#include "../Scene/VulkanGeometry.hpp"
#include "ManagedResource.hpp"
#include "../Scene/VulkanNode.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class ResourceManager : virtual public ICloseable, virtual public IShaderOwner
{
Context* context;
vk::Device device = nullptr;
vk::Queue transferQueue = nullptr;
vk::CommandPool* cmdPools = nullptr;
vk::CommandBuffer* cmdBuffers = nullptr;
vk::Semaphore* semaphores = nullptr;
std::vector<MemoryAllocation*> allocations;
std::vector<VulkanShader*> shaders;
MemoryAllocation* lastAllocation = nullptr;
std::mutex mutex;
vk::DeviceSize uniformBufferAlignment;
std::vector<std::vector<ManagedBuffer*>> toFree;
std::vector<ManagedBuffer*> recycleBuffers;
int buffers = -1, currentBuffer = -1;
public:
ResourceManager() = default;
virtual ~ResourceManager() { if (device) ResourceManager::Close(); }
void Init(Context* context, int buffers = 2)
{
this->context = context;
this->device = context->device->device;
this->buffers = buffers;
uniformBufferAlignment = context->device->properties.limits.minUniformBufferOffsetAlignment;
cmdPools = new vk::CommandPool[buffers];
cmdBuffers = new vk::CommandBuffer[buffers];
semaphores = new vk::Semaphore[buffers];
for (int i = 0; i < buffers; i++)
{
cmdPools[i] = this->device.createCommandPool({ {}, context->device->queueIndices.transfer });
cmdBuffers[i] = this->device.allocateCommandBuffers({ cmdPools[i], vk::CommandBufferLevel::ePrimary, 1 })[0];
semaphores[i] = this->device.createSemaphore({});
}
toFree.resize(buffers);
transferQueue = this->device.getQueue(context->device->queueIndices.transfer, 0);
}
void Close() override
{
transferQueue.waitIdle();
for (int i = 0; i < buffers; i++)
{
device.freeCommandBuffers(cmdPools[i], 1, &cmdBuffers[i]);
device.destroyCommandPool(cmdPools[0]);
}
for (auto shader : shaders)
{
shader->Close();
}
cmdBuffers = nullptr;
cmdPools = nullptr;
device = nullptr;
}
void StartFrame(uint64_t frameId)
{
currentBuffer = frameId;
FreeBuffers();
device.resetCommandPool(cmdPools[currentBuffer], {});
cmdBuffers[currentBuffer].begin({ vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
}
vk::Semaphore EndFrame()
{
cmdBuffers[currentBuffer].end();
vk::SubmitInfo si = { 0, nullptr, nullptr, 1, &cmdBuffers[currentBuffer], 1, &semaphores[currentBuffer] };
transferQueue.submit(1, &si, vk::Fence());
return semaphores[currentBuffer];
}
void Resize()
{
for (auto shader : shaders)
{
Scene::Shader* s = shader->shader;
shader->Close();
shader->Init(context, s, this);
}
}
void PrepareGeometry(Scene::Geometry* geometry)
{
mutex.lock();
if(!geometry->renderGeo)
{
VulkanGeometry* vkGeometry = new VulkanGeometry();
ManagedBuffer* vertexBuffer = CreateDeviceOnlyBufferWithData(sizeof(Vertex) * geometry->GetVertexCount(), vk::BufferUsageFlagBits::eVertexBuffer, geometry->GetVertices());
ManagedBuffer* indexBuffer = CreateDeviceOnlyBufferWithData(Utils::EnumAsInt(geometry->indexType) * geometry->GetIndexCount(), vk::BufferUsageFlagBits::eIndexBuffer, geometry->GetIndices());
vkGeometry->Init(geometry, vertexBuffer->buffer, indexBuffer->buffer);
geometry->renderGeo = vkGeometry;
}
mutex.unlock();
}
void PrepareMaterial(Scene::Material* material)
{
mutex.lock();
if(!material->shader->renderShader)
{
material->shader->renderShader = CreateShader(material->shader);
}
mutex.unlock();
}
void PrepareNode(Scene::Node* node)
{
mutex.lock();
if (!node->renderNode)
{
UniformBuffer* uBuffer = new UniformBuffer();
ManagedBuffer* buffer;
VulkanNode* vkNode;
const vk::DeviceSize allocSize = aligned(sizeof(glm::mat4x4), uniformBufferAlignment);
if (node->GetUpdateFrequency() != Scene::UpdateFrequency::Never)
{
vkNode = new VulkanNodeDynamic();
uint32_t imgs = context->swapChain.GetImageCount();
buffer = CreateBuffer(imgs * allocSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
buffer->Map();
}
else
{
vkNode = new VulkanNode();
buffer = CreateDeviceOnlyBufferWithData(sizeof(glm::mat4), vk::BufferUsageFlagBits::eUniformBuffer, &node->worldMat);
}
uBuffer->Init(buffer, allocSize, &context->pipeline.descriptorSetLayout, context->pipeline.pipelineLayout);
vkNode->Init(node, uBuffer);
node->renderNode = vkNode;
}
mutex.unlock();
}
void RemoveShader(VulkanShader* shader) override
{
Utils::Remove(shaders, shader);
}
protected: // Allocation management
static vk::DeviceSize aligned(vk::DeviceSize size, vk::DeviceSize byteAlignment)
{
return (size + byteAlignment - 1) & ~(byteAlignment - 1);
}
void FreeBuffer(ManagedBuffer* buffer)
{
toFree[currentBuffer].push_back(buffer);
}
void DoFreeBuffer(ManagedBuffer* buffer)
{
if (buffer->IsLast())
{
device.destroyBuffer(buffer->buffer);
buffer->allocation->used -= buffer->size;
}
else
{
recycleBuffers.push_back(buffer);
}
}
void FreeBuffers()
{
for (auto& i : toFree[currentBuffer])
{
DoFreeBuffer(i);
}
toFree[currentBuffer].clear();
}
ManagedBuffer* CreateDeviceOnlyBufferWithData(vk::DeviceSize size, vk::BufferUsageFlagBits usage, void* data)
{
ManagedBuffer* target = CreateBuffer(size, usage | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal);
ManagedBuffer* uploadBuffer = CreateBuffer(size, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
uploadBuffer->Copy(data, size, 0);
RecordCopy(uploadBuffer->buffer, target->buffer, size);
FreeBuffer(uploadBuffer);
return target;
}
void RecordCopy(vk::Buffer src, vk::Buffer dest, vk::DeviceSize size) const
{
vk::BufferCopy copyRegion = { 0, 0, size };
cmdBuffers[currentBuffer].copyBuffer(src, dest, 1, &copyRegion);
}
ManagedBuffer* CreateBuffer(vk::DeviceSize size, const vk::BufferUsageFlags& usage, const vk::MemoryPropertyFlags& properties)
{
size = aligned(size, uniformBufferAlignment);
const vk::BufferCreateInfo bufferCreateInfo = { {}, size, usage, vk::SharingMode::eExclusive };
vk::Buffer buffer = device.createBuffer(bufferCreateInfo);
const vk::MemoryRequirements memoryRequirements = device.getBufferMemoryRequirements(buffer);
uint32_t memtype = context->device->GetMemoryType(memoryRequirements.memoryTypeBits, properties);
if (memoryRequirements.size != size) Logger::DATA->warn("Memory Requirement Size ({0}) != Size ({1})", memoryRequirements.size, size);
MemoryAllocation* allocation = GetFreeMemoryAllocation(memoryRequirements.size, memtype);
uint32_t offset = allocation->used;
device.bindBufferMemory(buffer, allocation->memory, offset);
allocation->used += memoryRequirements.size;
return new ManagedBuffer{ allocation, offset, size, buffer, usage, properties, device, nullptr };
}
MemoryAllocation* CreateMemoryAllocation(size_t size, uint32_t type, bool addToCache = true)
{
MemoryAllocation* alloc = new MemoryAllocation(size, type);
const vk::MemoryAllocateInfo allocInfo = { size, type };
alloc->memory = device.allocateMemory(allocInfo);
if (addToCache) allocations.push_back(alloc);
return alloc;
}
MemoryAllocation* GetFreeMemoryAllocation(size_t size, uint32_t type, bool createIfAllFull = true)
{
MemoryAllocation* alloc = nullptr;
for (MemoryAllocation* allocation : allocations)
{
if (allocation->type == type && allocation->FreeSpace() >= size)
{
alloc = allocation;
break;
}
}
if(!alloc && createIfAllFull) alloc = CreateMemoryAllocation(256 * 1024 * 1024, type, true);
if(alloc) lastAllocation = alloc;
return alloc;
}
public:
VulkanShader* CreateShader(Scene::Shader* shader)
{
VulkanShader* vkShader = new VulkanShader();
vkShader->Init(context, shader, this);
shaders.push_back(vkShader);
return vkShader;
}
};
}
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include "../../Base/ICloseable.hpp"
#include "../Scene/IRecordable.hpp"
#include "ManagedResource.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct UniformBuffer : virtual ICloseable, virtual IRecordable
{
ManagedBuffer* buffer;
vk::DescriptorPool descPool;
vk::DescriptorSet descSet;
vk::PipelineLayout layout;
uint32_t allocSizeFrame;
void Init(ManagedBuffer* buffer, uint32_t allocSizeFrame, vk::DescriptorSetLayout* descriptorSetLayout, vk::PipelineLayout layout)
{
this->buffer = buffer;
this->layout = layout;
this->allocSizeFrame = allocSizeFrame;
vk::DescriptorPoolSize poolSize = { vk::DescriptorType::eUniformBufferDynamic, 1 };
const vk::DescriptorPoolCreateInfo poolCreateInfo = { {}, 1, 1, &poolSize };
descPool = buffer->device.createDescriptorPool(poolCreateInfo);
const vk::DescriptorSetAllocateInfo descSetAllocInfo = { descPool, 1, descriptorSetLayout };
descSet = buffer->device.allocateDescriptorSets(descSetAllocInfo)[0];
vk::DescriptorBufferInfo bufferInfo = { buffer->buffer, 0, allocSizeFrame };
vk::WriteDescriptorSet writeDescriptorSet = { descSet };
writeDescriptorSet.descriptorCount = 1;
writeDescriptorSet.descriptorType = vk::DescriptorType::eUniformBufferDynamic;
writeDescriptorSet.pBufferInfo = &bufferInfo;
buffer->device.updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr);
}
void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override
{
uint32_t frameOffset = allocSizeFrame * bufferId;
cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, 1,
&descSet, 1, &frameOffset);
}
void Update(void* data, uint32_t size, uint32_t bufferId) const
{
buffer->Copy(data, size, allocSizeFrame * bufferId);
}
void Close() override
{
buffer->device.destroyDescriptorPool(descPool);
}
};
}
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "vulkan/vulkan.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class IRecordable
{
public:
virtual ~IRecordable() = default;
virtual void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) = 0;
};
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include "IRecordable.hpp"
#include "../../Scene/Scene.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
class VulkanGeometry : virtual public IRecordable, virtual public ICloseable
{
Scene::Geometry* geometry = nullptr;
vk::Buffer vertexBuffer, indexBuffer;
vk::IndexType indexType;
vk::DeviceSize* offsets = new vk::DeviceSize();
public:
VulkanGeometry() = default;
virtual ~VulkanGeometry() { if (vertexBuffer) VulkanGeometry::Close(); };
void Init(Scene::Geometry* geo, vk::Buffer vertexBuffer, vk::Buffer indexBuffer)
{
this->geometry = geo;
offsets[0] = 0;
indexType = (geo->indexType == Scene::VertexIndexType::UINT16) ? vk::IndexType::eUint16 : vk::IndexType::eUint32;
this->vertexBuffer = vertexBuffer;
this->indexBuffer = indexBuffer;
}
void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override
{
cmdBuffer.bindVertexBuffers(0, 1, &vertexBuffer, offsets);
cmdBuffer.bindIndexBuffer(indexBuffer, 0, indexType);
}
void Close() override
{
}
};
}
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include "../../Base/ICloseable.hpp"
#include "IRecordable.hpp"
#include "../../Scene/Camera.hpp"
#include "../Resources/UniformBuffer.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct VulkanNode : virtual IRecordable, virtual ICloseable
{
Scene::Node* node = nullptr;
UniformBuffer* buffer = nullptr;
virtual void Init(Scene::Node* node, UniformBuffer* uniformBuffer)
{
this->node = node;
this->buffer = uniformBuffer;
}
void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override
{
buffer->Record(cmdBuffer, 0);
}
void Close() override {}
};
struct VulkanNodeDynamic : VulkanNode
{
uint32_t lastUpdate = -1;
void Init(Scene::Node* node, UniformBuffer* uniformBuffer) override
{
VulkanNode::Init(node, uniformBuffer);
lastUpdate = -1;
}
void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override
{
if(bufferId != lastUpdate)
{
lastUpdate = bufferId;
buffer->Update(&node->worldMat, sizeof(glm::mat4x4), bufferId);
}
buffer->Record(cmdBuffer, bufferId);
}
void Close() override{}
};
}
}

View File

@@ -0,0 +1,97 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "../Device.hpp"
#include "../../Scene/Shader.hpp"
#include "../../Scene/Vertex.hpp"
#include "../../Base/ICloseable.hpp"
#include "../Resources/IShaderOwner.hpp"
#include "IRecordable.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
vk::PrimitiveTopology ToVkTopology(Scene::Topology topology)
{
switch (topology) {
case Scene::Topology::PointList: return vk::PrimitiveTopology::ePointList;
case Scene::Topology::LineList: return vk::PrimitiveTopology::eLineList;
case Scene::Topology::LineStripe: return vk::PrimitiveTopology::eLineStrip;
case Scene::Topology::TriangleList: return vk::PrimitiveTopology::eTriangleList;
case Scene::Topology::TriangleStripe: return vk::PrimitiveTopology::eTriangleStrip;
default: throw std::runtime_error("Unknown topology!");
}
}
struct VulkanShader : virtual public ICloseable, virtual public IRecordable
{
Scene::Shader* shader = nullptr;
vk::Device device;
vk::ShaderModule shaderModuleVertex, shaderModuleFragment;
vk::Pipeline pipeline;
IShaderOwner* owner;
VulkanShader() = default;
virtual ~VulkanShader() { if (shader) VulkanShader::Close(); }
void Init(Context* context, Scene::Shader* shader, IShaderOwner* owner)
{
this->device = context->device->device;
this->shader = shader;
this->owner = owner;
shaderModuleVertex = context->device->CreateShaderModule(shader->vertexShaderName + ".vert.spv");
shaderModuleFragment = context->device->CreateShaderModule(shader->fragmentShaderName + ".frag.spv");
std::vector<vk::PipelineShaderStageCreateInfo> shaderStageCreateInfos(2);
shaderStageCreateInfos[0] = { {}, vk::ShaderStageFlagBits::eVertex, shaderModuleVertex, "main" };
shaderStageCreateInfos[1] = { {}, vk::ShaderStageFlagBits::eFragment, shaderModuleFragment, "main" };
auto vertexBindDesc = vk::VertexInputBindingDescription(0, sizeof(Vertex), vk::VertexInputRate::eVertex);
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions;
attributeDescriptions.emplace_back(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, position));
attributeDescriptions.emplace_back(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal));
attributeDescriptions.emplace_back(2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, tangent));
attributeDescriptions.emplace_back(3, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, biTangent));
attributeDescriptions.emplace_back(4, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, textureCoordinates));
attributeDescriptions.emplace_back(5, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Vertex, color));
auto viewport = context->swapChain.GetFullscreenViewport();
auto scissor = context->swapChain.GetFullscreenScissor();
vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = { {}, 1, &viewport, 1, &scissor };
vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = { {}, 1, &vertexBindDesc,
static_cast<uint32_t>(attributeDescriptions.size()), attributeDescriptions.data() };
vk::PipelineInputAssemblyStateCreateInfo inputAssembly = { {}, ToVkTopology(shader->topology), 0 };
vk::PipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.cullMode = vk::CullModeFlagBits::eBack;
vk::PipelineMultisampleStateCreateInfo msaa = {};
vk::PipelineDepthStencilStateCreateInfo depth = { {}, 1, 1, vk::CompareOp::eGreater };
vk::PipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eA | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eR;
vk::PipelineColorBlendStateCreateInfo colorInfo = {};
colorInfo.attachmentCount = 1;
colorInfo.pAttachments = &colorBlendAttachment;
vk::GraphicsPipelineCreateInfo pipelineCreateInfo = { {}, static_cast<uint32_t>(shaderStageCreateInfos.size()), shaderStageCreateInfos.data(), &pipelineVertexInputStateCreateInfo, &inputAssembly,
nullptr, &viewportStateCreateInfo, &rasterizer, &msaa, &depth, &colorInfo, nullptr, context->pipeline.pipelineLayout, context->swapChainRenderPass.renderPass };
pipeline = this->device.createGraphicsPipeline({}, pipelineCreateInfo);
}
void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override
{
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
}
void Close() override
{
owner->RemoveShader(this);
shader = nullptr;
device.destroyPipeline(pipeline);
device.destroyShaderModule(shaderModuleVertex);
device.destroyShaderModule(shaderModuleFragment);
}
};
}
}

View File

@@ -0,0 +1,261 @@
#pragma once
#include <algorithm>
#include <vulkan/vulkan.hpp>
#include "Device.hpp"
#include "Image.hpp"
#include "FrameBuffer.hpp"
namespace openVulkanoCpp
{
namespace Vulkan
{
struct SwapChainImage : virtual public IImage
{
vk::Image image;
vk::ImageView view;
vk::Fence fence;
vk::Image GetImage() override
{
return image;
}
vk::ImageView GetView() override
{
return view;
}
};
class SwapChain : public FrameBuffer
{
vk::SurfaceKHR surface;
std::vector<SwapChainImage> images;
Device* device = nullptr;
IVulkanWindow* window = nullptr;
vk::SurfaceFormatKHR surfaceFormat;
vk::PresentModeKHR presentMode;
vk::Viewport fullscreenViewport;
bool useVsync = false;
uint32_t preferredImageCount = 2; //TODO add option
vk::Extent2D size{0,0};
public:
vk::SwapchainKHR swapChain;
vk::Semaphore imageAvailableSemaphore;
SwapChain() = default;
~SwapChain() { if (device) SwapChain::Close(); }
void Init(Device* device, vk::SurfaceKHR surface, IVulkanWindow* window)
{
if (!device) throw std::runtime_error("The device must not be null");
if (!window) throw std::runtime_error("The window must not be null");
this->device = device;
this->surface = surface;
this->window = window;
imageAvailableSemaphore = device->device.createSemaphore({});
CreateSwapChain({window->GetWidth(), window->GetHeight() });
FrameBuffer::Init(device, vk::Extent3D(size, 1));
}
void Close() override
{
DestroySwapChain();
device->device.destroySemaphore(imageAvailableSemaphore);
device = nullptr;
FrameBuffer::Close();
}
void Resize(const uint32_t newWidth, const uint32_t newHeight)
{
if(newWidth == 0 || newHeight == 0) return; // Swap chain size of 0 pixel is not allowed
CreateSwapChain({ newWidth, newHeight });
FrameBuffer::Resize(vk::Extent3D(size, 1));
}
vk::Extent2D GetSize() const
{
return size;
}
vk::Viewport GetFullscreenViewport() const
{
return fullscreenViewport;
}
vk::Rect2D GetFullscreenScissor() const
{
return { {0,0}, GetSize() };
}
uint32_t AcquireNextImage(const vk::Fence fence = vk::Fence())
{
const auto resultValue = device->device.acquireNextImageKHR(swapChain, UINT64_MAX, imageAvailableSemaphore, fence);
const vk::Result result = resultValue.result;
if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) throw std::error_code(result);
SetCurrentFrameId(resultValue.value);
vk::Fence submitFence = GetCurrentSubmitFence();
device->device.waitForFences(1, &submitFence, true, -1);
device->device.resetFences(1, &submitFence);
return currentFrameBufferId;
}
vk::Fence& GetCurrentSubmitFence()
{
return images[currentFrameBufferId].fence;
}
void Present(vk::Queue& queue ,std::vector<vk::Semaphore>& semaphores) const
{
queue.presentKHR(vk::PresentInfoKHR(semaphores.size(), semaphores.data(),
1, &swapChain, &currentFrameBufferId));
}
bool UseVsync() { return useVsync; }
void SetVsync(bool useVsync)
{ //TODO change swap chain
this->useVsync = useVsync;
}
uint32_t GetImageCount() const
{
return images.size();
}
private:
void CreateSwapChain(vk::Extent2D size)
{
Logger::RENDER->debug("Creating swap chain for window {0} ...", window->GetWindowId());
surfaceFormat = ChoseSurfaceFormat();
presentMode = ChosePresentMode();
const vk::SurfaceCapabilitiesKHR surfaceCapabilities = device->physicalDevice.getSurfaceCapabilitiesKHR(surface);
if(surfaceCapabilities.currentExtent.width != ~static_cast<uint32_t>(0))
{ // The surface does provide it's size to the vulkan driver
size = surfaceCapabilities.currentExtent;
}
vk::SurfaceTransformFlagBitsKHR preTransform; //TODO add option to allow rotation and other modifications
if (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity)
{ preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; }
else { preTransform = surfaceCapabilities.currentTransform; }
uint32_t usingImages = std::max(preferredImageCount, surfaceCapabilities.minImageCount);
if (surfaceCapabilities.maxImageCount > 0) //GPU has limit of swap chain images
usingImages = std::min(usingImages, surfaceCapabilities.maxImageCount);
Logger::RENDER->debug("GPU supports {0} to {1} swap chain images. Preferred: {2}, Using: {3}", surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount, preferredImageCount, usingImages);
const vk::SwapchainCreateInfoKHR createInfo({}, surface, usingImages, surfaceFormat.format,
surfaceFormat.colorSpace, size, 1,
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst,
vk::SharingMode::eExclusive, 0, nullptr, preTransform,
vk::CompositeAlphaFlagBitsKHR::eOpaque, presentMode, VK_TRUE, swapChain);
const vk::SwapchainKHR newSwapChain = device->device.createSwapchainKHR(createInfo);
DestroySwapChain();
swapChain = newSwapChain;
this->size = size;
CreateSwapChainImages();
fullscreenViewport = vk::Viewport{ 0, 0, (float)size.width, (float)size.height, 0, 1 };
Logger::RENDER->debug("Swap chain for window {0} created", window->GetWindowId());
}
void CreateSwapChainImages()
{
vk::ImageViewCreateInfo imgViewCreateInfo;
imgViewCreateInfo.format = surfaceFormat.format;
imgViewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
imgViewCreateInfo.subresourceRange.levelCount = 1;
imgViewCreateInfo.subresourceRange.layerCount = 1;
imgViewCreateInfo.viewType = vk::ImageViewType::e2D;
auto swapChainImages = device->device.getSwapchainImagesKHR(swapChain);
images.resize(swapChainImages.size());
for (uint32_t i = 0; i < swapChainImages.size(); i++)
{
images[i].image = swapChainImages[i];
imgViewCreateInfo.image = swapChainImages[i];
images[i].view = device->device.createImageView(imgViewCreateInfo);
images[i].fence = device->device.createFence({ vk::FenceCreateFlags(vk::FenceCreateFlagBits::eSignaled)});
}
}
void DestroySwapChain() const
{
for(auto& image : images)
{
device->device.destroyImageView(image.view);
device->device.destroyFence(image.fence);
}
device->device.destroySwapchainKHR(swapChain);
}
protected:
vk::Format FindColorFormat() override
{
return surfaceFormat.format;
}
virtual vk::PresentModeKHR ChosePresentMode()
{
std::vector<vk::PresentModeKHR> presentModes = device->physicalDevice.getSurfacePresentModesKHR(surface);
#ifdef DEBUG
std::string availableModes = "";
for (const auto& presentMode : presentModes)
{
if (availableModes.length() > 0) availableModes += ", ";
availableModes += vk::to_string(presentMode);
}
Logger::RENDER->debug("Available swap chain present modes {0}. Searching best mode for: vsync={1}", availableModes, useVsync);
#endif
vk::PresentModeKHR mode = vk::PresentModeKHR::eFifo;
if (useVsync)
{
if (Utils::Contains(presentModes, vk::PresentModeKHR::eMailbox)) mode = vk::PresentModeKHR::eMailbox;
}
else
{
if (Utils::Contains(presentModes, vk::PresentModeKHR::eImmediate)) mode = vk::PresentModeKHR::eImmediate;
else if (Utils::Contains(presentModes, vk::PresentModeKHR::eFifoRelaxed)) mode = vk::PresentModeKHR::eFifoRelaxed;
}
Logger::RENDER->debug("Using swap chain present mode {0}", vk::to_string(mode));
return mode;
}
virtual vk::SurfaceFormatKHR ChoseSurfaceFormat()
{ //TODO allow to chose output format
std::vector<vk::SurfaceFormatKHR> surfaceFormats = device->physicalDevice.getSurfaceFormatsKHR(surface);
if(surfaceFormats.size() == 1 && surfaceFormats[0].format == vk::Format::eUndefined)
{ // GPU does not have a preferred surface format
return vk::SurfaceFormatKHR{ vk::Format::eB8G8R8A8Unorm, surfaceFormats[0].colorSpace };
}
else
{ //TODO chose best fitting
return surfaceFormats[0];
}
}
public:
std::vector<IImage*> GetImages() override
{
std::vector<IImage*> imgs;
for (auto& image : images)
{
imgs.push_back(&image);
}
return imgs;
}
};
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <vulkan/vulkan.hpp>
namespace openVulkanoCpp
{
namespace Vulkan
{
class VulkanUtils
{
public:
static vk::AccessFlags GetAccessFlagsForLayout(vk::ImageLayout layout)
{
switch (layout)
{
case vk::ImageLayout::ePreinitialized: return vk::AccessFlagBits::eHostWrite;
case vk::ImageLayout::eTransferSrcOptimal: return vk::AccessFlagBits::eTransferRead;
case vk::ImageLayout::eTransferDstOptimal: return vk::AccessFlagBits::eTransferWrite;
case vk::ImageLayout::eShaderReadOnlyOptimal: return vk::AccessFlagBits::eShaderRead;
case vk::ImageLayout::eColorAttachmentOptimal: return vk::AccessFlagBits::eColorAttachmentWrite;
case vk::ImageLayout::eDepthStencilAttachmentOptimal: return vk::AccessFlagBits::eDepthStencilAttachmentWrite;
default: return vk::AccessFlags();
}
}
static vk::PipelineStageFlags GetPipelineStageForLayout(vk::ImageLayout layout)
{
switch (layout)
{
case vk::ImageLayout::ePreinitialized: return vk::PipelineStageFlagBits::eHost;
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal: return vk::PipelineStageFlagBits::eTransfer;
case vk::ImageLayout::eShaderReadOnlyOptimal: return vk::PipelineStageFlagBits::eFragmentShader;
case vk::ImageLayout::eColorAttachmentOptimal: return vk::PipelineStageFlagBits::eColorAttachmentOutput;
case vk::ImageLayout::eDepthStencilAttachmentOptimal: return vk::PipelineStageFlagBits::eEarlyFragmentTests;
case vk::ImageLayout::eUndefined: return vk::PipelineStageFlagBits::eTopOfPipe;
default: return vk::PipelineStageFlagBits::eBottomOfPipe;
}
}
};
}
}