mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 22:28:32 +09:00
212 lines
11 KiB
C++
212 lines
11 KiB
C++
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.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 "PackDoubleVertexInputsPass.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/opt/types.h"
|
|
#include "source/util/make_unique.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace MobileGL {
|
|
namespace MG_Util {
|
|
namespace ShaderTranspiler {
|
|
namespace {
|
|
using spvtools::opt::IRContext;
|
|
using spvtools::opt::Instruction;
|
|
using spvtools::opt::Operand;
|
|
namespace analysis = spvtools::opt::analysis;
|
|
|
|
// Component count of a 64-bit float input, or 0 if the type is not one.
|
|
Uint32 DoubleComponentCount(const analysis::Type* type) {
|
|
if (type == nullptr) return 0;
|
|
if (const auto* scalar = type->AsFloat()) {
|
|
return scalar->width() == 64 ? 1u : 0u;
|
|
}
|
|
if (const auto* vector = type->AsVector()) {
|
|
const auto* element = vector->element_type()->AsFloat();
|
|
if (element == nullptr || element->width() != 64) return 0;
|
|
return vector->element_count();
|
|
}
|
|
return 0;
|
|
}
|
|
} // namespace
|
|
|
|
spvtools::opt::Pass::Status PackDoubleVertexInputsPass::Process() {
|
|
auto* irContext = context();
|
|
auto entryPoints = irContext->module()->entry_points();
|
|
if (entryPoints.begin() == entryPoints.end()) return Status::SuccessWithoutChange;
|
|
|
|
Instruction* entryPoint = &*entryPoints.begin();
|
|
if (static_cast<spv::ExecutionModel>(entryPoint->GetSingleWordInOperand(0)) !=
|
|
spv::ExecutionModel::Vertex) {
|
|
return Status::SuccessWithoutChange;
|
|
}
|
|
|
|
auto* defUseMgr = irContext->get_def_use_mgr();
|
|
auto* typeMgr = irContext->get_type_mgr();
|
|
|
|
struct Target {
|
|
Instruction* variable = nullptr;
|
|
Uint32 doubleTypeId = 0;
|
|
Uint32 componentCount = 0;
|
|
Uint32 packedTypeId = 0;
|
|
Uint32 packedPointerTypeId = 0;
|
|
Uint32 privatePointerTypeId = 0;
|
|
};
|
|
std::vector<Target> targets;
|
|
|
|
for (Instruction& inst : irContext->types_values()) {
|
|
if (inst.opcode() != spv::Op::OpVariable) continue;
|
|
if (static_cast<spv::StorageClass>(inst.GetSingleWordInOperand(0)) !=
|
|
spv::StorageClass::Input) {
|
|
continue;
|
|
}
|
|
Instruction* pointerType = defUseMgr->GetDef(inst.type_id());
|
|
if (pointerType == nullptr) continue;
|
|
const Uint32 pointeeTypeId = pointerType->GetSingleWordInOperand(1);
|
|
const Uint32 components = DoubleComponentCount(typeMgr->GetType(pointeeTypeId));
|
|
if (components == 0) continue;
|
|
if (components > 2) {
|
|
// 6 or 8 uint32 components has no single vertex format, and GL spreads such
|
|
// an input over two attribute locations. Left alone; the vertex-input
|
|
// factory declines the matching attribute for the same reason.
|
|
MGLOG_E_ONCE("PackDoubleVertexInputsPass: vertex input %%%u is a %u-component 64-bit "
|
|
"float; only double and dvec2 inputs can be packed",
|
|
inst.result_id(), components);
|
|
continue;
|
|
}
|
|
targets.push_back({&inst, pointeeTypeId, components});
|
|
}
|
|
|
|
if (targets.empty()) return Status::SuccessWithoutChange;
|
|
|
|
// Entry block insertion point: after the block's leading OpVariable run, which
|
|
// SPIR-V requires to stay at the top of a function's first block.
|
|
const Uint32 entryFunctionId = entryPoint->GetSingleWordInOperand(1);
|
|
spvtools::opt::Function* entryFunction = nullptr;
|
|
for (auto& function : *irContext->module()) {
|
|
if (function.result_id() == entryFunctionId) {
|
|
entryFunction = &function;
|
|
break;
|
|
}
|
|
}
|
|
if (entryFunction == nullptr || entryFunction->begin() == entryFunction->end()) {
|
|
return Status::SuccessWithoutChange;
|
|
}
|
|
auto& entryBlock = *entryFunction->begin();
|
|
auto insertPoint = entryBlock.begin();
|
|
while (insertPoint != entryBlock.end() && insertPoint->opcode() == spv::Op::OpVariable) {
|
|
++insertPoint;
|
|
}
|
|
if (insertPoint == entryBlock.end()) return Status::SuccessWithoutChange;
|
|
|
|
const Uint32 uintTypeId = typeMgr->GetUIntTypeId();
|
|
const analysis::Integer* uintType = typeMgr->GetType(uintTypeId)->AsInteger();
|
|
|
|
// Every type instruction has to exist before any variable that names it: the
|
|
// types-and-variables section is walked in order and a forward reference to a type is
|
|
// invalid SPIR-V. GetTypeInstruction/FindPointerToType append, and the variables are
|
|
// appended (or re-appended) below, so all three lookups run first for every target.
|
|
for (auto& target : targets) {
|
|
analysis::Vector packedVectorType(uintType, target.componentCount * 2u);
|
|
target.packedTypeId = typeMgr->GetTypeInstruction(&packedVectorType);
|
|
target.packedPointerTypeId =
|
|
typeMgr->FindPointerToType(target.packedTypeId, spv::StorageClass::Input);
|
|
target.privatePointerTypeId =
|
|
typeMgr->FindPointerToType(target.doubleTypeId, spv::StorageClass::Private);
|
|
}
|
|
|
|
for (const auto& target : targets) {
|
|
Instruction* variable = target.variable;
|
|
const Uint32 oldVariableId = variable->result_id();
|
|
const Uint32 packedTypeId = target.packedTypeId;
|
|
const Uint32 packedPointerTypeId = target.packedPointerTypeId;
|
|
|
|
const Uint32 packedVariableId = irContext->TakeNextId();
|
|
irContext->AddGlobalValue(spvtools::MakeUnique<Instruction>(
|
|
irContext, spv::Op::OpVariable, packedPointerTypeId, packedVariableId,
|
|
std::initializer_list<Operand>{
|
|
{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
|
{static_cast<Uint32>(spv::StorageClass::Input)}}}));
|
|
|
|
// The interface decorations belong to whatever is actually the Input now.
|
|
std::vector<Instruction*> deadDecorations;
|
|
for (auto& annotation : irContext->annotations()) {
|
|
if (annotation.opcode() != spv::Op::OpDecorate) continue;
|
|
if (annotation.GetSingleWordInOperand(0) != oldVariableId) continue;
|
|
const auto decoration =
|
|
static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(1));
|
|
if (decoration == spv::Decoration::Location ||
|
|
decoration == spv::Decoration::Component ||
|
|
decoration == spv::Decoration::RelaxedPrecision) {
|
|
annotation.SetInOperand(0, {packedVariableId});
|
|
} else {
|
|
deadDecorations.push_back(&annotation);
|
|
}
|
|
}
|
|
for (auto* annotation : deadDecorations) {
|
|
irContext->KillInst(annotation);
|
|
}
|
|
|
|
// Demote the original to a Private global: every existing OpLoad /
|
|
// OpAccessChain on it stays valid and keeps its double type. It is also moved to
|
|
// the end of the section, because the pointer-to-Private type it now names was
|
|
// appended above and a variable may not forward-reference its own type.
|
|
variable->SetResultType(target.privatePointerTypeId);
|
|
variable->SetInOperand(0, {static_cast<Uint32>(spv::StorageClass::Private)});
|
|
variable->RemoveFromList();
|
|
irContext->AddGlobalValue(std::unique_ptr<Instruction>(variable));
|
|
|
|
// SPIR-V 1.3 lists only Input/Output in the entry-point interface.
|
|
std::vector<Operand> interfaceOperands;
|
|
for (Uint32 i = 0; i < entryPoint->NumInOperands(); ++i) {
|
|
const Operand& operand = entryPoint->GetInOperand(i);
|
|
if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID &&
|
|
entryPoint->GetSingleWordInOperand(i) == oldVariableId) {
|
|
interfaceOperands.push_back({SPV_OPERAND_TYPE_ID, {packedVariableId}});
|
|
continue;
|
|
}
|
|
interfaceOperands.push_back(operand);
|
|
}
|
|
entryPoint->SetInOperands(std::move(interfaceOperands));
|
|
|
|
const Uint32 loadedId = irContext->TakeNextId();
|
|
const Uint32 bitcastId = irContext->TakeNextId();
|
|
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
|
irContext, spv::Op::OpLoad, packedTypeId, loadedId,
|
|
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {packedVariableId}}}));
|
|
++insertPoint;
|
|
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
|
irContext, spv::Op::OpBitcast, target.doubleTypeId, bitcastId,
|
|
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {loadedId}}}));
|
|
++insertPoint;
|
|
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
|
irContext, spv::Op::OpStore, 0, 0,
|
|
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {oldVariableId}},
|
|
{SPV_OPERAND_TYPE_ID, {bitcastId}}}));
|
|
++insertPoint;
|
|
}
|
|
|
|
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
|
return Status::SuccessWithChange;
|
|
}
|
|
|
|
spvtools::Optimizer::PassToken PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass() {
|
|
return spvtools::Optimizer::PassToken(MakeUnique<PackDoubleVertexInputsPass>());
|
|
}
|
|
} // namespace ShaderTranspiler
|
|
} // namespace MG_Util
|
|
} // namespace MobileGL
|