From 484174767dd765fbe5f34f9ee6e7433b0da3eb05 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 24 Jan 2026 17:35:33 +0800 Subject: [PATCH] [Feat] (MG_Util/ShaderTranspiler): do `FloatEqualsZero` elimination by leveraging SPIRV-Tool opt pass --- CMakeLists.txt | 43 +++--- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 4 +- .../GLState/ProgramState/ProgramObject.cpp | 4 + MobileGL/MG_Test/Program/ProgramTest.cpp | 30 ++-- .../ShaderTranspiler/ShaderCompiler.cpp | 15 +- .../MG_Util/ShaderTranspiler/ShaderCompiler.h | 1 + .../FloatEqualsZeroEliminationPass.cpp | 141 ++++++++++++++++++ .../FloatEqualsZeroEliminationPass.h | 26 ++++ 8 files changed, 224 insertions(+), 40 deletions(-) create mode 100644 MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.cpp create mode 100644 MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b0cbf1e6..7f87daba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ set(SOURCE_FILES MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp + MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.cpp MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -204,6 +205,22 @@ set(SOURCE_FILES MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp ) +set(MOBILEGL_LINK_LIBRARIES + glslang::glslang + spirv-cross-c + SPIRV-Tools-opt + SPIRV-Tools +) + +set(MOBILEGL_INCLUDE_DIR + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/MobileGL + ${spirv-tools_SOURCE_DIR} + ${spirv-tools_SOURCE_DIR}/include + ${spirv-tools_BINARY_DIR} + ${SPIRV-Headers_SOURCE_DIR}/include +) + add_library(${CMAKE_PROJECT_NAME} SHARED ${SOURCE_FILES} ) @@ -223,21 +240,12 @@ else() endif() target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/MobileGL + ${MOBILEGL_INCLUDE_DIR} ) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE - # Diligent-Common - # Diligent-GraphicsEngineOpenGL-shared - # Diligent-GraphicsEngineVk-shared -) - -target_link_libraries(${CMAKE_PROJECT_NAME} - PRIVATE - glslang::glslang - spirv-cross-c + ${MOBILEGL_LINK_LIBRARIES} ) add_library(${CMAKE_PROJECT_NAME}_s STATIC @@ -259,21 +267,12 @@ else() endif() target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/MobileGL + ${MOBILEGL_INCLUDE_DIR} ) target_link_libraries(${CMAKE_PROJECT_NAME}_s PRIVATE - # Diligent-Common - # Diligent-GraphicsEngineOpenGL-shared - # Diligent-GraphicsEngineVk-shared -) - -target_link_libraries(${CMAKE_PROJECT_NAME}_s - PRIVATE - glslang::glslang - spirv-cross-c + ${MOBILEGL_LINK_LIBRARIES} ) if (TRACY_ENABLE) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 2b5997bc..477783d6 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -259,7 +259,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const auto& backendBufferObject = backendBufferIt->second; backendBufferObject->Bind(GL_ELEMENT_ARRAY_BUFFER); } else { - MGLOG_E("No backend buffer found for index buffer binding, cannot bind index buffer."); + MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer."); } } @@ -873,7 +873,7 @@ namespace MobileGL::MG_Backend::DirectGLES { spvc_compiler_options options; spvcSession.CreateOptions(&options); - spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); + spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 300); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 9e3aa278..79110c6b 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -455,6 +455,10 @@ namespace MobileGL { for (SizeT i = 0; i < m_generatedSpirv.size(); i++) { auto& spv = m_generatedSpirv[i]; + + auto success = ShaderCompiler::SanitizeBinary(spv, spv); + MOBILEGL_ASSERT(success, "SanitizeBinary failed"); + auto shaderType = shaderTypes[i]; MGLOG_D("ProgramObject %u: GenerateBinary - parsing SPIR-V meta data for module %zu " "(shaderType=%u, wordCount=%zu)", diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index ba19555e..20b92a1d 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -922,26 +922,26 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) { auto programObject = MG_State::pGLContext->GetCurrentProgram(); auto& spirvs = programObject->GetGeneratedSpirv(); - auto& vertexSpirv = spirvs[1]; // 0 - fragment, 1 - vertex + // auto& vertexSpirv = spirvs[1]; // 0 - fragment, 1 - vertex char* pSrcVertIn = nullptr; const char* needle = "layout(location = 2) in vec2 UV0;"; - // for (auto spirv: spirvs) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession(vertexSpirv); - spvc_compiler_options options; - spvcSession.CreateOptions(&options); + for (auto spirv: spirvs) { + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv); + spvc_compiler_options options; + spvcSession.CreateOptions(&options); - spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 460); - spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE); - // spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); + spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 460); + spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE); + // spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); - spvcSession.SetOptions(options); + spvcSession.SetOptions(options); - const char* result = nullptr; - spvcSession.Compile(&result); - printf("%s\n\n", result); - const char* ret = strstr(result, needle); - if (ret) pSrcVertIn = (char*)ret; - // } + const char* result = nullptr; + spvcSession.Compile(&result); + printf("%s\n\n", result); + const char* ret = strstr(result, needle); + if (ret) pSrcVertIn = (char*)ret; + } ASSERT_TRUE(pSrcVertIn != nullptr) << "Not found expected string in generated shader.\n(Searching for \"" << needle << "\")"; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 265a3fa0..730a6f47 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -7,6 +7,11 @@ // End of Source File Header #include "ShaderCompiler.h" + +#include "SpirvPasses/FloatEqualsZeroEliminationPass.h" +#include "spirv-tools/libspirv.h" +#include "spirv-tools/optimizer.hpp" + #include #include @@ -182,7 +187,7 @@ namespace MobileGL { return std::unexpected(r); } - for (auto [name, loc] : attrib.explicitVertexInLocations) { + for (const auto& [name, loc] : attrib.explicitVertexInLocations) { MGLOG_D("%s: got explicitly set - layout(location = %d) %s;", __func__, loc, name.c_str()); } @@ -222,6 +227,14 @@ namespace MobileGL { return allSpirv; } + bool ShaderCompiler::SanitizeBinary(const Vector& inputBinary, Vector& outputBinary) { + spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_5); + + optimizer.RegisterPass(spvtools::Optimizer::PassToken(MakeUnique())); + + return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary); + } + Result ShaderCompiler::DecompileShader(SpvcSession& session) { spvc_compiler_options options; session.CreateOptions(&options); diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h index 7b167359..b152a41c 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h @@ -20,6 +20,7 @@ namespace MobileGL { static Result> CompileShader(const ShaderAttrib& attrib); static Result> LinkProgram(const ProgramAttrib& attrib); static Result>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib); + static bool SanitizeBinary(const Vector& inputBinary, Vector& outputBinary); static Result DecompileShader(SpvcSession& session); }; } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.cpp new file mode 100644 index 00000000..0b8cde95 --- /dev/null +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.cpp @@ -0,0 +1,141 @@ +// MobileGL - MobileGL/MG_Util/ShaderTranspiler/FloatEqualsZeroEliminationPass.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 "FloatEqualsZeroEliminationPass.h" + +#include "spirv.hpp" +#include "source/opt/constants.h" +#include "source/opt/def_use_manager.h" +#include "source/opt/instruction.h" +#include "source/opt/ir_builder.h" +#include "source/opt/ir_context.h" +#include "source/opt/module.h" +#include "source/opt/type_manager.h" +#include + +namespace MobileGL { + namespace MG_Util { + namespace ShaderTranspiler { + spvtools::opt::Pass::Status FloatEqualsZeroEliminationPass::Process() { + using namespace spvtools; + using namespace spvtools::opt; + bool modified = false; + + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + + // 2. Import `GLSL.std.450` extension ID (for abs() func) + uint32_t glsl_std_450_id = context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); + if (glsl_std_450_id == 0) { + return Status::SuccessWithoutChange; + } + + // 3. iterate all function -> basic block -> insn + for (auto& func : *get_module()) { + for (auto& bb : func) { + for (auto itInst = bb.begin(); itInst != bb.end(); ++itInst) { + auto& inst = *itInst; + + // Check if opcode is `OpFOrdEqual` or `OpFUnordEqual` + if (inst.opcode() != spv::Op::OpFOrdEqual && + inst.opcode() != spv::Op::OpFUnordEqual) { + continue; + } + + // check if operand is "float 0.0" + // OpFOrdEqual ResultType ResultID Operand1 Operand2 + uint32_t op1_id = inst.GetSingleWordInOperand(0); + uint32_t op2_id = inst.GetSingleWordInOperand(1); + + uint32_t var_id = 0; + + auto is_float_zero = [&](uint32_t id) -> bool { + const analysis::Constant* c = const_mgr->FindDeclaredConstant(id); + if (c && c->AsFloatConstant() && fabs(c->AsFloatConstant()->GetFloat()) <= K_EPSILON) { + return true; + } + return false; + }; + + if (is_float_zero(op2_id)) { + var_id = op1_id; // x == 0.0 + } else if (is_float_zero(op1_id)) { + var_id = op2_id; // 0.0 == x + } else { + continue; + } + + // --- Found it, continue to patch it --- + + // 1. Get var type (Float) and result type (Bool) + uint32_t float_type_id = def_use_mgr->GetDef(var_id)->type_id(); + uint32_t bool_type_id = inst.type_id(); + + // 2. Create constant ID for `Epsilon` + const analysis::Constant* eps_const = const_mgr->GetConstant( + type_mgr->GetType(float_type_id), + {*(reinterpret_cast(&K_EPSILON))} + ); + uint32_t eps_id = const_mgr->GetDefiningInstruction(eps_const)->result_id(); + + // 3. Build Abs(x) inst + // OpExtInst %float_type %glsl_import Abs %x + InstructionBuilder builder( + context(), + &inst, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping + ); + + std::vector abs_operands; + abs_operands.push_back({spv_operand_type_t::SPV_OPERAND_TYPE_ID, {glsl_std_450_id}}); + abs_operands.push_back({spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {4}}); // 4 is FAbs + abs_operands.push_back({spv_operand_type_t::SPV_OPERAND_TYPE_ID, {var_id}}); + + // In GLSL.std.450, `FAbs`'s OpCode == 4 + // Ref: https://registry.khronos.org/SPIR-V/specs/1.0/GLSL.std.450.html + Instruction* abs_inst = builder.AddInstruction(MakeUnique( + context(), + spv::Op::OpExtInst, + float_type_id, + context()->TakeNextId(), + abs_operands + )); + + // 4. build Abs(x) < Epsilon + // OpFOrdLessThan %bool_type %abs_val %eps + std::vector less_operands; + less_operands.push_back({spv_operand_type_t::SPV_OPERAND_TYPE_ID, {abs_inst->result_id()}}); + less_operands.push_back({spv_operand_type_t::SPV_OPERAND_TYPE_ID, {eps_id}}); + + Instruction* less_than_inst = builder.AddInstruction(MakeUnique( + context(), + spv::Op::OpFOrdLessThan, + bool_type_id, + context()->TakeNextId(), + less_operands + )); + + // 5. Replaces all uses of old insn with new one + context()->ReplaceAllUsesWith(inst.result_id(), less_than_inst->result_id()); + + // 6. Kill old instruction (will be cleaned up by DCE later) + auto nextInstIt = context()->KillInst(&inst); + if (nextInstIt) { + itInst = nextInstIt; + } + + modified = true; + } + } + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; + } + } + } +} \ No newline at end of file diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.h new file mode 100644 index 00000000..63f119f6 --- /dev/null +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FloatEqualsZeroEliminationPass.h @@ -0,0 +1,26 @@ +// MobileGL - MobileGL/MG_Util/ShaderTranspiler/FloatEqualsZeroEliminationPass.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 "source/opt/pass.h" + +#include + +namespace MobileGL { + namespace MG_Util { + namespace ShaderTranspiler { + class FloatEqualsZeroEliminationPass: public spvtools::opt::Pass { + public: + const char* name() const override { return "float-equals-zero-elimination"; } + Status Process() override; + private: + const float K_EPSILON = 0.0001f; + }; + } // namespace ShaderTranspiler + } // namespace MG_Util +} // namespace MobileGL