first release
This commit is contained in:
109
openVulkanoCpp/Vulkan/Buffer.hpp
Normal file
109
openVulkanoCpp/Vulkan/Buffer.hpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
45
openVulkanoCpp/Vulkan/CommandHelper.hpp
Normal file
45
openVulkanoCpp/Vulkan/CommandHelper.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
119
openVulkanoCpp/Vulkan/Context.hpp
Normal file
119
openVulkanoCpp/Vulkan/Context.hpp
Normal 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());;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
97
openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp
Normal file
97
openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
279
openVulkanoCpp/Vulkan/Device.hpp
Normal file
279
openVulkanoCpp/Vulkan/Device.hpp
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
44
openVulkanoCpp/Vulkan/DeviceManager.hpp
Normal file
44
openVulkanoCpp/Vulkan/DeviceManager.hpp
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
76
openVulkanoCpp/Vulkan/FrameBuffer.cpp
Normal file
76
openVulkanoCpp/Vulkan/FrameBuffer.cpp
Normal 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();
|
||||
}
|
||||
107
openVulkanoCpp/Vulkan/FrameBuffer.hpp
Normal file
107
openVulkanoCpp/Vulkan/FrameBuffer.hpp
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
98
openVulkanoCpp/Vulkan/Image.hpp
Normal file
98
openVulkanoCpp/Vulkan/Image.hpp
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
42
openVulkanoCpp/Vulkan/Pipeline.hpp
Normal file
42
openVulkanoCpp/Vulkan/Pipeline.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
146
openVulkanoCpp/Vulkan/RenderPass.hpp
Normal file
146
openVulkanoCpp/Vulkan/RenderPass.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
220
openVulkanoCpp/Vulkan/Renderer.hpp
Normal file
220
openVulkanoCpp/Vulkan/Renderer.hpp
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
16
openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp
Normal file
16
openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
93
openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp
Normal file
93
openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
258
openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp
Normal file
258
openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp
Normal 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, ©Region);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
54
openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp
Normal file
54
openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
15
openVulkanoCpp/Vulkan/Scene/IRecordable.hpp
Normal file
15
openVulkanoCpp/Vulkan/Scene/IRecordable.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
41
openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp
Normal file
41
openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
53
openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp
Normal file
53
openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp
Normal 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{}
|
||||
};
|
||||
}
|
||||
}
|
||||
97
openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp
Normal file
97
openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
261
openVulkanoCpp/Vulkan/SwapChain.hpp
Normal file
261
openVulkanoCpp/Vulkan/SwapChain.hpp
Normal 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, ¤tFrameBufferId));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
41
openVulkanoCpp/Vulkan/VulkanUtils.hpp
Normal file
41
openVulkanoCpp/Vulkan/VulkanUtils.hpp
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user