[Feat] (MG_Backend/DirectVulkan): ProgramFactory and some refactoring

This commit is contained in:
2026-02-16 22:29:04 +08:00
parent 91326025f3
commit 2ad1207a31
10 changed files with 197 additions and 57 deletions
+3 -5
View File
@@ -211,9 +211,7 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
# MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp
# MobileGL/MG_Backend/DirectVulkan/Managers/VertexInputStateManager.cpp
# MobileGL/MG_Backend/DirectVulkan/Managers/RenderStateManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp
MobileGL/MG_State/GLState/Core.cpp
MobileGL/MG_State/GLState/ErrorState/Error.cpp
@@ -282,7 +280,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
)
target_link_libraries(${CMAKE_PROJECT_NAME}
PRIVATE
PUBLIC
${MOBILEGL_LINK_LIBRARIES}
)
@@ -310,7 +308,7 @@ if(NOT ANDROID)
)
target_link_libraries(${CMAKE_PROJECT_NAME}_s
PRIVATE
PUBLIC
${MOBILEGL_LINK_LIBRARIES}
)
endif()
+3
View File
@@ -50,6 +50,9 @@
// Include FastSTL
#include <FastSTL/UnorderedMap.h>
// Include xxHash
#include <xxhash.h>
// Include spirv_cross
#include <spirv_cross/spirv_cross_c.h>
@@ -0,0 +1,86 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.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 "ProgramFactory.h"
namespace MobileGL::MG_Backend::DirectVulkan {
ProgramFactory::~ProgramFactory() {}
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
switch (stage) {
case ShaderStage::Vertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case ShaderStage::Fragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case ShaderStage::Geometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case ShaderStage::TessControl:
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
case ShaderStage::TessEval:
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
case ShaderStage::Compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
default:
return VK_SHADER_STAGE_ALL_GRAPHICS;
}
}
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
CompileOptionFlags flags) const {
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
// We expect shader stages in program object are sorted
const auto& spirvs = program.GetGeneratedSpirv();
for (const auto& spv : spirvs) {
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
}
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
HashType hash = XXH64_digest(m_hashState);
return hash;
}
Vector<VkPipelineShaderStageCreateInfo>& ProgramFactory::GetOrCreatePipelineShaderStages(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
auto hash = ComputeHash(program, flags);
auto it = m_cache.find(hash);
if (it != m_cache.end()) {
return it->second.stages;
}
auto& entry = m_cache[hash];
entry.device = m_device;
entry.hash = hash;
auto& shaders = program.GetAttachedShaders();
auto& spirv = program.GetGeneratedSpirv();
for (SizeT i = 0; i < shaders.size(); ++i) {
auto& spv = spirv[i];
if (spv.empty())
continue;
// TODO: Do SPIR-V postprocessing here
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
smci.codeSize = spv.size() * sizeof(Uint);
smci.pCode = spv.data();
VkShaderModule module = VK_NULL_HANDLE;
VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &module), "vkCreateShaderModule");
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
ShaderStage shaderStage = shaders[i]->GetShaderStage();
stage.stage = ToVkStage(shaderStage);
stage.module = module;
stage.pName = "main";
entry.modules.push_back(module);
entry.stages.push_back(stage);
}
return entry.stages;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,56 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h
// 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
#pragma once
#include "../VkIncludes.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_State/GLState/ProgramState/ShaderObject.h"
#include <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
class ProgramFactory {
public:
enum class CompileOptionBit : Uint {
None = 0,
PositionYFlip = 1 << 0,
PositionZRemap = 1 << 1,
};
using CompileOptionFlags = Flags<CompileOptionBit>;
using HashType = Uint64;
struct BackendProgramObject {
HashType hash = 0;
VkDevice device = VK_NULL_HANDLE;
Vector<VkPipelineShaderStageCreateInfo> stages;
Vector<VkShaderModule> modules;
~BackendProgramObject() {
for (auto module: modules) {
vkDestroyShaderModule(device, module, nullptr);
}
modules.clear();
stages.clear();
}
};
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config):
m_device(device), m_config(config) {}
~ProgramFactory();
ProgramFactory(const ProgramFactory&) = delete;
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
Vector<VkPipelineShaderStageCreateInfo>& GetOrCreatePipelineShaderStages(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
private:
VkDevice m_device = VK_NULL_HANDLE;
UnorderedMap<HashType, BackendProgramObject> m_cache;
const VulkanRendererConfig& m_config;
static inline XXH64_state_t* m_hashState = XXH64_createState();
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -48,7 +48,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return VK_FALSE;
}
VulkanRenderer::VulkanRenderer(NativeWindowType window, const RendererConfig& cfg)
VulkanRenderer::VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg)
: m_window(window), m_config(cfg) {
// Initialize();
}
@@ -100,41 +100,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return;
}
Vector<Uint> vsSpv;
Vector<Uint> fsSpv;
auto& shaderSpirvs = programObject.GetGeneratedSpirv();
auto& attachedShaders = programObject.GetAttachedShaders();
for (int index = 0; index < attachedShaders.size(); ++index) {
auto& shader = attachedShaders[index];
auto& spirvCode = shaderSpirvs[index];
if (shader->GetShaderStage() == ShaderStage::Vertex) {
vsSpv = spirvCode;
} else if (shader->GetShaderStage() == ShaderStage::Fragment) {
fsSpv = spirvCode;
}
}
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
smci.codeSize = vsSpv.size() * sizeof(uint32_t);
smci.pCode = vsSpv.data();
VkShaderModule vs;
VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &vs), "vkCreateShaderModule VS");
smci.codeSize = fsSpv.size() * sizeof(uint32_t);
smci.pCode = fsSpv.data();
VkShaderModule fs;
VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &fs), "vkCreateShaderModule FS");
VkPipelineShaderStageCreateInfo stages[2]{};
stages[0] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[0].module = vs;
stages[0].pName = "main";
stages[1] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = fs;
stages[1].pName = "main";
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(programObject, ProgramFactory::CompileOptionBit::None);
VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
VK_VERIFY(vkCreatePipelineLayout(m_device, &plci, nullptr, &m_pipelineLayout), "vkCreatePipelineLayout");
@@ -188,7 +154,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Create Pipeline
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
gpi.stageCount = 2;
gpi.pStages = stages;
gpi.pStages = stages.data();
gpi.pVertexInputState = &vertexInput;
gpi.pInputAssemblyState = &ia;
gpi.pViewportState = &vpci;
@@ -203,8 +169,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_pipeline),
"vkCreateGraphicsPipelines");
vkDestroyShaderModule(m_device, vs, nullptr);
vkDestroyShaderModule(m_device, fs, nullptr);
// vkDestroyShaderModule(m_device, vs, nullptr);
// vkDestroyShaderModule(m_device, fs, nullptr);
MGLOG_I("PrepareDemoPipeline completed");
}
@@ -225,6 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
RecreateSwapchain();
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config);
PrepareDemoPipeline();
CreateFrameContexts();
@@ -241,6 +209,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VulkanRenderer::Shutdown() {
VK_VERIFY(vkDeviceWaitIdle(m_device));
m_programFactory.reset();
m_frameContext.Destroy(m_device, m_commandPool);
if (m_pipeline != VK_NULL_HANDLE) {
@@ -9,25 +9,16 @@
#pragma once
#include "Config.h"
#include "FrameContext.h"
#include "ProgramFactory.h"
#include "SwapchainObject.h"
#include <Includes.h>
#include "../VkIncludes.h"
namespace MobileGL::MG_Backend::DirectVulkan {
using RenderCallback = std::function<void(VkCommandBuffer cmdBuf, uint32_t imageIndex, VkExtent2D extent)>;
struct RendererConfig {
Uint32 MaxFramesInFlight = 2;
String AppName = "MobileGL-VulkanRenderer";
Version Version = MG_Config::CoreVersion;
Uint64 CacheVersion = MG_Config::CacheVersion;
Bool EnableValidationLayers = true;
};
class VulkanRenderer {
public:
VulkanRenderer(NativeWindowType window, const RendererConfig& cfg = {});
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
~VulkanRenderer();
void Initialize();
@@ -57,7 +48,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
NativeWindowType m_window = 0;
RendererConfig m_config;
VulkanRendererConfig m_config;
// Vulkan objects
Bool m_validationLayersEnabled = false;
@@ -87,6 +78,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint m_imageIndexAcquired = 0;
FrameContext m_frameContext;
UniquePtr<ProgramFactory> m_programFactory;
void CreateInstance();
VkResult SetupDebugMessenger();
VkResult DestroyDebugMessenger();
@@ -8,10 +8,18 @@
#pragma once
#include "VulkanRendererConfig.h"
#define VK_VERIFY(expr, ...) \
do { \
VkResult _vk_verify_result = (expr); \
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \
} while (0)
#define ENUM_STR_CASE(c) case c: return #c;
#define ENUM_STR_CASE(c) case c: return #c;
#define XXHASH_VERIFY(expr, ...) \
do { \
XXH_errorcode _xxh_verify_result = (expr); \
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _xxh_verify_result, __FILE__, __LINE__); \
} while (0)
@@ -0,0 +1,20 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererConfig.h
// 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
#pragma once
#include "Config.h"
namespace MobileGL::MG_Backend::DirectVulkan {
struct VulkanRendererConfig {
Uint32 MaxFramesInFlight = 2;
String AppName = "MobileGL-VulkanRenderer";
Version Version = MG_Config::CoreVersion;
Uint64 CacheVersion = MG_Config::CacheVersion;
Bool EnableValidationLayers = true;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -179,6 +179,10 @@ namespace MobileGL {
return m_shaders;
}
const Vector<SharedPtr<ShaderObject>>& ProgramObject::GetAttachedShaders() const {
return m_shaders;
}
void ProgramObject::DoReflection() {
if (!m_program) {
MGLOG_E("ProgramObject %u: DoReflection called but m_program is null", m_externalIndex);
@@ -30,6 +30,7 @@ namespace MobileGL {
Int GetFragmentDataLocation(const char* name);
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
const String& GetInfoLog() const { return m_infoLog; }
Int GetUniformMaxLength() const { return m_uniformNameMaxLength; }
Uint GetUniformCount() { return m_activeUniformCount; }
@@ -111,6 +112,7 @@ namespace MobileGL {
Uint GetUniformBlockBinding(Uint index) const { return m_uniformBlockBinding[index]; }
Vector<Vector<unsigned>>& GetGeneratedSpirv() { return m_generatedSpirv; }
const Vector<Vector<unsigned>>& GetGeneratedSpirv() const { return m_generatedSpirv; }
Int GetShaderIndexByStage(ShaderStage stage) const {
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),