diff --git a/CMakeLists.txt b/CMakeLists.txt index 43d72c1f..b629bdb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/MobileGL/Includes.h b/MobileGL/Includes.h index 9da3de4c..68930b2a 100644 --- a/MobileGL/Includes.h +++ b/MobileGL/Includes.h @@ -50,6 +50,9 @@ // Include FastSTL #include +// Include xxHash +#include + // Include spirv_cross #include diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp new file mode 100644 index 00000000..8b417990 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -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& 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 \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h new file mode 100644 index 00000000..b39fe2c3 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -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 + +namespace MobileGL::MG_Backend::DirectVulkan { + class ProgramFactory { + public: + enum class CompileOptionBit : Uint { + None = 0, + PositionYFlip = 1 << 0, + PositionZRemap = 1 << 1, + }; + using CompileOptionFlags = Flags; + using HashType = Uint64; + struct BackendProgramObject { + HashType hash = 0; + VkDevice device = VK_NULL_HANDLE; + Vector stages; + Vector 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& GetOrCreatePipelineShaderStages(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); + + static VkShaderStageFlagBits ToVkStage(ShaderStage stage); + private: + VkDevice m_device = VK_NULL_HANDLE; + UnorderedMap m_cache; + const VulkanRendererConfig& m_config; + static inline XXH64_state_t* m_hashState = XXH64_createState(); + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index d3060fae..6d5b0d33 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -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 vsSpv; - Vector 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(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) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 40de30a4..acb5213e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -9,25 +9,16 @@ #pragma once #include "Config.h" #include "FrameContext.h" +#include "ProgramFactory.h" #include "SwapchainObject.h" #include #include "../VkIncludes.h" namespace MobileGL::MG_Backend::DirectVulkan { - using RenderCallback = std::function; - - 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 m_programFactory; + void CreateInstance(); VkResult SetupDebugMessenger(); VkResult DestroyDebugMessenger(); diff --git a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h index cb0f7a83..fc981291 100644 --- a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h +++ b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h @@ -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; \ No newline at end of file +#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) \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h b/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h new file mode 100644 index 00000000..ee01301c --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/VulkanRendererConfig.h @@ -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 \ No newline at end of file diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 19905d2d..c3bac839 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -179,6 +179,10 @@ namespace MobileGL { return m_shaders; } + const Vector>& 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); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 5ad9860a..0a40dacf 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -30,6 +30,7 @@ namespace MobileGL { Int GetFragmentDataLocation(const char* name); Vector>& GetAttachedShaders(); + const Vector>& 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>& GetGeneratedSpirv() { return m_generatedSpirv; } + const Vector>& GetGeneratedSpirv() const { return m_generatedSpirv; } Int GetShaderIndexByStage(ShaderStage stage) const { auto it = std::find_if(m_shaders.begin(), m_shaders.end(),