mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 05:38:31 +09:00
[Feat] (MG_Backend/DirectGLES): support Flywheel indirect rendering
- Advertise ARB_gpu_shader5 / ARB_multi_bind / ARB_shading_language_420pack / ARB_vertex_attrib_binding / ARB_shader_image_size so LWJGL reports SUPPORTS_INDIRECT. - New LowerDrawParametersPass demotes DrawIndex/BaseInstance/BaseVertex builtins to Private globals (mg_DrawID/mg_BaseInstance/mg_BaseVertex) for the ESSL transpile; SPIRV-Cross otherwise throws for ES profiles. The program manager promotes the emitted globals to uniforms and feeds them per (sub-)draw. - Indirect draws now execute natively on the GPU (glDrawElementsIndirect / glDrawArraysIndirect per command) when an indirect buffer is bound, so compute-written command fields (Flywheel culling updates instanceCount) are honored; detects GL_EXT_base_instance and falls back to the CPU loop when the command's baseInstance cannot be consumed natively. - Sync SSBO binding points for graphics draws, not just compute (Flywheel vertex shaders read instance data from SSBOs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "SpirvPasses/FlattenInterfaceStructPass.h"
|
||||
#include "SpirvPasses/RenameSamplerFunctionParameterPass.h"
|
||||
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
|
||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
@@ -251,6 +252,18 @@ namespace MobileGL {
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
using namespace spvtools;
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
||||
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
|
||||
spvc_compiler_options options;
|
||||
session.CreateOptions(&options);
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace MobileGL {
|
||||
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
||||
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
||||
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
||||
// Only for backends without native draw-parameter support (DirectGLES).
|
||||
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
static Result<String> DecompileShader(SpvcSession& session);
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.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 "LowerDrawParametersPass.h"
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::IRContext;
|
||||
using spvtools::opt::Operand;
|
||||
|
||||
const char* LoweredNameForBuiltin(spv::BuiltIn builtin) {
|
||||
switch (builtin) {
|
||||
case spv::BuiltIn::BaseVertex:
|
||||
return "mg_BaseVertex";
|
||||
case spv::BuiltIn::BaseInstance:
|
||||
return "mg_BaseInstance";
|
||||
case spv::BuiltIn::DrawIndex:
|
||||
return "mg_DrawID";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceName(IRContext* context, uint32_t id, const char* name) {
|
||||
for (auto& debugInst : context->debugs2()) {
|
||||
if (debugInst.opcode() == spv::Op::OpName && debugInst.GetSingleWordInOperand(0) == id) {
|
||||
debugInst.SetInOperand(
|
||||
1, spvtools::utils::MakeVector<spvtools::opt::Operand::OperandData>(name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
context->AddDebug2Inst(spvtools::MakeUnique<Instruction>(
|
||||
context, spv::Op::OpName, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(name)}}));
|
||||
}
|
||||
|
||||
void RemoveFromEntryPointInterfaces(IRContext* context, uint32_t id) {
|
||||
for (Instruction& entryPoint : context->module()->entry_points()) {
|
||||
std::vector<Operand> newOperands;
|
||||
Bool changed = false;
|
||||
for (uint32_t i = 0; i < entryPoint.NumInOperands(); ++i) {
|
||||
const Operand& operand = entryPoint.GetInOperand(i);
|
||||
// Interface ids start after execution model, entry-point id and name.
|
||||
if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID &&
|
||||
entryPoint.GetSingleWordInOperand(i) == id) {
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
newOperands.push_back(operand);
|
||||
}
|
||||
if (changed) {
|
||||
entryPoint.SetInOperands(std::move(newOperands));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status LowerDrawParametersPass::Process() {
|
||||
auto* irContext = context();
|
||||
auto* defUseMgr = irContext->get_def_use_mgr();
|
||||
|
||||
// Collect the BuiltIn decorations we want to lower first; mutating while
|
||||
// iterating annotations invalidates the range.
|
||||
struct LoweredVariable {
|
||||
Instruction* variable = nullptr;
|
||||
Instruction* decoration = nullptr;
|
||||
const char* name = nullptr;
|
||||
};
|
||||
std::vector<LoweredVariable> targets;
|
||||
|
||||
for (auto& annotation : irContext->annotations()) {
|
||||
if (annotation.opcode() != spv::Op::OpDecorate ||
|
||||
static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(1)) !=
|
||||
spv::Decoration::BuiltIn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto builtin = static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(2));
|
||||
const char* loweredName = LoweredNameForBuiltin(builtin);
|
||||
if (loweredName == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction* variable = defUseMgr->GetDef(annotation.GetSingleWordInOperand(0));
|
||||
if (variable == nullptr || variable->opcode() != spv::Op::OpVariable ||
|
||||
static_cast<spv::StorageClass>(variable->GetSingleWordInOperand(0)) !=
|
||||
spv::StorageClass::Input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.push_back({variable, &annotation, loweredName});
|
||||
}
|
||||
|
||||
if (targets.empty()) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
auto* typeMgr = irContext->get_type_mgr();
|
||||
|
||||
for (auto& target : targets) {
|
||||
Instruction* variable = target.variable;
|
||||
const uint32_t variableId = variable->result_id();
|
||||
|
||||
// Demote the Input builtin to a plain Private global.
|
||||
Instruction* pointerType = defUseMgr->GetDef(variable->type_id());
|
||||
const uint32_t pointeeTypeId = pointerType->GetSingleWordInOperand(1);
|
||||
const uint32_t privatePointerTypeId =
|
||||
typeMgr->FindPointerToType(pointeeTypeId, spv::StorageClass::Private);
|
||||
variable->SetResultType(privatePointerTypeId);
|
||||
variable->SetInOperand(0, {static_cast<uint32_t>(spv::StorageClass::Private)});
|
||||
|
||||
irContext->KillInst(target.decoration);
|
||||
RemoveFromEntryPointInterfaces(irContext, variableId);
|
||||
ReplaceName(irContext, variableId, target.name);
|
||||
}
|
||||
|
||||
// The DrawParameters capability only covered these builtins; it must not leak
|
||||
// into the ESSL decompile.
|
||||
std::vector<Instruction*> deadModuleInsts;
|
||||
for (auto& capability : irContext->module()->capabilities()) {
|
||||
if (static_cast<spv::Capability>(capability.GetSingleWordInOperand(0)) ==
|
||||
spv::Capability::DrawParameters) {
|
||||
deadModuleInsts.push_back(&capability);
|
||||
}
|
||||
}
|
||||
for (auto& extension : irContext->module()->extensions()) {
|
||||
if (extension.GetInOperand(0).AsString() == "SPV_KHR_shader_draw_parameters") {
|
||||
deadModuleInsts.push_back(&extension);
|
||||
}
|
||||
}
|
||||
for (auto* inst : deadModuleInsts) {
|
||||
irContext->KillInst(inst);
|
||||
}
|
||||
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken LowerDrawParametersPass::CreateLowerDrawParametersPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<LowerDrawParametersPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,34 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.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 {
|
||||
// ESSL has no gl_DrawID / gl_BaseInstance / gl_BaseVertex builtins and SPIRV-Cross
|
||||
// refuses to emit them for ES targets. This pass demotes the DrawIndex /
|
||||
// BaseInstance / BaseVertex builtin inputs to plain Private globals with
|
||||
// well-known names (mg_DrawID / mg_BaseInstance / mg_BaseVertex) so the decompiled
|
||||
// ESSL declares ordinary globals; the DirectGLES program manager then upgrades the
|
||||
// declarations to uniforms and feeds them per (sub-)draw. Only meant for the
|
||||
// DirectGLES transpile path - the Vulkan backend keeps the native builtins.
|
||||
class LowerDrawParametersPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "lower-draw-parameters"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateLowerDrawParametersPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user