// 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 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(m_swapchainObject.GetImageCount())), "CreateFrameContexts, InitializeSwapchainSemaphores"); MGLOG_I("CreateFrameContexts completed"); } void VulkanRenderer::Initialize() { CreateInstance(); CreateSurface(); PickPhysicalDevice(); CreateLogicalDeviceAndQueues(); CreateAllocator(); CreateCommandPool(); m_textureManager = MakeUnique(); 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(); MOBILEGL_ASSERT(m_clearManager != nullptr, "VkClearManager creation failed."); succeeded = m_clearManager->Initialize(); MOBILEGL_ASSERT(succeeded, "VkClearManager initialization failed."); m_renderPassManager = MakeUnique(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(m_device, m_config); MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed."); m_programFactory = MakeUnique(m_device, m_config); MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed."); m_samplerManager = MakeUnique(); 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(); 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(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(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 vkBuffers(bindingCount, VK_NULL_HANDLE); Vector 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(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(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(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(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 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(renderPassEntry.extent.x()); viewport.height = static_cast(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(payload.count), 1, static_cast(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(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(indexSize); const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1); const VkDeviceSize writeEnd = writeOffset + static_cast(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(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(payload.count), 1, 0, static_cast(payload.baseVertex), 0); } void VulkanRenderer::MultiDrawElements(const Vector& 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 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(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 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 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 availableExtensions = EnumerateDeviceExtensions(device); MGLOG_I("Got %u Vulkan device extensions: ", static_cast(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 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(std::size(s_validationLayerNames)); deviceCreateInfo.ppEnabledLayerNames = s_validationLayerNames; } else { deviceCreateInfo.enabledLayerCount = 0; } Vector enabledDeviceExtensions; enabledDeviceExtensions.reserve(std::size(s_deviceExtensionNames) + 2); for (const char* extensionName : s_deviceExtensionNames) { enabledDeviceExtensions.push_back(extensionName); } const Vector 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(enabledDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice"); m_cmdDrawIndexedIndirectCount = reinterpret_cast( vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR")); if (m_cmdDrawIndexedIndirectCount == nullptr) { m_cmdDrawIndexedIndirectCount = reinterpret_cast( 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(m_physicalDevice.queueFamilies.graphicsFamily), static_cast(m_physicalDevice.queueFamilies.presentFamily), m_config.MaxFramesInFlight); } Uint64 VulkanRenderer::BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer) { return (static_cast(targetsDefaultFramebuffer ? 1 : 0) << 63) | static_cast(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(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(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(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 VulkanRenderer::GetQueueFamilyFromPhysicalDevice(VkPhysicalDevice device) { Uint32 queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); Vector queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); return queueFamilies; } Int VulkanRenderer::GetQueueFamilyIndex(const Vector& 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& 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 VulkanRenderer::EnumerateInstanceExtensions() { Uint32 extensionCount = 0; VK_VERIFY(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr)); Vector extensions(extensionCount); vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); return extensions; } Vector VulkanRenderer::EnumerateDeviceExtensions(VkPhysicalDevice device) { Uint32 extensionCount = 0; VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr)); Vector extensions(extensionCount); VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data())); return extensions; } Bool VulkanRenderer::IsExtensionSupported(const Vector& availableExtensions, const char* extensionName) { for (const auto& extension : availableExtensions) { if (strcmp(extension.extensionName, extensionName) == 0) { return true; } } return false; } Bool VulkanRenderer::IsExtensionAlreadyEnabled(const Vector& enabledExtensions, const char* extensionName) { for (const char* enabledExtensionName : enabledExtensions) { if (strcmp(enabledExtensionName, extensionName) == 0) { return true; } } return false; } Bool VulkanRenderer::EnableOptionalDeviceExtension(const Vector& availableExtensions, Vector& 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& availableExtensions, Vector& 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 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(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