Files
MobileGL/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp
T

1241 lines
58 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 "VertexInputStateFactory.h"
#include "VertexInputStateBuilder.h"
#include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
#include <vulkan/vulkan_core.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 VulkanRendererConfig& cfg)
: m_window(window), m_config(cfg) {
// Initialize();
}
VulkanRenderer::~VulkanRenderer() {
Shutdown();
}
inline ProgramFactory::CompileOptionFlags GetShaderTransformFlags(VkSurfaceTransformFlagBitsKHR preTransform) {
ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap;
const auto& currentDrawFBO =
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
if (currentDrawFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) {
flags |= ProgramFactory::CompileOptionBit::PositionYFlip;
switch (preTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
flags |= ProgramFactory::CompileOptionBit::SurfaceRotate90;
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
flags |= ProgramFactory::CompileOptionBit::SurfaceRotate180;
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
flags |= ProgramFactory::CompileOptionBit::SurfaceRotate270;
break;
default:
break;
}
}
return flags;
}
void VulkanRenderer::CreateFrameContexts() {
VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight),
"CreateFrameContexts");
VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(m_device,
static_cast<Uint32>(m_swapchainObject.GetImageCount())),
"CreateFrameContexts, InitializeSwapchainSemaphores");
MGLOG_I("CreateFrameContexts completed");
}
void VulkanRenderer::Initialize() {
CreateInstance();
CreateSurface();
PickPhysicalDevice();
CreateLogicalDeviceAndQueues();
CreateAllocator();
CreateCommandPool();
m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
auto succeeded = false;
succeeded = m_textureManager->Initialize(
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue});
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
m_clearManager = MakeUnique<VkClearManager>();
MOBILEGL_ASSERT(m_clearManager != nullptr, "VkClearManager creation failed.");
succeeded = m_clearManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkClearManager initialization failed.");
m_renderPassManager = MakeUnique<VkRenderPassManager>(m_device, m_config, *m_clearManager, *m_textureManager);
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "VkRenderPassManager creation failed.");
succeeded = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
RecreateSwapchain();
m_pipelineFactory = MakeUnique<PipelineFactory>(m_device, m_config);
MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed.");
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config);
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
m_samplerManager = MakeUnique<VkSamplerManager>();
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
succeeded = m_samplerManager->Initialize({m_device, &m_config});
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed.");
succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator,
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment,
m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024,
m_textureManager.get(), m_samplerManager.get());
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);
MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed.");
CreateFrameContexts();
m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
m_deferredBufferReleases.clear();
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
// Prime the first frame so Render() always targets an acquired swapchain image.
VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired),
"Initialize, WaitAndAcquireNextImage");
MGLOG_D("VulkanRenderer initialized");
}
void VulkanRenderer::Shutdown() {
VK_VERIFY(vkDeviceWaitIdle(m_device));
m_pipelineFactory.reset();
m_programFactory.reset();
if (m_samplerManager) {
m_samplerManager->Shutdown();
m_samplerManager.reset();
}
if (m_textureManager) {
m_textureManager->Shutdown();
m_textureManager.reset();
}
m_vertexInputStateFactory.reset();
for (auto& buffer : m_frameVertexUploadBuffers) {
buffer.Destroy();
}
for (auto& buffer : m_frameIndexUploadBuffers) {
buffer.Destroy();
}
m_frameVertexUploadBuffers.clear();
m_frameVertexUploadHeads.clear();
m_frameIndexUploadBuffers.clear();
m_frameIndexUploadHeads.clear();
m_deferredBufferReleases.clear();
m_frameContext.Destroy(m_device, m_commandPool);
if (m_uniformDescriptorBinder) {
m_uniformDescriptorBinder->Shutdown();
m_uniformDescriptorBinder.reset();
}
ShutdownSwapchain();
m_renderPassManager.reset();
if (m_clearManager) {
m_clearManager->Shutdown();
m_clearManager.reset();
}
if (m_commandPool != VK_NULL_HANDLE) {
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
m_commandPool = VK_NULL_HANDLE;
}
DestroyAllocator();
if (m_device != VK_NULL_HANDLE) {
vkDestroyDevice(m_device, nullptr);
m_device = VK_NULL_HANDLE;
}
m_cmdDrawIndexedIndirectCount = nullptr;
if (m_surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
m_surface = VK_NULL_HANDLE;
}
if (m_debugMessenger != VK_NULL_HANDLE) {
DestroyDebugMessenger();
m_debugMessenger = VK_NULL_HANDLE;
}
if (m_instance != VK_NULL_HANDLE) {
vkDestroyInstance(m_instance, nullptr);
m_instance = VK_NULL_HANDLE;
}
MGLOG_I("VulkanRenderer shut down completed");
}
void VulkanRenderer::TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex) {
auto layout = m_swapchainObject.GetImageLayout(imageIndex);
Bool ok = VkTextureManager::TransitionImageLayout(commandBuffer, m_swapchainObject.GetImage(imageIndex),
layout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
MOBILEGL_ASSERT(ok, "TransitionSwapchainImageToColorAttachment failed");
m_swapchainObject.SetImageLayout(imageIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
void VulkanRenderer::TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex) {
if (m_swapchainObject.GetDepthStencilFormat() == VK_FORMAT_UNDEFINED) {
return;
}
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
const auto depthStencilFormat = m_swapchainObject.GetDepthStencilFormat();
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT || depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
auto oldLayout = m_swapchainObject.GetDepthStencilImageLayout(imageIndex);
Bool ok = VkTextureManager::TransitionImageLayout(
commandBuffer, m_swapchainObject.GetDepthStencilImage(imageIndex),
oldLayout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, aspectMask);
MOBILEGL_ASSERT(ok, "TransitionDepthStencilImageToAttachment failed");
m_swapchainObject.SetDepthStencilImageLayout(imageIndex, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
}
void VulkanRenderer::EndFrameRecordingIfNeeded() {
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
return;
}
m_frameContext.EndCommandRecording();
}
void VulkanRenderer::DeferDestroyBuffer(VkBufferObject& buffer) {
if (!buffer.IsValid()) {
return;
}
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
if (m_deferredBufferReleases.size() < m_frameContext.GetFrameCount()) {
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
}
m_deferredBufferReleases[frameIndex].push_back(std::move(buffer));
}
void VulkanRenderer::CollectDeferredBufferReleases(Uint32 frameIndex) {
if (frameIndex >= m_deferredBufferReleases.size()) {
return;
}
m_deferredBufferReleases[frameIndex].clear();
}
Bool VulkanRenderer::EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer,
VkDeviceSize requiredEndOffset, VkDeviceSize minCapacity,
VkBufferUsageFlags usage) {
auto& buffers = isIndexBuffer ? m_frameIndexUploadBuffers : m_frameVertexUploadBuffers;
auto& heads = isIndexBuffer ? m_frameIndexUploadHeads : m_frameVertexUploadHeads;
if (frameIndex >= buffers.size() || frameIndex >= heads.size()) {
return false;
}
auto& uploadBuffer = buffers[frameIndex];
if (uploadBuffer.IsValid() && uploadBuffer.GetSize() >= requiredEndOffset) {
return true;
}
VkDeviceSize newCapacity = uploadBuffer.IsValid() ? uploadBuffer.GetSize() : 0;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
while (newCapacity < requiredEndOffset) {
newCapacity *= 2;
}
DeferDestroyBuffer(uploadBuffer);
if (!uploadBuffer.Create(m_allocator, newCapacity, usage, VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT)) {
MGLOG_E("EnsureFrameUploadBufferCapacity failed: create upload buffer (index=%d, capacity=%zu)",
isIndexBuffer, static_cast<SizeT>(newCapacity));
return false;
}
heads[frameIndex] = 0;
return true;
}
Bool VulkanRenderer::UploadAndBindVertexStreams(
VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) {
auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
const auto bindingCount = vertexInputState.bindings.size();
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
const auto& attrs = vao.GetAllAttributes();
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
const auto& attr = attrs[location];
if (!attr.Buffer) {
continue;
}
const auto* buffer = attr.Buffer.get();
if (reinterpret_cast<SizeT>(buffer) == bufferKey) {
return buffer;
}
}
return nullptr;
};
for (SizeT binding = 0; binding < bindingCount; ++binding) {
const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding];
const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey);
const auto sourceData = sourceBuffer->GetDataReadOnly();
const SizeT sourceSize = sourceBuffer->GetSize();
VkDeviceSize& frameHead = m_frameVertexUploadHeads[frameIndex];
const VkDeviceSize writeOffset = (frameHead + 0x0F) & ~VkDeviceSize(0x0F);
const VkDeviceSize writeEnd = writeOffset + static_cast<VkDeviceSize>(sourceSize);
if (!EnsureFrameUploadBufferCapacity(frameIndex, false, writeEnd, 4 * 1024 * 1024,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) {
return false;
}
auto& frameUploadBuffer = m_frameVertexUploadBuffers[frameIndex];
if (!frameUploadBuffer.Upload(sourceData->data(), static_cast<VkDeviceSize>(sourceSize), writeOffset)) {
MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding);
return false;
}
frameHead = writeEnd;
vkBuffers[binding] = frameUploadBuffer.GetHandle();
vkOffsets[binding] = writeOffset;
}
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
return true;
}
VkPipeline VulkanRenderer::GetOrCreatePipeline(
GLenum mode,
const MG_State::GLState::ProgramObject& program,
const MG_State::GLState::VertexArrayObject& vao,
const MG_State::GLState::FramebufferObject& drawFbo) {
auto toVkTopology = [](GLenum mode) -> VkPrimitiveTopology {
switch (mode) {
case GL_POINTS:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case GL_LINES:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case GL_LINE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case GL_TRIANGLES:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case GL_TRIANGLE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case GL_TRIANGLE_FAN:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
case GL_LINE_LOOP:
default:
MGLOG_W("Unrecognized primitive topology");
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
}
};
auto toVkCompareOp = [](DepthTestFunc func) -> VkCompareOp {
switch (func) {
case DepthTestFunc::Never:
return VK_COMPARE_OP_NEVER;
case DepthTestFunc::Less:
return VK_COMPARE_OP_LESS;
case DepthTestFunc::Equal:
return VK_COMPARE_OP_EQUAL;
case DepthTestFunc::LessEqual:
return VK_COMPARE_OP_LESS_OR_EQUAL;
case DepthTestFunc::Greater:
return VK_COMPARE_OP_GREATER;
case DepthTestFunc::NotEqual:
return VK_COMPARE_OP_NOT_EQUAL;
case DepthTestFunc::GreaterEqual:
return VK_COMPARE_OP_GREATER_OR_EQUAL;
case DepthTestFunc::Always:
default:
return VK_COMPARE_OP_ALWAYS;
}
};
auto toVkBlendFactor = [](BlendFactor factor) -> VkBlendFactor {
switch (factor) {
case BlendFactor::Zero:
return VK_BLEND_FACTOR_ZERO;
case BlendFactor::One:
return VK_BLEND_FACTOR_ONE;
case BlendFactor::SrcColor:
return VK_BLEND_FACTOR_SRC_COLOR;
case BlendFactor::OneMinusSrcColor:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
case BlendFactor::DstColor:
return VK_BLEND_FACTOR_DST_COLOR;
case BlendFactor::OneMinusDstColor:
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
case BlendFactor::SrcAlpha:
return VK_BLEND_FACTOR_SRC_ALPHA;
case BlendFactor::OneMinusSrcAlpha:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
case BlendFactor::DstAlpha:
return VK_BLEND_FACTOR_DST_ALPHA;
case BlendFactor::OneMinusDstAlpha:
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
case BlendFactor::ConstantColor:
return VK_BLEND_FACTOR_CONSTANT_COLOR;
case BlendFactor::OneMinusConstantColor:
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
case BlendFactor::ConstantAlpha:
return VK_BLEND_FACTOR_CONSTANT_ALPHA;
case BlendFactor::OneMinusConstantAlpha:
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
default:
return VK_BLEND_FACTOR_ONE;
}
};
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(program, transformFlags);
if (stages.empty()) {
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
return VK_NULL_HANDLE;
}
const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags);
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
auto pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(program);
auto renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(drawFbo);
auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest);
BlendFactor srcRGB = BlendFactor::One;
BlendFactor dstRGB = BlendFactor::Zero;
BlendFactor srcAlpha = BlendFactor::One;
BlendFactor dstAlpha = BlendFactor::Zero;
MG_State::pGLContext->GetBlendFunc(srcRGB, dstRGB, srcAlpha, dstAlpha);
auto mask = MG_State::pGLContext->GetColorMask();
PipelineFactory::PipelineCreatePayload payload {
.programHash = programHash,
.vertexInputHash = vertexInputHash,
.pipelineLayout = pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.subpass = renderPassEntry.subpass,
.topology = toVkTopology(mode),
.depthTestEnable = depthTestEnabled,
.depthWriteEnable = depthTestEnabled && MG_State::pGLContext->GetDepthMask(),
.depthCompareOp = toVkCompareOp(MG_State::pGLContext->GetDepthFunc()),
.blendEnable = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::Blend),
.srcColorBlendFactor = toVkBlendFactor(srcRGB),
.dstColorBlendFactor = toVkBlendFactor(dstRGB),
.srcAlphaBlendFactor = toVkBlendFactor(srcAlpha),
.dstAlphaBlendFactor = toVkBlendFactor(dstAlpha),
.colorWriteMask = (
(mask.r() ? VK_COLOR_COMPONENT_R_BIT : 0u) |
(mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
(mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) |
(mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ),
.stages = &stages,
.vertexInputState = &vis.state
};
return m_pipelineFactory->GetOrCreatePipeline(payload);
}
void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects) {
// Begin command recording if not yet
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
// Begin render pass
// TODO: properly deal with clear
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*drawFbo);
if (m_activeRenderPass != &renderPassEntry) {
if (m_activeRenderPass) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
m_activeRenderPass = &renderPassEntry;
Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry);
MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__);
} else {
// We probably already have one compatible render pass running. Keep going.
}
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
auto pipeline = GetOrCreatePipeline(mode, program, vao, *drawFbo);
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program,
m_frameContext.GetCurrentFrameIndex());
UploadAndBindVertexStreams(frame.commandBuffer, vao);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(renderPassEntry.extent.x());
viewport.height = static_cast<float>(renderPassEntry.extent.y());
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() };
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
}
void VulkanRenderer::Clear(GLbitfield mask) {
m_clearManager->CollectGarbage();
auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
if (!fbo) {
MGLOG_D("VulkanRenderer::Clear: draw framebuffer not found");
}
ClearFramebufferPayload payload {
.color = MG_State::pGLContext->GetClearColor(),
.depth = MG_State::pGLContext->GetClearDepth(),
.stencil = MG_State::pGLContext->GetClearStencil()
};
m_clearManager->QueueClear(mask, payload, *fbo);
}
void VulkanRenderer::DrawArrays(const DrawArrayCmd& payload) {
auto& frame = m_frameContext.GetCurrent();
SetupDraw(frame, payload.mode, 0);
MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__);
VkCommandBuffer& commandBuffer = frame.commandBuffer;
vkCmdDraw(commandBuffer, static_cast<Uint32>(payload.count), 1, static_cast<Uint32>(payload.first), 0);
}
void VulkanRenderer::DrawElements(const DrawElementCmd& payload) {
auto& frame = m_frameContext.GetCurrent();
SetupDraw(frame, payload.mode, 0);
if (!frame.isCommandRecording) {
MGLOG_D("DrawElements skipped: frame recording was not started");
return;
}
VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM;
switch (payload.indexType) {
case GL_UNSIGNED_SHORT:
vkIndexType = VK_INDEX_TYPE_UINT16;
break;
case GL_UNSIGNED_INT:
vkIndexType = VK_INDEX_TYPE_UINT32;
break;
default:
MGLOG_D("DrawElements skipped: index type %u is not supported yet", payload.indexType);
return;
}
auto* vao = MG_State::pGLContext->GetBoundVertexArray().get();
const auto* indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject().get();
const auto indexData = indexBuffer->GetDataReadOnly();
MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data");
const SizeT indexSize = (payload.indexType == GL_UNSIGNED_SHORT) ? sizeof(Uint16) : sizeof(Uint32);
const SizeT indexDataSizeBytes = static_cast<SizeT>(payload.count) * indexSize;
MOBILEGL_ASSERT(payload.indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(),
"DrawElements index range out of bounds");
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex];
const VkDeviceSize alignment = static_cast<VkDeviceSize>(indexSize);
const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1);
const VkDeviceSize writeEnd = writeOffset + static_cast<VkDeviceSize>(indexDataSizeBytes);
if (!EnsureFrameUploadBufferCapacity(frameIndex, true, writeEnd, 1 * 1024 * 1024,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) {
MGLOG_E("DrawElements skipped: failed to prepare index upload buffer");
return;
}
auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex];
if (!frameIndexUploadBuffer.Upload(indexData->data() + payload.indexByteOffset,
static_cast<VkDeviceSize>(indexDataSizeBytes), writeOffset)) {
MGLOG_E("DrawElements skipped: failed to upload index data");
return;
}
frameIndexHead = writeEnd;
VkCommandBuffer& commandBuffer = frame.commandBuffer;
vkCmdBindIndexBuffer(commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType);
vkCmdDrawIndexed(commandBuffer, static_cast<Uint32>(payload.count), 1, 0,
static_cast<Int32>(payload.baseVertex), 0);
}
void VulkanRenderer::MultiDrawElements(const Vector<DrawElementCmd>& payloads) {
}
void VulkanRenderer::Present() {
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
"Present, acquired image index out of range");
auto& frame = m_frameContext.GetCurrent();
EndFrameRecordingIfNeeded();
const auto acquiredImageLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
const Bool needsLayoutTransitionForPresent =
m_frameContext.TransitionToPresent(m_swapchainObject.GetImage(m_imageIndexAcquired), acquiredImageLayout);
const Bool shouldSubmitCommandBuffer = frame.hasCommandBufferRecorded || needsLayoutTransitionForPresent;
// 1) Submit current frame work.
auto submitPacket = m_frameContext.GetSubmitInfo(shouldSubmitCommandBuffer, m_imageIndexAcquired);
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitPacket.submitInfo, frame.imageInFlightFence));
frame.isCommandRecording = false;
frame.hasCommandBufferRecorded = false;
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
// 2) Present current frame.
auto presentPacket = m_frameContext.GetPresentInfo(m_swapchainObject.GetHandle(), m_imageIndexAcquired);
auto result = vkQueuePresentKHR(m_presentQueue, &presentPacket.presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
MGLOG_D("Present, vkQueuePresentKHR got %d, recreating swapchain", result);
RecreateSwapchain();
result = VK_SUCCESS;
}
VK_VERIFY(result, "Present, vkQueuePresentKHR");
// 3) Advance frame slot.
m_frameContext.AdvanceToNext();
// 4) Wait/reset/acquire for next frame.
result = m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
MGLOG_D("Present, vkAcquireNextImageKHR got %d, recreating swapchain", result);
RecreateSwapchain();
result = VK_SUCCESS;
}
VK_VERIFY(result, "Present, vkAcquireNextImageKHR");
CollectDeferredBufferReleases(m_frameContext.GetCurrentFrameIndex());
if (m_frameContext.GetCurrentFrameIndex() < m_frameVertexUploadHeads.size()) {
m_frameVertexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
}
if (m_frameContext.GetCurrentFrameIndex() < m_frameIndexUploadHeads.size()) {
m_frameIndexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0;
}
}
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_I("Validation layers %s.", validationLayerAvailable ? "available" : "not available");
MGLOG_I("Validation layers %s.", m_config.EnableValidationLayers ? "requested" : "not requested");
if (m_config.EnableValidationLayers && !validationLayerAvailable) {
MGLOG_I("Validation layers not available! Disabling validation layers.");
}
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
#elif defined VK_USE_PLATFORM_METAL_EXT
VK_EXT_METAL_SURFACE_EXTENSION_NAME
#else
#warning "VulkanContext::CreateInstance: VK_KHR_*_surface extension not defined on this platform"
#endif
}; // TODO: support more platforms
#if defined(VK_USE_PLATFORM_METAL_EXT)
exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
instanceInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
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());
}
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);
}
MOBILEGL_ASSERT(deviceCount > 0, "No physical devices found.");
Vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());
for (Int i = 0; i < deviceCount; i++) {
if (GetMoreCapablePhysicalDevice(devices[i], m_surface, m_physicalDevice, m_physicalDevice))
MGLOG_I("Picked physical device %d.", i);
}
if (m_physicalDevice.handle == VK_NULL_HANDLE) {
m_physicalDevice.handle = devices[0];
vkGetPhysicalDeviceProperties(devices[0], &m_physicalDevice.properties);
MGLOG_I("No suitable physical device picked yet, defaulting to device 0.");
MGLOG_W("No graphics queue found on physical device. Picking a device that doesn't do graphics?");
}
}
Bool VulkanRenderer::GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
const PhysicalDevice& otherDevice,
PhysicalDevice& outBetterDevice) {
const 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";
}
};
PhysicalDevice newDevice;
newDevice.handle = newVkDevice;
vkGetPhysicalDeviceProperties(newVkDevice, &newDevice.properties);
const auto& deviceProperties = newDevice.properties;
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));
// Check device extensions (including swapchain extension)
Bool deviceExtSupported = IsNecessaryDeviceExtensionSupported(newVkDevice);
if (!deviceExtSupported) {
outBetterDevice = otherDevice;
MGLOG_I(" Ignored physical device. (Reason: Some of the required device extension not supported on this "
"device)");
return false;
}
// Check swapchain capabilities
auto swapchainCapabilities = SwapchainObject::GetSwapchainCapabilities(newVkDevice, surface);
if (!swapchainCapabilities.IsComplete()) {
outBetterDevice = otherDevice;
MGLOG_I(" Ignored physical device. (Reason: Swapchain capabilities not met)");
return false;
}
// Check queue families
Vector<VkQueueFamilyProperties> queueFamilies = GetQueueFamilyFromPhysicalDevice(newVkDevice);
newDevice.queueFamilies.graphicsFamily = GetQueueFamilyIndex(queueFamilies, VK_QUEUE_GRAPHICS_BIT);
if (newDevice.queueFamilies.graphicsFamily == -1) {
outBetterDevice = otherDevice;
MGLOG_I(" Ignored physical device. (Reason: No graphics queue family)");
return false;
}
newDevice.queueFamilies.presentFamily =
GetPresentQueueFamilyIndex(newDevice, surface, queueFamilies, newDevice.queueFamilies.graphicsFamily);
if (newDevice.queueFamilies.presentFamily == -1) {
outBetterDevice = otherDevice;
MGLOG_I(" Ignored physical device. (Reason: No present queue family)");
return false;
}
// Pick discrete GPU
if (newDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
otherDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
outBetterDevice = newDevice;
MGLOG_I(" Picked physical device. (Reason: Discrete GPU)");
return true;
}
// Pick integrated GPU if no discrete GPU
if (newDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
otherDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
outBetterDevice = newDevice;
MGLOG_I(" Picked physical device. (Reason: Integrated GPU and no discrete one found yet)");
return true;
}
// Ignore other GPU when discrete GPU found
if (newDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
otherDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
outBetterDevice = newDevice;
MGLOG_I(" Ignored physical device. (Reason: Already picked discrete GPU)");
return false;
}
return false;
}
Bool VulkanRenderer::IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device) {
const Vector<VkExtensionProperties> availableExtensions = EnumerateDeviceExtensions(device);
MGLOG_I("Got %u Vulkan device extensions: ", static_cast<Uint32>(availableExtensions.size()));
for (auto& extension : availableExtensions) {
MGLOG_I(" %s (r.%u)", extension.extensionName, extension.specVersion);
}
for (SizeT i = 0; i < std::size(s_deviceExtensionNames); ++i) {
if (!IsExtensionSupported(availableExtensions, s_deviceExtensionNames[i])) {
MGLOG_I("Required extension not found: %s", s_deviceExtensionNames[i]);
return false;
}
MGLOG_I("Required extension found: %s", s_deviceExtensionNames[i]);
}
return true;
}
void VulkanRenderer::CreateLogicalDeviceAndQueues() {
Float queuePriority = 1.0f;
Vector<VkDeviceQueueCreateInfo> queueCreateInfos;
MOBILEGL_ASSERT(m_physicalDevice.queueFamilies.graphicsFamily != -1, "Graphics queue family not found.");
VkDeviceQueueCreateInfo& gfxQueueCreateInfo = queueCreateInfos.emplace_back();
gfxQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
gfxQueueCreateInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.graphicsFamily;
gfxQueueCreateInfo.queueCount = 1;
gfxQueueCreateInfo.pQueuePriorities = &queuePriority;
if (m_physicalDevice.queueFamilies.graphicsFamily != m_physicalDevice.queueFamilies.presentFamily) {
MOBILEGL_ASSERT(m_physicalDevice.queueFamilies.presentFamily != -1, "Present queue family not found.");
VkDeviceQueueCreateInfo& presentQueueCreateInfo = queueCreateInfos.emplace_back();
presentQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
presentQueueCreateInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.presentFamily;
presentQueueCreateInfo.queueCount = 1;
presentQueueCreateInfo.pQueuePriorities = &queuePriority;
}
VkPhysicalDeviceFeatures deviceFeatures{};
// TODO: query and make use of device features
VkDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
deviceCreateInfo.queueCreateInfoCount = queueCreateInfos.size();
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
if (m_validationLayersEnabled) {
deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
deviceCreateInfo.ppEnabledLayerNames = s_validationLayerNames;
} else {
deviceCreateInfo.enabledLayerCount = 0;
}
Vector<const char*> enabledDeviceExtensions;
enabledDeviceExtensions.reserve(std::size(s_deviceExtensionNames) + 2);
for (const char* extensionName : s_deviceExtensionNames) {
enabledDeviceExtensions.push_back(extensionName);
}
const Vector<VkExtensionProperties> availableExtensions = EnumerateDeviceExtensions(m_physicalDevice.handle);
ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions);
MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false");
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
m_cmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR"));
if (m_cmdDrawIndexedIndirectCount == nullptr) {
m_cmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount"));
}
if (m_drawIndirectCountExtensionEnabled && m_cmdDrawIndexedIndirectCount == nullptr) {
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing");
}
MGLOG_I("Logical device created.");
// Queues
vkGetDeviceQueue(m_device, m_physicalDevice.queueFamilies.graphicsFamily, 0, &m_graphicsQueue);
vkGetDeviceQueue(m_device, m_physicalDevice.queueFamilies.presentFamily, 0, &m_presentQueue);
MGLOG_I("Queues got successfully.");
}
void VulkanRenderer::CreateAllocator() {
MOBILEGL_ASSERT(m_instance != VK_NULL_HANDLE, "CreateAllocator requires valid VkInstance");
MOBILEGL_ASSERT(m_physicalDevice.handle != VK_NULL_HANDLE, "CreateAllocator requires valid physical device");
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "CreateAllocator requires valid VkDevice");
if (m_allocator != nullptr) {
return;
}
VmaAllocatorCreateInfo allocatorInfo{};
VmaVulkanFunctions vulkanFunctions{};
vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
allocatorInfo.instance = m_instance;
allocatorInfo.physicalDevice = m_physicalDevice.handle;
allocatorInfo.device = m_device;
allocatorInfo.pVulkanFunctions = &vulkanFunctions;
allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0;
VK_VERIFY(vmaCreateAllocator(&allocatorInfo, &m_allocator), "vmaCreateAllocator");
}
void VulkanRenderer::DestroyAllocator() {
if (m_allocator != nullptr) {
vmaDestroyAllocator(m_allocator);
m_allocator = nullptr;
}
}
void VulkanRenderer::CreateSwapchain() {
m_swapchainObject.Create(m_device, m_physicalDevice.handle, m_surface,
static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily),
static_cast<Uint32>(m_physicalDevice.queueFamilies.presentFamily),
m_config.MaxFramesInFlight);
}
Uint64 VulkanRenderer::BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer) {
return (static_cast<Uint64>(targetsDefaultFramebuffer ? 1 : 0) << 63) |
static_cast<Uint64>(drawFboExternalIndex);
}
void VulkanRenderer::CreateCommandPool() {
VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
createInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.graphicsFamily;
VK_VERIFY(vkCreateCommandPool(m_device, &createInfo, nullptr, &m_commandPool));
MGLOG_I("Command pool created");
}
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");
#elif defined VK_USE_PLATFORM_METAL_EXT
MOBILEGL_ASSERT(m_window, "CAMetalLayer is null");
VkMetalSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT};
sci.pLayer = reinterpret_cast<const void*>(m_window);
VK_VERIFY(vkCreateMetalSurfaceEXT(m_instance, &sci, nullptr, &m_surface), "vkCreateMetalSurfaceEXT 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;
}
Int 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;
}
Int VulkanRenderer::GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
const Vector<VkQueueFamilyProperties>& queueFamilies,
Int preferredFamilyIndex) {
if (preferredFamilyIndex != -1) {
VkBool32 supportsPresent = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice.handle, preferredFamilyIndex, surface,
&supportsPresent);
if (supportsPresent) return preferredFamilyIndex;
}
for (Uint32 i = 0; i < queueFamilies.size(); i++) {
VkBool32 supportsPresent = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice.handle, i, surface, &supportsPresent);
if (supportsPresent) 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;
}
Vector<VkExtensionProperties> VulkanRenderer::EnumerateDeviceExtensions(VkPhysicalDevice device) {
Uint32 extensionCount = 0;
VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr));
Vector<VkExtensionProperties> extensions(extensionCount);
VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data()));
return extensions;
}
Bool VulkanRenderer::IsExtensionSupported(const Vector<VkExtensionProperties>& availableExtensions,
const char* extensionName) {
for (const auto& extension : availableExtensions) {
if (strcmp(extension.extensionName, extensionName) == 0) {
return true;
}
}
return false;
}
Bool VulkanRenderer::IsExtensionAlreadyEnabled(const Vector<const char*>& enabledExtensions,
const char* extensionName) {
for (const char* enabledExtensionName : enabledExtensions) {
if (strcmp(enabledExtensionName, extensionName) == 0) {
return true;
}
}
return false;
}
Bool VulkanRenderer::EnableOptionalDeviceExtension(const Vector<VkExtensionProperties>& availableExtensions,
Vector<const char*>& inOutEnabledExtensions,
const char* extensionName) {
if (!IsExtensionSupported(availableExtensions, extensionName)) {
MGLOG_I("Optional device extension not supported: %s", extensionName);
return false;
}
if (!IsExtensionAlreadyEnabled(inOutEnabledExtensions, extensionName)) {
inOutEnabledExtensions.push_back(extensionName);
}
MGLOG_I("Enabled optional device extension: %s", extensionName);
return true;
}
void VulkanRenderer::ResolveOptionalDeviceExtensions(const Vector<VkExtensionProperties>& availableExtensions,
Vector<const char*>& inOutEnabledExtensions) {
m_drawIndirectCountExtensionEnabled = EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME);
#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
#endif
}
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;
}
void VulkanRenderer::ShutdownSwapchain() {
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "ShutdownSwapchain: render pass manager is null");
m_renderPassManager->Shutdown();
m_swapchainObject.Shutdown(m_device);
}
void VulkanRenderer::RecreateSwapchain() {
// Handle cases like minimize on Windows, where swapchain could return a 0x0 extent
const auto swapchainCapabilities =
SwapchainObject::GetSwapchainCapabilities(m_physicalDevice.handle, m_surface);
if (swapchainCapabilities.capabilities.currentExtent.width == 0 ||
swapchainCapabilities.capabilities.currentExtent.height == 0) {
return;
}
vkDeviceWaitIdle(m_device);
ShutdownSwapchain();
CreateSwapchain();
VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(m_device,
static_cast<Uint32>(m_swapchainObject.GetImageCount())),
"RecreateSwapchain, InitializeSwapchainSemaphores");
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "RecreateSwapchain: render pass manager is null");
Bool ok = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(ok, "RecreateSwapchain: render pass manager initialization failed");
if (m_pipelineFactory) {
m_pipelineFactory->DestroyAll();
}
if (m_frameContext.GetFrameCount() > 0) {
m_frameContext.GetCurrent().isCommandRecording = false;
m_frameContext.GetCurrent().hasCommandBufferRecorded = false;
}
m_deferredBufferReleases.clear();
m_deferredBufferReleases.resize(m_frameContext.GetFrameCount());
m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount());
m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0);
}
const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const {
return m_physicalDevice;
}
Bool VulkanRenderer::IsDrawIndirectCountExtensionEnabled() const {
return m_drawIndirectCountExtensionEnabled;
}
} // namespace MobileGL::MG_Backend::DirectVulkan