From 7f9953fdeef92679e19fc0d9318ba2da30e94c1a Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sat, 21 Feb 2026 15:36:28 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectVulkan): Handle shader position transform. --- MobileGL/Defines.h | 2 +- .../DirectVulkan/Renderer/ProgramFactory.cpp | 304 +++++++++++++++++- .../DirectVulkan/Renderer/ProgramFactory.h | 10 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 17 +- 4 files changed, 318 insertions(+), 15 deletions(-) diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 26d2ee42..083d6c9a 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -34,7 +34,7 @@ #define MOBILEGL_EGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // -#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG +#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO #define MOBILEGL_LOG_ENABLE_CONSOLE 1 #define MOBILEGL_LOG_ENABLE_FILE 1 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index d639f40a..db99322e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -8,8 +8,292 @@ #include "ProgramFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace MobileGL::MG_Backend::DirectVulkan { - ProgramFactory::~ProgramFactory() {} + namespace { + using ShaderObject = MG_State::GLState::ShaderObject; + + struct PositionTargetInfo { + Uint32 variableId = 0; + Uint32 vectorTypeId = 0; + Uint32 floatTypeId = 0; + Uint32 vectorPtrTypeId = 0; + Uint32 memberIndex = 0; + Bool isMember = false; + }; + + Bool IsVec4Float32(spvtools::opt::IRContext* context, Uint32 typeId, Uint32* outFloatTypeId) { + auto* vecInst = context->get_def_use_mgr()->GetDef(typeId); + if (!vecInst || vecInst->opcode() != spv::Op::OpTypeVector) return false; + if (vecInst->GetSingleWordInOperand(1) != 4) return false; + + const Uint32 floatTypeId = vecInst->GetSingleWordInOperand(0); + auto* floatInst = context->get_def_use_mgr()->GetDef(floatTypeId); + if (!floatInst || floatInst->opcode() != spv::Op::OpTypeFloat) return false; + if (floatInst->GetSingleWordInOperand(0) != 32) return false; + + if (outFloatTypeId) *outFloatTypeId = floatTypeId; + return true; + } + + Bool ResolveDirectPositionTarget(spvtools::opt::IRContext* context, Uint32 variableId, + PositionTargetInfo* outTarget) { + auto* varInst = context->get_def_use_mgr()->GetDef(variableId); + if (!varInst || varInst->opcode() != spv::Op::OpVariable) return false; + if (varInst->GetSingleWordInOperand(0) != static_cast(spv::StorageClass::Output)) return false; + + auto* ptrTypeInst = context->get_def_use_mgr()->GetDef(varInst->type_id()); + if (!ptrTypeInst || ptrTypeInst->opcode() != spv::Op::OpTypePointer) return false; + if (ptrTypeInst->GetSingleWordInOperand(0) != static_cast(spv::StorageClass::Output)) return false; + + PositionTargetInfo target{}; + target.variableId = variableId; + target.vectorTypeId = ptrTypeInst->GetSingleWordInOperand(1); + if (!IsVec4Float32(context, target.vectorTypeId, &target.floatTypeId)) return false; + target.vectorPtrTypeId = varInst->type_id(); + target.isMember = false; + + *outTarget = target; + return true; + } + + Uint32 FindOutputVectorPointerTypeId(spvtools::opt::IRContext* context, Uint32 vectorTypeId) { + auto* vectorType = context->get_type_mgr()->GetType(vectorTypeId); + if (!vectorType) return 0; + spvtools::opt::analysis::Pointer ptrType(vectorType, spv::StorageClass::Output); + return context->get_type_mgr()->GetTypeInstruction(&ptrType); + } + + Bool ResolveMemberPositionTarget(spvtools::opt::IRContext* context, Uint32 structTypeId, Uint32 memberIndex, + PositionTargetInfo* outTarget) { + auto* structInst = context->get_def_use_mgr()->GetDef(structTypeId); + if (!structInst || structInst->opcode() != spv::Op::OpTypeStruct) return false; + if (memberIndex >= structInst->NumInOperands()) return false; + + const Uint32 vectorTypeId = structInst->GetSingleWordInOperand(memberIndex); + Uint32 floatTypeId = 0; + if (!IsVec4Float32(context, vectorTypeId, &floatTypeId)) return false; + + const Uint32 vectorPtrTypeId = FindOutputVectorPointerTypeId(context, vectorTypeId); + if (vectorPtrTypeId == 0) return false; + + for (auto& inst : context->module()->types_values()) { + if (inst.opcode() != spv::Op::OpVariable) continue; + if (inst.GetSingleWordInOperand(0) != static_cast(spv::StorageClass::Output)) continue; + + auto* ptrTypeInst = context->get_def_use_mgr()->GetDef(inst.type_id()); + if (!ptrTypeInst || ptrTypeInst->opcode() != spv::Op::OpTypePointer) continue; + if (ptrTypeInst->GetSingleWordInOperand(0) != static_cast(spv::StorageClass::Output)) continue; + if (ptrTypeInst->GetSingleWordInOperand(1) != structTypeId) continue; + + PositionTargetInfo target{}; + target.variableId = inst.result_id(); + target.vectorTypeId = vectorTypeId; + target.floatTypeId = floatTypeId; + target.vectorPtrTypeId = vectorPtrTypeId; + target.memberIndex = memberIndex; + target.isMember = true; + *outTarget = target; + return true; + } + + return false; + } + + Bool FindPositionTarget(spvtools::opt::IRContext* context, PositionTargetInfo* outTarget) { + Vector> memberCandidates; + constexpr auto kDecorationBuiltIn = static_cast(spv::Decoration::BuiltIn); + constexpr auto kBuiltInPosition = static_cast(spv::BuiltIn::Position); + + for (auto& inst : context->module()->annotations()) { + if (inst.opcode() == spv::Op::OpDecorate) { + if (inst.NumInOperands() < 3) continue; + if (inst.GetSingleWordInOperand(1) != kDecorationBuiltIn) continue; + if (inst.GetSingleWordInOperand(2) != kBuiltInPosition) continue; + if (ResolveDirectPositionTarget(context, inst.GetSingleWordInOperand(0), outTarget)) return true; + } else if (inst.opcode() == spv::Op::OpMemberDecorate) { + if (inst.NumInOperands() < 4) continue; + if (inst.GetSingleWordInOperand(2) != kDecorationBuiltIn) continue; + if (inst.GetSingleWordInOperand(3) != kBuiltInPosition) continue; + memberCandidates.emplace_back(inst.GetSingleWordInOperand(0), inst.GetSingleWordInOperand(1)); + } + } + + for (const auto& [structTypeId, memberIndex] : memberCandidates) { + if (ResolveMemberPositionTarget(context, structTypeId, memberIndex, outTarget)) return true; + } + return false; + } + + Bool InsertPositionFixup(spvtools::opt::IRContext* context, spvtools::opt::Instruction* insertBefore, + const PositionTargetInfo& target, Uint32 halfConstId, Bool doYFlip, Bool doZRemap) { + using namespace spvtools::opt; + InstructionBuilder builder(context, insertBefore, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + + Uint32 positionPtrId = target.variableId; + if (target.isMember) { + const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex); + if (memberIndexId == 0) return false; + auto* access = builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId}); + if (!access) return false; + positionPtrId = access->result_id(); + } + + auto* position = builder.AddLoad(target.vectorTypeId, positionPtrId); + if (!position) return false; + auto* x = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {0}); + auto* y = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {1}); + auto* z = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {2}); + auto* w = builder.AddCompositeExtract(target.floatTypeId, position->result_id(), {3}); + if (!x || !y || !z || !w) return false; + + if (!doYFlip && !doZRemap) return false; + + Uint32 yValueId = y->result_id(); + if (doYFlip) { + auto* negY = builder.AddUnaryOp(target.floatTypeId, spv::Op::OpFNegate, y->result_id()); + if (!negY) return false; + yValueId = negY->result_id(); + } + + Uint32 zValueId = z->result_id(); + if (doZRemap) { + auto* zPlusW = builder.AddBinaryOp(target.floatTypeId, spv::Op::OpFAdd, z->result_id(), w->result_id()); + if (!zPlusW) return false; + auto* mappedZ = + builder.AddBinaryOp(target.floatTypeId, spv::Op::OpFMul, zPlusW->result_id(), halfConstId); + if (!mappedZ) return false; + zValueId = mappedZ->result_id(); + } + + auto* fixedPosition = builder.AddCompositeConstruct(target.vectorTypeId, + {x->result_id(), yValueId, zValueId, w->result_id()}); + if (!fixedPosition) return false; + + return builder.AddStore(positionPtrId, fixedPosition->result_id()) != nullptr; + } + + class GlToVulkanPositionFixPass final : public spvtools::opt::Pass { + public: + const char* name() const override { return "gl-to-vulkan-position-fix"; } + explicit GlToVulkanPositionFixPass(ProgramFactory::CompileOptionFlags transformFlags) + : m_transformFlags(transformFlags) {} + + Status Process() override { + if (!m_transformFlags) return Status::SuccessWithoutChange; + PositionTargetInfo target{}; + if (!FindPositionTarget(context(), &target)) return Status::SuccessWithoutChange; + + auto* floatType = context()->get_type_mgr()->GetType(target.floatTypeId); + if (!floatType) return Status::SuccessWithoutChange; + + const auto halfBits = std::bit_cast(0.5f); + const auto* halfConst = context()->get_constant_mgr()->GetConstant(floatType, {halfBits}); + auto* halfInst = context()->get_constant_mgr()->GetDefiningInstruction(halfConst); + if (!halfInst) return Status::SuccessWithoutChange; + const Uint32 halfConstId = halfInst->result_id(); + + const Bool doYFlip = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip); + const Bool doZRemap = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionZRemap); + + Bool modified = false; + for (auto& entryPoint : get_module()->entry_points()) { + if (entryPoint.opcode() != spv::Op::OpEntryPoint) continue; + if (entryPoint.NumInOperands() < 2) continue; + + const auto model = static_cast(entryPoint.GetSingleWordInOperand(0)); + if (model != spv::ExecutionModel::Vertex && model != spv::ExecutionModel::TessellationEvaluation && + model != spv::ExecutionModel::Geometry) { + continue; + } + + auto* function = context()->GetFunction(entryPoint.GetSingleWordInOperand(1)); + if (!function) continue; + + for (auto& bb : *function) { + for (auto instIter = bb.begin(); instIter != bb.end(); ++instIter) { + auto* inst = &*instIter; + const Bool needsFixup = + (model == spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpEmitVertex) || + (model != spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpReturn); + if (!needsFixup) continue; + + modified |= InsertPositionFixup(context(), inst, target, halfConstId, doYFlip, doZRemap); + } + } + } + + if (!modified) return Status::SuccessWithoutChange; + context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisDefUse | + spvtools::opt::IRContext::kAnalysisInstrToBlockMapping); + return Status::SuccessWithChange; + } + + private: + ProgramFactory::CompileOptionFlags m_transformFlags; + }; + + spvtools::Optimizer::PassToken CreateGlToVulkanPositionFixPass( + ProgramFactory::CompileOptionFlags transformFlags) { + return spvtools::Optimizer::PassToken(MakeUnique(transformFlags)); + } + + Bool TransformSpirvForVulkanPositionFix(const Vector& input, Vector& output, + ProgramFactory::CompileOptionFlags transformFlags) { + if (input.empty()) { + output.clear(); + return true; + } + + if (!transformFlags) { + output = input; + return true; + } + + spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3); + spvtools::OptimizerOptions options; + options.set_run_validator(false); + optimizer.RegisterPass(CreateGlToVulkanPositionFixPass(transformFlags)); + + const Bool success = optimizer.Run(input.data(), input.size(), &output, options); + if (!success) { + MGLOG_E("Vulkan: failed to run GL->Vulkan position fix pass"); + output = input; + } + return success; + } + + ShaderStage PickClipFixupStage(const Vector>& shaders) { + Bool hasGeometry = false; + Bool hasTessEval = false; + Bool hasVertex = false; + + for (const auto& shader : shaders) { + if (!shader) continue; + const auto stage = shader->GetShaderStage(); + hasGeometry |= (stage == ShaderStage::Geometry); + hasTessEval |= (stage == ShaderStage::TessEval); + hasVertex |= (stage == ShaderStage::Vertex); + } + + if (hasGeometry) return ShaderStage::Geometry; + if (hasTessEval) return ShaderStage::TessEval; + if (hasVertex) return ShaderStage::Vertex; + return ShaderStage::Unknown; + } + } // namespace + + ProgramFactory::~ProgramFactory() = default; VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { switch (stage) { @@ -57,16 +341,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& shaders = program.GetAttachedShaders(); auto& spirv = program.GetGeneratedSpirv(); + const ShaderStage fixupStage = PickClipFixupStage(shaders); + for (SizeT i = 0; i < shaders.size(); ++i) { auto& spv = spirv[i]; - if (spv.empty()) - continue; + if (spv.empty()) continue; - // TODO: Do SPIR-V postprocessing here + Vector moduleSpv; + + // Apply position fixup if needed + if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) { + TransformSpirvForVulkanPositionFix(spv, moduleSpv, flags); + } else { + moduleSpv = spv; + } VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - smci.codeSize = spv.size() * sizeof(Uint); - smci.pCode = spv.data(); + smci.codeSize = moduleSpv.size() * sizeof(Uint); + smci.pCode = moduleSpv.data(); VkShaderModule module = VK_NULL_HANDLE; VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &module), "vkCreateShaderModule"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 48c52a14..21f0ce5d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -62,7 +62,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { private: void DestroyModules() { - for (auto module: modules) { + for (auto module : modules) { if (module != VK_NULL_HANDLE && device != VK_NULL_HANDLE) { vkDestroyShaderModule(device, module, nullptr); } @@ -71,15 +71,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { } }; - explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config): - m_device(device), m_config(config) {} + 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); + 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 8dd1a1ea..29a09b8c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -61,18 +61,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { Shutdown(); } + inline ProgramFactory::CompileOptionFlags GetShaderTransformFlags() { + 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; + } + return flags; + } + VkPipeline VulkanRenderer::GetOrCreatePipeline(const MG_State::GLState::ProgramObject& program, VkPipelineLayout pipelineLayout, Uint64 vertexInputHash, const VkPipelineVertexInputStateCreateInfo& vertexInputState) { MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory is not initialized"); MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory is not initialized"); - auto& stages = - m_programFactory->GetOrCreatePipelineShaderStages(program, ProgramFactory::CompileOptionBit::None); + ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(); + 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, ProgramFactory::CompileOptionBit::None); + const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags); auto toVkCompareOp = [](DepthTestFunc func) -> VkCompareOp { switch (func) { case DepthTestFunc::Never: @@ -90,7 +100,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { case DepthTestFunc::GreaterEqual: return VK_COMPARE_OP_GREATER_OR_EQUAL; case DepthTestFunc::Always: - return VK_COMPARE_OP_ALWAYS; default: return VK_COMPARE_OP_ALWAYS; }