mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Refactor] (ShaderTranspiler, DirectVulkan): replace the hand-rolled SPIR-V word walkers with a DecoratePositionInvariantPass and SPIRV-Reflect-based InstanceIndex detection
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
#include "SpirvPasses/FlattenInterfaceStructPass.h"
|
||||
#include "SpirvPasses/RenameSamplerFunctionParameterPass.h"
|
||||
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
|
||||
#include "SpirvPasses/DecoratePositionInvariantPass.h"
|
||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
|
||||
@@ -347,71 +348,14 @@ namespace MobileGL {
|
||||
|
||||
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
static constexpr Uint32 kHeaderWords = 5;
|
||||
static constexpr Uint32 kOpDecorate = 71;
|
||||
static constexpr Uint32 kOpMemberDecorate = 72;
|
||||
static constexpr Uint32 kDecorationInvariant = 18;
|
||||
static constexpr Uint32 kDecorationBuiltIn = 11;
|
||||
static constexpr Uint32 kBuiltInPosition = 0;
|
||||
if (inputBinary.size() < kHeaderWords) {
|
||||
return false;
|
||||
}
|
||||
using namespace spvtools;
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
// First pass: find targets that already carry Invariant so we never duplicate.
|
||||
struct MemberKey {
|
||||
Uint32 id;
|
||||
Uint32 member;
|
||||
bool operator==(const MemberKey& o) const { return id == o.id && member == o.member; }
|
||||
};
|
||||
Vector<Uint32> invariantIds;
|
||||
Vector<MemberKey> invariantMembers;
|
||||
for (SizeT i = kHeaderWords; i < inputBinary.size();) {
|
||||
const Uint32 word0 = inputBinary[i];
|
||||
const Uint32 opcode = word0 & 0xFFFFu;
|
||||
const Uint32 length = word0 >> 16;
|
||||
if (length == 0 || i + length > inputBinary.size()) {
|
||||
return false;
|
||||
}
|
||||
if (opcode == kOpDecorate && length >= 3 && inputBinary[i + 2] == kDecorationInvariant) {
|
||||
invariantIds.push_back(inputBinary[i + 1]);
|
||||
} else if (opcode == kOpMemberDecorate && length >= 4 &&
|
||||
inputBinary[i + 3] == kDecorationInvariant) {
|
||||
invariantMembers.push_back({inputBinary[i + 1], inputBinary[i + 2]});
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass());
|
||||
|
||||
outputBinary.clear();
|
||||
outputBinary.reserve(inputBinary.size() + 8);
|
||||
outputBinary.insert(outputBinary.end(), inputBinary.begin(), inputBinary.begin() + kHeaderWords);
|
||||
for (SizeT i = kHeaderWords; i < inputBinary.size();) {
|
||||
const Uint32 word0 = inputBinary[i];
|
||||
const Uint32 opcode = word0 & 0xFFFFu;
|
||||
const Uint32 length = word0 >> 16;
|
||||
outputBinary.insert(outputBinary.end(), inputBinary.begin() + i,
|
||||
inputBinary.begin() + i + length);
|
||||
if (opcode == kOpDecorate && length == 4 &&
|
||||
inputBinary[i + 2] == kDecorationBuiltIn && inputBinary[i + 3] == kBuiltInPosition) {
|
||||
const Uint32 target = inputBinary[i + 1];
|
||||
if (std::find(invariantIds.begin(), invariantIds.end(), target) == invariantIds.end()) {
|
||||
outputBinary.push_back((3u << 16) | kOpDecorate);
|
||||
outputBinary.push_back(target);
|
||||
outputBinary.push_back(kDecorationInvariant);
|
||||
}
|
||||
} else if (opcode == kOpMemberDecorate && length == 5 &&
|
||||
inputBinary[i + 3] == kDecorationBuiltIn && inputBinary[i + 4] == kBuiltInPosition) {
|
||||
const MemberKey key{inputBinary[i + 1], inputBinary[i + 2]};
|
||||
if (std::find(invariantMembers.begin(), invariantMembers.end(), key) ==
|
||||
invariantMembers.end()) {
|
||||
outputBinary.push_back((4u << 16) | kOpMemberDecorate);
|
||||
outputBinary.push_back(key.id);
|
||||
outputBinary.push_back(key.member);
|
||||
outputBinary.push_back(kDecorationInvariant);
|
||||
}
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
return true;
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.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 "DecoratePositionInvariantPass.h"
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::IRContext;
|
||||
using spvtools::opt::Operand;
|
||||
|
||||
// Identifies one member of a decorated struct (gl_PerVertex's Position slot).
|
||||
struct MemberKey {
|
||||
uint32_t id = 0;
|
||||
uint32_t member = 0;
|
||||
bool operator==(const MemberKey& other) const {
|
||||
return id == other.id && member == other.member;
|
||||
}
|
||||
};
|
||||
|
||||
// OpDecorate <target-id> <decoration> [literals...]
|
||||
// OpMemberDecorate <struct-id> <member> <decoration> [literals...]
|
||||
constexpr uint32_t kDecorateTargetOperand = 0;
|
||||
constexpr uint32_t kDecorateDecorationOperand = 1;
|
||||
constexpr uint32_t kDecorateBuiltInOperand = 2;
|
||||
constexpr uint32_t kMemberDecorateStructOperand = 0;
|
||||
constexpr uint32_t kMemberDecorateMemberOperand = 1;
|
||||
constexpr uint32_t kMemberDecorateDecorationOperand = 2;
|
||||
constexpr uint32_t kMemberDecorateBuiltInOperand = 3;
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status DecoratePositionInvariantPass::Process() {
|
||||
auto* irContext = context();
|
||||
|
||||
// Collect first: AddAnnotationInst mutates the list being walked.
|
||||
std::vector<uint32_t> invariantIds;
|
||||
std::vector<MemberKey> invariantMembers;
|
||||
std::vector<uint32_t> positionIds;
|
||||
std::vector<MemberKey> positionMembers;
|
||||
|
||||
for (const Instruction& annotation : irContext->annotations()) {
|
||||
if (annotation.opcode() == spv::Op::OpDecorate) {
|
||||
if (annotation.NumInOperands() <= kDecorateDecorationOperand) {
|
||||
continue;
|
||||
}
|
||||
const auto decoration = static_cast<spv::Decoration>(
|
||||
annotation.GetSingleWordInOperand(kDecorateDecorationOperand));
|
||||
const uint32_t target = annotation.GetSingleWordInOperand(kDecorateTargetOperand);
|
||||
if (decoration == spv::Decoration::Invariant) {
|
||||
invariantIds.push_back(target);
|
||||
} else if (decoration == spv::Decoration::BuiltIn &&
|
||||
annotation.NumInOperands() > kDecorateBuiltInOperand &&
|
||||
static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(
|
||||
kDecorateBuiltInOperand)) == spv::BuiltIn::Position) {
|
||||
positionIds.push_back(target);
|
||||
}
|
||||
} else if (annotation.opcode() == spv::Op::OpMemberDecorate) {
|
||||
if (annotation.NumInOperands() <= kMemberDecorateDecorationOperand) {
|
||||
continue;
|
||||
}
|
||||
const auto decoration = static_cast<spv::Decoration>(
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateDecorationOperand));
|
||||
const MemberKey key{
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateStructOperand),
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateMemberOperand)};
|
||||
if (decoration == spv::Decoration::Invariant) {
|
||||
invariantMembers.push_back(key);
|
||||
} else if (decoration == spv::Decoration::BuiltIn &&
|
||||
annotation.NumInOperands() > kMemberDecorateBuiltInOperand &&
|
||||
static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(
|
||||
kMemberDecorateBuiltInOperand)) == spv::BuiltIn::Position) {
|
||||
positionMembers.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool changed = false;
|
||||
|
||||
for (const uint32_t target : positionIds) {
|
||||
if (std::find(invariantIds.begin(), invariantIds.end(), target) != invariantIds.end()) {
|
||||
continue;
|
||||
}
|
||||
irContext->AddAnnotationInst(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpDecorate, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {target}},
|
||||
{SPV_OPERAND_TYPE_DECORATION,
|
||||
{static_cast<uint32_t>(spv::Decoration::Invariant)}}}));
|
||||
// Guard against a second Position decoration on the same target.
|
||||
invariantIds.push_back(target);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
for (const MemberKey& key : positionMembers) {
|
||||
if (std::find(invariantMembers.begin(), invariantMembers.end(), key) !=
|
||||
invariantMembers.end()) {
|
||||
continue;
|
||||
}
|
||||
irContext->AddAnnotationInst(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpMemberDecorate, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {key.id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {key.member}},
|
||||
{SPV_OPERAND_TYPE_DECORATION,
|
||||
{static_cast<uint32_t>(spv::Decoration::Invariant)}}}));
|
||||
invariantMembers.push_back(key);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken
|
||||
DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<DecoratePositionInvariantPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,35 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.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 "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
// Adds the Invariant decoration to every Position builtin output. GL apps
|
||||
// routinely rely on cross-program position invariance for multi-pass equality
|
||||
// depth tests - MC 26.3's OIT re-draws the cloud geometry with GEQUAL against the
|
||||
// depth its own first pass wrote - and a driver that optimizes each pipeline
|
||||
// separately may otherwise vary the position math between passes, dropping whole
|
||||
// primitives from the later ones. Both the plain (OpDecorate on a Position
|
||||
// variable) and the block-member (OpMemberDecorate on gl_PerVertex) spellings are
|
||||
// handled; targets that already carry Invariant are left alone. DirectVulkan only.
|
||||
class DecoratePositionInvariantPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "decorate-position-invariant"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateDecoratePositionInvariantPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user