mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Feat] (MG_Backend/DirectVulkan): redo the whole thing, bringing up Vulkan instance
This commit is contained in:
@@ -13,8 +13,45 @@
|
|||||||
#include "FrameContext.h"
|
#include "FrameContext.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||||
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
|
||||||
|
auto typeToString = [](VkDebugUtilsMessageTypeFlagsEXT messageType) {
|
||||||
|
switch (messageType) {
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
|
||||||
|
return "General";
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
|
||||||
|
return "Validation";
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
|
||||||
|
return "Performance";
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT:
|
||||||
|
return "DeviceAddressBinding";
|
||||||
|
default:
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (messageSeverity) {
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
||||||
|
MGLOG_E("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
||||||
|
MGLOG_W("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
|
||||||
|
MGLOG_I("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
|
||||||
|
MGLOG_D("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return VK_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
VulkanRenderer::VulkanRenderer(NativeWindowType window, const RendererConfig& cfg) : m_window(window), m_config(cfg) {
|
VulkanRenderer::VulkanRenderer(NativeWindowType window, const RendererConfig& cfg) : m_window(window), m_config(cfg) {
|
||||||
m_context = std::make_unique<VulkanContext>();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanRenderer::~VulkanRenderer() {
|
VulkanRenderer::~VulkanRenderer() {
|
||||||
@@ -22,282 +59,159 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::Initialize() {
|
void VulkanRenderer::Initialize() {
|
||||||
m_context->Initialize(m_window, m_config.AppName);
|
CreateInstance();
|
||||||
|
|
||||||
m_swapchain = std::make_unique<SwapchainManager>(*m_context);
|
|
||||||
m_swapchain->Initialize();
|
|
||||||
|
|
||||||
CreateRenderPass();
|
|
||||||
|
|
||||||
m_pipelineMgr = std::make_unique<PipelineManager>(*m_context);
|
|
||||||
|
|
||||||
CreateCommandPool();
|
|
||||||
CreateFrameResources();
|
|
||||||
|
|
||||||
MGLOG_D("VulkanRenderer initialized");
|
MGLOG_D("VulkanRenderer initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::Shutdown() {
|
void VulkanRenderer::Shutdown() {
|
||||||
if (!m_context) return;
|
if (m_debugMessenger != VK_NULL_HANDLE) {
|
||||||
|
DestroyDebugMessenger();
|
||||||
if (m_context->GetDevice() == VK_NULL_HANDLE) return;
|
|
||||||
|
|
||||||
vkDeviceWaitIdle(m_context->GetDevice());
|
|
||||||
|
|
||||||
DestroyFrameResources();
|
|
||||||
DestroyCommandPool();
|
|
||||||
|
|
||||||
if (m_pipelineMgr) {
|
|
||||||
m_pipelineMgr->Cleanup();
|
|
||||||
m_pipelineMgr.reset();
|
|
||||||
}
|
}
|
||||||
DestroyRenderPass();
|
DestroyInstance();
|
||||||
if (m_swapchain) {
|
MGLOG_D("VulkanRenderer shut down completed");
|
||||||
m_swapchain->Cleanup();
|
|
||||||
m_swapchain.reset();
|
|
||||||
}
|
|
||||||
if (m_context) {
|
|
||||||
m_context->Shutdown();
|
|
||||||
m_context.reset();
|
|
||||||
}
|
|
||||||
MGLOG_D("VulkanRenderer shutdown");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::CreateRenderPass() {
|
void VulkanRenderer::Render() {
|
||||||
VkAttachmentDescription color{};
|
|
||||||
color.format = m_swapchain->GetFormat();
|
|
||||||
color.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
||||||
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
||||||
color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
||||||
|
|
||||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
||||||
VkSubpassDescription sub{};
|
|
||||||
sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
||||||
sub.colorAttachmentCount = 1;
|
|
||||||
sub.pColorAttachments = &colorRef;
|
|
||||||
|
|
||||||
VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
|
||||||
rpci.attachmentCount = 1;
|
|
||||||
rpci.pAttachments = &color;
|
|
||||||
rpci.subpassCount = 1;
|
|
||||||
rpci.pSubpasses = ⊂
|
|
||||||
|
|
||||||
VK_VERIFY(vkCreateRenderPass(m_context->GetDevice(), &rpci, nullptr, &m_renderPass), "vkCreateRenderPass");
|
|
||||||
|
|
||||||
// Create framebuffers now (use swapchain imageviews)
|
|
||||||
const auto& imageViews = m_swapchain->GetImageViews();
|
|
||||||
std::vector<VkFramebuffer> fbs;
|
|
||||||
fbs.reserve(imageViews.size());
|
|
||||||
for (auto iv : imageViews) {
|
|
||||||
VkImageView attachments[] = {iv};
|
|
||||||
VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
|
||||||
fbci.renderPass = m_renderPass;
|
|
||||||
fbci.attachmentCount = 1;
|
|
||||||
fbci.pAttachments = attachments;
|
|
||||||
fbci.width = m_swapchain->GetExtent().width;
|
|
||||||
fbci.height = m_swapchain->GetExtent().height;
|
|
||||||
fbci.layers = 1;
|
|
||||||
VkFramebuffer fb;
|
|
||||||
VK_VERIFY(vkCreateFramebuffer(m_context->GetDevice(), &fbci, nullptr, &fb), "vkCreateFramebuffer");
|
|
||||||
fbs.push_back(fb);
|
|
||||||
}
|
|
||||||
m_swapchain->SetFramebuffers(std::move(fbs));
|
|
||||||
MGLOG_D("RenderPass created and framebuffers set");
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::DestroyRenderPass() {
|
|
||||||
if (m_renderPass != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyRenderPass(m_context->GetDevice(), m_renderPass, nullptr);
|
|
||||||
m_renderPass = VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::CreateCommandPool() {
|
|
||||||
VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
||||||
cpci.queueFamilyIndex = m_context->GetGraphicsQueueFamily();
|
|
||||||
cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
||||||
VK_VERIFY(vkCreateCommandPool(m_context->GetDevice(), &cpci, nullptr, &m_commandPool), "vkCreateCommandPool");
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::DestroyCommandPool() {
|
|
||||||
if (m_commandPool != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyCommandPool(m_context->GetDevice(), m_commandPool, nullptr);
|
|
||||||
m_commandPool = VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::CreateFrameResources() {
|
|
||||||
uint32_t imageCount = static_cast<uint32_t>(m_swapchain->GetImageViews().size());
|
|
||||||
if (imageCount == 0) throw RuntimeError("Swapchain has zero images");
|
|
||||||
uint32_t frames = std::min<uint32_t>(m_config.MaxFramesInFlight, imageCount);
|
|
||||||
m_frames.clear();
|
|
||||||
for (uint32_t i = 0; i < frames; ++i) {
|
|
||||||
auto fr = std::make_unique<FrameContext>();
|
|
||||||
fr->Initialize(*m_context, m_commandPool);
|
|
||||||
m_frames.push_back(std::move(fr));
|
|
||||||
}
|
|
||||||
m_currentFrame = 0;
|
|
||||||
MGLOG_D("FrameResources created: %u", (uint32_t)Frames.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::DestroyFrameResources() {
|
|
||||||
for (auto& f : m_frames) {
|
|
||||||
if (f) f->Cleanup(*m_context);
|
|
||||||
}
|
|
||||||
m_frames.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex) {
|
|
||||||
// Begin
|
|
||||||
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
||||||
VK_VERIFY(vkBeginCommandBuffer(frame.CommandBuffer, &bi), "vkBeginCommandBuffer");
|
|
||||||
|
|
||||||
VkClearValue clear{};
|
|
||||||
clear.color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
|
||||||
VkRenderPassBeginInfo rpbi{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
|
||||||
rpbi.renderPass = m_renderPass;
|
|
||||||
rpbi.framebuffer = m_swapchain->GetFramebuffers()[imageIndex];
|
|
||||||
rpbi.renderArea.offset = {0, 0};
|
|
||||||
rpbi.renderArea.extent = m_swapchain->GetExtent();
|
|
||||||
rpbi.clearValueCount = 1;
|
|
||||||
rpbi.pClearValues = &clear;
|
|
||||||
|
|
||||||
vkCmdBeginRenderPass(frame.CommandBuffer, &rpbi, VK_SUBPASS_CONTENTS_INLINE);
|
|
||||||
|
|
||||||
for (auto& kv : m_renderCallbacks) {
|
|
||||||
if (kv.second) {
|
|
||||||
kv.second(frame.CommandBuffer, imageIndex, m_swapchain->GetExtent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vkCmdEndRenderPass(frame.CommandBuffer);
|
|
||||||
VK_VERIFY(vkEndCommandBuffer(frame.CommandBuffer), "vkEndCommandBuffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanRenderer::RecreateSwapchainIfNeeded() {
|
|
||||||
vkDeviceWaitIdle(m_context->GetDevice());
|
|
||||||
DestroyFrameResources();
|
|
||||||
DestroyRenderPass();
|
|
||||||
m_swapchain->Recreate();
|
|
||||||
CreateRenderPass();
|
|
||||||
CreateFrameResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait fence & Acquire image & Record commands & Submit
|
|
||||||
void VulkanRenderer::RenderFrame() {
|
|
||||||
if (!m_context) throw RuntimeError("Renderer not initialized");
|
|
||||||
FrameContext& frame = *m_frames[m_currentFrame];
|
|
||||||
|
|
||||||
// Ensure the previous use of this frame context has fully completed
|
|
||||||
// before reusing its semaphores in vkAcquireNextImageKHR.
|
|
||||||
VK_VERIFY(vkWaitForFences(m_context->GetDevice(), 1, &frame.InFlightFence, VK_TRUE, UINT64_MAX), "vkWaitForFences");
|
|
||||||
|
|
||||||
if (!FrameBegin()) return;
|
|
||||||
|
|
||||||
// Fence will be signaled by vkQueueSubmit below.
|
|
||||||
VK_VERIFY(vkResetFences(m_context->GetDevice(), 1, &frame.InFlightFence), "vkResetFences");
|
|
||||||
|
|
||||||
// Record commands
|
|
||||||
VK_VERIFY(vkResetCommandBuffer(frame.CommandBuffer, 0), "vkResetCommandBuffer");
|
|
||||||
RecordFrameCommandBuffer(frame, frame.CurrentImageIndex);
|
|
||||||
|
|
||||||
// Submit
|
|
||||||
VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
||||||
VkSemaphore waitSemaphores[] = {frame.ImageAvailable};
|
|
||||||
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
|
||||||
si.waitSemaphoreCount = 1;
|
|
||||||
si.pWaitSemaphores = waitSemaphores;
|
|
||||||
si.pWaitDstStageMask = waitStages;
|
|
||||||
si.commandBufferCount = 1;
|
|
||||||
si.pCommandBuffers = &frame.CommandBuffer;
|
|
||||||
VkSemaphore signalSemaphores[] = {frame.RenderFinished};
|
|
||||||
si.signalSemaphoreCount = 1;
|
|
||||||
si.pSignalSemaphores = signalSemaphores;
|
|
||||||
|
|
||||||
VK_VERIFY(vkQueueSubmit(m_context->GetGraphicsQueue(), 1, &si, frame.InFlightFence), "vkQueueSubmit");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VulkanRenderer::FrameBegin() {
|
|
||||||
FrameContext& frame = *m_frames[m_currentFrame];
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
// Acquire image for this frame. Acquire can fail with OUT_OF_DATE during resize/minimize.
|
|
||||||
Uint32 imageIndex = 0;
|
|
||||||
VkResult res = vkAcquireNextImageKHR(m_context->GetDevice(), m_swapchain->GetSwapchain(), UINT64_MAX,
|
|
||||||
frame.ImageAvailable, VK_NULL_HANDLE, &imageIndex);
|
|
||||||
|
|
||||||
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
||||||
MGLOG_D("vkAcquireNextImageKHR: OUT_OF_DATE -> recreate");
|
|
||||||
RecreateSwapchainIfNeeded();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) {
|
|
||||||
VK_VERIFY(res, "vkAcquireNextImageKHR");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& imagesInFlight = m_swapchain->GetImagesInFlight();
|
|
||||||
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
|
|
||||||
vkWaitForFences(m_context->GetDevice(), 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
|
|
||||||
}
|
|
||||||
imagesInFlight[imageIndex] = frame.InFlightFence;
|
|
||||||
frame.CurrentImageIndex = imageIndex;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::Present() {
|
void VulkanRenderer::Present() {
|
||||||
if (!m_context) throw RuntimeError("Renderer not initialized");
|
|
||||||
FrameContext& frame = *m_frames[m_currentFrame];
|
|
||||||
|
|
||||||
// Present
|
}
|
||||||
VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
|
||||||
VkSemaphore signalSemaphores[] = {frame.RenderFinished};
|
void VulkanRenderer::CreateInstance() {
|
||||||
pi.waitSemaphoreCount = 1;
|
m_extensions = EnumerateExtensions();
|
||||||
pi.pWaitSemaphores = signalSemaphores;
|
MGLOG_I("Got %d Vulkan extensions: ", m_extensions.size());
|
||||||
VkSwapchainKHR scs[] = {m_swapchain->GetSwapchain()};
|
for (auto& extension : m_extensions) {
|
||||||
pi.swapchainCount = 1;
|
MGLOG_I(" %s (r.%u)", extension.extensionName, extension.specVersion);
|
||||||
pi.pSwapchains = scs;
|
}
|
||||||
pi.pImageIndices = &frame.CurrentImageIndex;
|
|
||||||
VkResult pres = vkQueuePresentKHR(m_context->GetGraphicsQueue(), &pi);
|
Bool validationLayerAvailable = CheckValidationLayerSupport();
|
||||||
if (pres == VK_ERROR_OUT_OF_DATE_KHR || pres == VK_SUBOPTIMAL_KHR) {
|
MGLOG_D("Validation layers %s.", validationLayerAvailable ? "available" : "not available");
|
||||||
MGLOG_D("vkQueuePresentKHR: out_of_date/suboptimal -> recreate");
|
MGLOG_D("Validation layers %s.", m_config.EnableValidationLayers ? "requested" : "not requested");
|
||||||
RecreateSwapchainIfNeeded();
|
|
||||||
|
if (m_config.EnableValidationLayers && !validationLayerAvailable) {
|
||||||
|
MOBILEGL_ASSERT(false, "Validation layers requested but not available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enableValidationLayers = m_config.EnableValidationLayers && validationLayerAvailable;
|
||||||
|
|
||||||
|
// ---------------- App info -------------------
|
||||||
|
VkApplicationInfo appInfo = {};
|
||||||
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||||
|
appInfo.pApplicationName = m_config.AppName.c_str();
|
||||||
|
appInfo.applicationVersion = VK_MAKE_VERSION(m_config.CacheVersion, 0, 0);
|
||||||
|
appInfo.pEngineName = "MobileGL";
|
||||||
|
appInfo.engineVersion = VK_MAKE_VERSION(m_config.Version.Major, m_config.Version.Minor, m_config.Version.Patch);
|
||||||
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
appInfo.apiVersion = VK_API_VERSION_1_3;
|
||||||
|
#else
|
||||||
|
appInfo.apiVersion = VK_API_VERSION_1_1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------- Instance info -------------------
|
||||||
|
VkInstanceCreateInfo instanceInfo = {};
|
||||||
|
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||||
|
instanceInfo.pApplicationInfo = &appInfo;
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
Vector<const char*> exts = {VK_KHR_SURFACE_EXTENSION_NAME,
|
||||||
|
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||||
|
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME
|
||||||
|
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
VK_KHR_WIN32_SURFACE_EXTENSION_NAME
|
||||||
|
#else
|
||||||
|
#warning "VulkanContext::CreateInstance: VK_KHR_*_surface extension not defined on this platform"
|
||||||
|
#endif
|
||||||
|
}; // TODO: support more platforms
|
||||||
|
|
||||||
|
if (enableValidationLayers) {
|
||||||
|
exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceInfo.enabledExtensionCount = exts.size();
|
||||||
|
instanceInfo.ppEnabledExtensionNames = exts.data();
|
||||||
|
|
||||||
|
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
|
||||||
|
// Layers
|
||||||
|
if (enableValidationLayers) {
|
||||||
|
MGLOG_I("Enabling validation layer.");
|
||||||
|
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
||||||
|
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
|
||||||
|
instanceInfo.pNext = &debugMessengerCreateInfo;
|
||||||
} else {
|
} else {
|
||||||
VK_VERIFY(pres, "vkQueuePresentKHR");
|
instanceInfo.enabledLayerCount = 0;
|
||||||
|
instanceInfo.pNext = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentFrame = (m_currentFrame + 1) % m_frames.size();
|
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
|
||||||
|
|
||||||
|
if (enableValidationLayers)
|
||||||
|
VK_VERIFY(SetupDebugMessenger());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::RegisterRenderCallback(const String& name, RenderCallback cb) {
|
void VulkanRenderer::DestroyInstance() {
|
||||||
auto it = std::find_if(m_renderCallbacks.begin(), m_renderCallbacks.end(),
|
if (m_instance != VK_NULL_HANDLE) {
|
||||||
[&](const auto& kv) { return kv.first == name; });
|
vkDestroyInstance(m_instance, nullptr);
|
||||||
if (it != m_renderCallbacks.end()) {
|
m_instance = VK_NULL_HANDLE;
|
||||||
MGLOG_W("Render callback '%s' already registered", name.c_str());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_renderCallbacks.emplace_back(name, std::move(cb));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::UnregisterRenderCallback(const String& name) {
|
VkResult VulkanRenderer::SetupDebugMessenger() {
|
||||||
m_renderCallbacks.erase(std::remove_if(m_renderCallbacks.begin(), m_renderCallbacks.end(),
|
auto createInfo = PopulateDebugMessengerCreateInfo();
|
||||||
[&](const auto& kv) { return kv.first == name; }),
|
auto vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT");
|
||||||
m_renderCallbacks.end());
|
if (!vkCreateDebugUtilsMessengerEXT)
|
||||||
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||||
|
VK_VERIFY(vkCreateDebugUtilsMessengerEXT(m_instance, &createInfo, nullptr, &m_debugMessenger));
|
||||||
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline VulkanRenderer::CreateGraphicsPipelineFromSpv(const String& key, const Vector<uint32_t>& vsSpv,
|
VkResult VulkanRenderer::DestroyDebugMessenger() {
|
||||||
const Vector<uint32_t>& fsSpv) {
|
if (m_debugMessenger != VK_NULL_HANDLE) {
|
||||||
return m_pipelineMgr->CreateGraphicsPipelineFromSpv(key, vsSpv, fsSpv, m_renderPass, m_swapchain->GetExtent());
|
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||||
|
if (func != nullptr) {
|
||||||
|
func(m_instance, m_debugMessenger, nullptr);
|
||||||
|
} else {
|
||||||
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkExtent2D VulkanRenderer::GetExtent() const {
|
VkDebugUtilsMessengerCreateInfoEXT VulkanRenderer::PopulateDebugMessengerCreateInfo() {
|
||||||
return m_swapchain ? m_swapchain->GetExtent() : VkExtent2D{0, 0};
|
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||||||
|
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||||
|
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||||
|
createInfo.pfnUserCallback = DebugCallback;
|
||||||
|
createInfo.pUserData = this;
|
||||||
|
return createInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::WaitIdle() {
|
Vector<VkExtensionProperties> VulkanRenderer::EnumerateExtensions() {
|
||||||
if (m_context && m_context->GetDevice() != VK_NULL_HANDLE) vkDeviceWaitIdle(m_context->GetDevice());
|
Uint32 extensionCount = 0;
|
||||||
|
VK_VERIFY(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
|
||||||
|
Vector<VkExtensionProperties> extensions(extensionCount);
|
||||||
|
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
|
||||||
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool VulkanRenderer::CheckValidationLayerSupport() {
|
||||||
|
Uint32 layerCount = 0;
|
||||||
|
VK_VERIFY(vkEnumerateInstanceLayerProperties(&layerCount, nullptr));
|
||||||
|
|
||||||
|
Vector<VkLayerProperties> layers(layerCount);
|
||||||
|
VK_VERIFY(vkEnumerateInstanceLayerProperties(&layerCount, layers.data()));
|
||||||
|
|
||||||
|
for (const char* layerName : s_validationLayerNames) {
|
||||||
|
for (const auto& layerProperties : layers) {
|
||||||
|
if (strcmp(layerName, layerProperties.layerName) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -7,8 +7,15 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Config.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
|
||||||
|
#define VK_VERIFY(expr, ...) \
|
||||||
|
do { \
|
||||||
|
VkResult _vk_verify_result = (expr); \
|
||||||
|
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
class VulkanContext;
|
class VulkanContext;
|
||||||
class SwapchainManager;
|
class SwapchainManager;
|
||||||
@@ -20,6 +27,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
struct RendererConfig {
|
struct RendererConfig {
|
||||||
Uint32 MaxFramesInFlight = 2;
|
Uint32 MaxFramesInFlight = 2;
|
||||||
String AppName = "MobileGL-VulkanRenderer";
|
String AppName = "MobileGL-VulkanRenderer";
|
||||||
|
Version Version = MG_Config::CoreVersion;
|
||||||
|
Uint64 CacheVersion = MG_Config::CacheVersion;
|
||||||
|
Bool EnableValidationLayers = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanRenderer {
|
class VulkanRenderer {
|
||||||
@@ -30,45 +40,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void RenderFrame();
|
void Render();
|
||||||
void Present();
|
void Present();
|
||||||
|
|
||||||
void RegisterRenderCallback(const String& name, RenderCallback cb);
|
|
||||||
void UnregisterRenderCallback(const String& name);
|
|
||||||
|
|
||||||
VkPipeline CreateGraphicsPipelineFromSpv(const String& key, const Vector<uint32_t>& vsSpv,
|
|
||||||
const Vector<uint32_t>& fsSpv);
|
|
||||||
|
|
||||||
VkExtent2D GetExtent() const;
|
|
||||||
|
|
||||||
void WaitIdle();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NativeWindowType m_window = 0;
|
NativeWindowType m_window = 0;
|
||||||
RendererConfig m_config;
|
RendererConfig m_config;
|
||||||
|
|
||||||
UniquePtr<VulkanContext> m_context;
|
// Vulkan objects
|
||||||
UniquePtr<SwapchainManager> m_swapchain;
|
Vector<VkExtensionProperties> m_extensions;
|
||||||
UniquePtr<PipelineManager> m_pipelineMgr;
|
VkInstance m_instance = VK_NULL_HANDLE;
|
||||||
|
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkRenderPass m_renderPass = VK_NULL_HANDLE;
|
void CreateInstance();
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
void DestroyInstance();
|
||||||
|
VkResult SetupDebugMessenger();
|
||||||
|
VkResult DestroyDebugMessenger();
|
||||||
|
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
||||||
|
static Vector<VkExtensionProperties> EnumerateExtensions();
|
||||||
|
static constexpr const char* s_validationLayerNames[] = {
|
||||||
|
"VK_LAYER_KHRONOS_validation"
|
||||||
|
};
|
||||||
|
static Bool CheckValidationLayerSupport();
|
||||||
|
|
||||||
Vector<UniquePtr<FrameContext>> m_frames;
|
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(
|
||||||
Uint32 m_currentFrame = 0;
|
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||||
// Render callbacks map
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||||
Vector<std::pair<String, RenderCallback>> m_renderCallbacks;
|
void* pUserData);
|
||||||
|
|
||||||
// Internals
|
|
||||||
void CreateRenderPass();
|
|
||||||
void DestroyRenderPass();
|
|
||||||
void CreateCommandPool();
|
|
||||||
void DestroyCommandPool();
|
|
||||||
void CreateFrameResources();
|
|
||||||
void DestroyFrameResources();
|
|
||||||
void RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex);
|
|
||||||
void RecreateSwapchainIfNeeded();
|
|
||||||
bool FrameBegin();
|
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -75,16 +75,16 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create pipeline
|
// Create pipeline
|
||||||
VkPipeline trianglePipeline = MG_Backend::DirectVulkan::pVulkanRenderer->CreateGraphicsPipelineFromSpv(
|
// VkPipeline trianglePipeline = MG_Backend::DirectVulkan::pVulkanRenderer->CreateGraphicsPipelineFromSpv(
|
||||||
"TrianglePipeline", vsSpv, fsSpv);
|
// "TrianglePipeline", vsSpv, fsSpv);
|
||||||
|
//
|
||||||
// Register render callback
|
// // Register render callback
|
||||||
MG_Backend::DirectVulkan::pVulkanRenderer->RegisterRenderCallback(
|
// MG_Backend::DirectVulkan::pVulkanRenderer->RegisterRenderCallback(
|
||||||
"DrawTriangle", [trianglePipeline](VkCommandBuffer cmd, uint32_t imageIndex, VkExtent2D extent) {
|
// "DrawTriangle", [trianglePipeline](VkCommandBuffer cmd, uint32_t imageIndex, VkExtent2D extent) {
|
||||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, trianglePipeline);
|
// vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, trianglePipeline);
|
||||||
|
//
|
||||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
// vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateWindowSurfaceForVulkan(NativeWindowType window) {
|
void CreateWindowSurfaceForVulkan(NativeWindowType window) {
|
||||||
@@ -106,8 +106,6 @@ namespace MobileGL {
|
|||||||
MGLOG_E("EGLForVulkan::SwapBuffers called but VulkanRenderer is null");
|
MGLOG_E("EGLForVulkan::SwapBuffers called but VulkanRenderer is null");
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
// TODO: replace this with real rendering code
|
|
||||||
MG_Backend::DirectVulkan::pVulkanRenderer->RenderFrame();
|
|
||||||
MG_Backend::DirectVulkan::pVulkanRenderer->Present();
|
MG_Backend::DirectVulkan::pVulkanRenderer->Present();
|
||||||
return EGL_TRUE;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user