mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_Backend/DirectVulkan): ProgramFactory and some refactoring
This commit is contained in:
+3
-5
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user