mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
346 lines
14 KiB
C++
346 lines
14 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "VulkanRenderer.h"
|
|
#include "VulkanContext.h"
|
|
#include "SwapchainManager.h"
|
|
#include "PipelineManager.h"
|
|
#include "FrameContext.h"
|
|
|
|
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) {
|
|
Initialize();
|
|
}
|
|
|
|
VulkanRenderer::~VulkanRenderer() {
|
|
Shutdown();
|
|
}
|
|
|
|
void VulkanRenderer::Initialize() {
|
|
CreateInstance();
|
|
CreateSurface();
|
|
PickPhysicalDevice();
|
|
CreateLogicalDevice();
|
|
MGLOG_D("VulkanRenderer initialized");
|
|
}
|
|
|
|
void VulkanRenderer::Shutdown() {
|
|
if (m_device != VK_NULL_HANDLE)
|
|
vkDestroyDevice(m_device, nullptr);
|
|
|
|
if (m_surface != VK_NULL_HANDLE)
|
|
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
|
|
|
if (m_debugMessenger != VK_NULL_HANDLE)
|
|
DestroyDebugMessenger();
|
|
|
|
DestroyInstance();
|
|
MGLOG_D("VulkanRenderer shut down completed");
|
|
}
|
|
|
|
void VulkanRenderer::Render() {
|
|
|
|
}
|
|
|
|
void VulkanRenderer::Present() {
|
|
|
|
}
|
|
|
|
void VulkanRenderer::CreateInstance() {
|
|
m_extensions = EnumerateInstanceExtensions();
|
|
MGLOG_I("Got %d Vulkan instance extensions: ", m_extensions.size());
|
|
for (auto& extension : m_extensions) {
|
|
MGLOG_I(" %s (r.%u)", extension.extensionName, extension.specVersion);
|
|
}
|
|
|
|
Bool validationLayerAvailable = CheckValidationLayerSupport();
|
|
MGLOG_D("Validation layers %s.", validationLayerAvailable ? "available" : "not available");
|
|
MGLOG_D("Validation layers %s.", m_config.EnableValidationLayers ? "requested" : "not requested");
|
|
|
|
if (m_config.EnableValidationLayers && !validationLayerAvailable) {
|
|
MOBILEGL_ASSERT(false, "Validation layers requested but not available!");
|
|
}
|
|
|
|
m_validationLayersEnabled = 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 (m_validationLayersEnabled) {
|
|
exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
}
|
|
|
|
instanceInfo.enabledExtensionCount = exts.size();
|
|
instanceInfo.ppEnabledExtensionNames = exts.data();
|
|
|
|
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
|
|
// Layers
|
|
if (m_validationLayersEnabled) {
|
|
MGLOG_I("Enabling validation layer.");
|
|
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
|
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
|
|
instanceInfo.pNext = &debugMessengerCreateInfo;
|
|
} else {
|
|
instanceInfo.enabledLayerCount = 0;
|
|
instanceInfo.pNext = nullptr;
|
|
}
|
|
|
|
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
|
|
|
|
if (m_validationLayersEnabled)
|
|
VK_VERIFY(SetupDebugMessenger());
|
|
}
|
|
|
|
void VulkanRenderer::DestroyInstance() {
|
|
if (m_instance != VK_NULL_HANDLE) {
|
|
vkDestroyInstance(m_instance, nullptr);
|
|
m_instance = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
VkResult VulkanRenderer::SetupDebugMessenger() {
|
|
auto createInfo = PopulateDebugMessengerCreateInfo();
|
|
auto vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT");
|
|
if (!vkCreateDebugUtilsMessengerEXT)
|
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
|
VK_VERIFY(vkCreateDebugUtilsMessengerEXT(m_instance, &createInfo, nullptr, &m_debugMessenger));
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VkResult VulkanRenderer::DestroyDebugMessenger() {
|
|
if (m_debugMessenger != VK_NULL_HANDLE) {
|
|
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;
|
|
}
|
|
|
|
VkDebugUtilsMessengerCreateInfoEXT VulkanRenderer::PopulateDebugMessengerCreateInfo() {
|
|
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::PickPhysicalDevice() {
|
|
Uint32 deviceCount = 0;
|
|
vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr);
|
|
if (deviceCount == 0) {
|
|
MGLOG_E("No physical devices supporting Vulkan found.");
|
|
} else {
|
|
MGLOG_I("Found %d physical device(s).", deviceCount);
|
|
}
|
|
|
|
auto deviceTypeToStr = [](VkPhysicalDeviceType type) {
|
|
switch (type) {
|
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
|
return "INTEGRATED_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
|
return "DISCRETE_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_CPU:
|
|
return "CPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
|
|
return "VIRTUAL_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
|
return "OTHER";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
};
|
|
|
|
Vector<VkPhysicalDevice> devices(deviceCount);
|
|
vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());
|
|
for (const auto& device : devices) {
|
|
VkPhysicalDeviceProperties deviceProperties;
|
|
vkGetPhysicalDeviceProperties(device, &deviceProperties);
|
|
auto apiVersion = deviceProperties.apiVersion;
|
|
MGLOG_I(" %s (Vulkan %d.%d.%d, %s)",
|
|
deviceProperties.deviceName,
|
|
VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion), VK_VERSION_PATCH(apiVersion),
|
|
deviceTypeToStr(deviceProperties.deviceType));
|
|
// TODO: Properly check device eligibility
|
|
m_queueFamilies = GetQueueFamilyFromPhysicalDevice(device);
|
|
auto gfxQueueFamily = GetQueueFamilyIndex(m_queueFamilies, VK_QUEUE_GRAPHICS_BIT);
|
|
if (m_physicalDevice == VK_NULL_HANDLE && gfxQueueFamily != -1) {
|
|
m_physicalDevice = device;
|
|
m_queueFamilyIndices.graphicsFamily = gfxQueueFamily;
|
|
MGLOG_I("Physical device picked.");
|
|
}
|
|
}
|
|
|
|
MOBILEGL_ASSERT(!devices.empty(), "No physical devices found.");
|
|
if (m_physicalDevice == VK_NULL_HANDLE) {
|
|
m_physicalDevice = devices[0];
|
|
MGLOG_I("No suitable physical device picked yet, defaulting to device 0.");
|
|
}
|
|
}
|
|
|
|
void VulkanRenderer::CreateLogicalDevice() {
|
|
VkDeviceQueueCreateInfo queueCreateInfo{};
|
|
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
queueCreateInfo.queueFamilyIndex = m_queueFamilyIndices.graphicsFamily;
|
|
queueCreateInfo.queueCount = 1;
|
|
Float queuePriority = 1.0f;
|
|
queueCreateInfo.pQueuePriorities = &queuePriority;
|
|
|
|
VkPhysicalDeviceFeatures deviceFeatures{};
|
|
// TODO: query and make use of device features
|
|
|
|
VkDeviceCreateInfo deviceCreateInfo{};
|
|
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
|
deviceCreateInfo.queueCreateInfoCount = 1;
|
|
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
|
|
if (m_validationLayersEnabled) {
|
|
deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
|
deviceCreateInfo.ppEnabledLayerNames = s_validationLayerNames;
|
|
} else {
|
|
deviceCreateInfo.enabledLayerCount = 0;
|
|
}
|
|
VK_VERIFY(vkCreateDevice(m_physicalDevice, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
|
|
vkGetDeviceQueue(m_device, m_queueFamilyIndices.graphicsFamily, 0, &m_graphicsQueue);
|
|
}
|
|
|
|
void VulkanRenderer::CreateSurface() {
|
|
#if defined VK_USE_PLATFORM_ANDROID_KHR
|
|
auto* nativeWindow = static_cast<ANativeWindow*>(m_window);
|
|
if (!nativeWindow) throw RuntimeError("ANativeWindowType is null");
|
|
|
|
VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
|
|
sci.window = nativeWindow;
|
|
VK_VERIFY(vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateAndroidSurfaceKHR failed");
|
|
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
|
auto hwnd = static_cast<HWND>(m_window);
|
|
MOBILEGL_ASSERT(hwnd, "HWND is null");
|
|
|
|
VkWin32SurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR};
|
|
sci.hwnd = hwnd;
|
|
VK_VERIFY(vkCreateWin32SurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateWin32SurfaceKHR failed");
|
|
#else
|
|
//#warning "VulkanRenderer::Initialize called on a platform which is not supported yet"
|
|
MGLOG_W("VulkanRenderer::Initialize called on a platform which is not supported yet"); // TODO: support more
|
|
// platforms
|
|
#endif
|
|
}
|
|
|
|
Vector<VkQueueFamilyProperties> VulkanRenderer::GetQueueFamilyFromPhysicalDevice(VkPhysicalDevice device) {
|
|
Uint32 queueFamilyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
|
|
|
Vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
|
return queueFamilies;
|
|
}
|
|
|
|
Uint32 VulkanRenderer::GetQueueFamilyIndex(const Vector<VkQueueFamilyProperties>& queueFamilies, VkQueueFlagBits flag) {
|
|
for (Uint32 i = 0; i < queueFamilies.size(); i++) {
|
|
if (queueFamilies[i].queueFlags & flag) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Vector<VkExtensionProperties> VulkanRenderer::EnumerateInstanceExtensions() {
|
|
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
|