mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-10 11:33:24 +09:00
511 lines
30 KiB
C++
511 lines
30 KiB
C++
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp
|
|
// Copyright (c) 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 "DeferGeometryEmitsPass.h"
|
|
|
|
#include "spirv.hpp"
|
|
#include "source/opt/basic_block.h"
|
|
#include "source/opt/constants.h"
|
|
#include "source/opt/decoration_manager.h"
|
|
#include "source/opt/def_use_manager.h"
|
|
#include "source/opt/function.h"
|
|
#include "source/opt/instruction.h"
|
|
#include "source/opt/ir_context.h"
|
|
#include "source/opt/module.h"
|
|
#include "source/opt/type_manager.h"
|
|
#include "source/util/make_unique.h"
|
|
#include "source/util/string_utils.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace MobileGL {
|
|
namespace MG_Util {
|
|
namespace ShaderTranspiler {
|
|
namespace {
|
|
using spvtools::opt::BasicBlock;
|
|
using spvtools::opt::Function;
|
|
using spvtools::opt::Instruction;
|
|
using spvtools::opt::IRContext;
|
|
using spvtools::opt::Operand;
|
|
using OperandList = std::vector<Operand>;
|
|
|
|
// Scalars of snapshot per invocation above which the pass declines. A geometry
|
|
// stage's own output budget (GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS) is 1024 on
|
|
// every ES 3.2 driver measured, so a legal shader stays well inside this; the cap
|
|
// only guards against a pathological one turning into a huge private array.
|
|
constexpr uint32_t kMaxSnapshotScalars = 8192;
|
|
|
|
Operand Id(uint32_t id) { return {SPV_OPERAND_TYPE_ID, {id}}; }
|
|
|
|
std::unique_ptr<Instruction> Make(IRContext* context, spv::Op op, uint32_t typeId, uint32_t resultId,
|
|
OperandList operands) {
|
|
return spvtools::MakeUnique<Instruction>(context, op, typeId, resultId, operands);
|
|
}
|
|
|
|
// The value of an OpConstant integer, or nullopt for anything else (a spec
|
|
// constant included: a length that can change after the pass cannot size a
|
|
// snapshot).
|
|
Optional<uint32_t> ConstantValue(IRContext* context, uint32_t id) {
|
|
const Instruction* def = context->get_def_use_mgr()->GetDef(id);
|
|
if (def == nullptr || def->opcode() != spv::Op::OpConstant || def->NumInOperands() < 1)
|
|
return std::nullopt;
|
|
return def->GetSingleWordInOperand(0);
|
|
}
|
|
|
|
// Scalar components a value of this type holds, or nullopt when it holds anything
|
|
// the snapshot cannot copy by value (a pointer, an opaque handle, a runtime array).
|
|
Optional<uint32_t> ScalarCount(IRContext* context, uint32_t typeId) {
|
|
const Instruction* type = context->get_def_use_mgr()->GetDef(typeId);
|
|
if (type == nullptr) return std::nullopt;
|
|
switch (type->opcode()) {
|
|
case spv::Op::OpTypeInt:
|
|
case spv::Op::OpTypeFloat:
|
|
case spv::Op::OpTypeBool:
|
|
return 1u;
|
|
case spv::Op::OpTypeVector:
|
|
case spv::Op::OpTypeMatrix: {
|
|
const auto inner = ScalarCount(context, type->GetSingleWordInOperand(0));
|
|
if (!inner) return std::nullopt;
|
|
return *inner * type->GetSingleWordInOperand(1);
|
|
}
|
|
case spv::Op::OpTypeArray: {
|
|
const auto inner = ScalarCount(context, type->GetSingleWordInOperand(0));
|
|
const auto length = ConstantValue(context, type->GetSingleWordInOperand(1));
|
|
if (!inner || !length) return std::nullopt;
|
|
return *inner * *length;
|
|
}
|
|
case spv::Op::OpTypeStruct: {
|
|
uint32_t total = 0;
|
|
for (uint32_t i = 0; i < type->NumInOperands(); ++i) {
|
|
const auto inner = ScalarCount(context, type->GetSingleWordInOperand(i));
|
|
if (!inner) return std::nullopt;
|
|
total += *inner;
|
|
}
|
|
return total;
|
|
}
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
spv::StorageClass PointerStorageClass(IRContext* context, uint32_t pointerId) {
|
|
const Instruction* pointer = context->get_def_use_mgr()->GetDef(pointerId);
|
|
if (pointer == nullptr || pointer->type_id() == 0) return spv::StorageClass::Max;
|
|
const Instruction* type = context->get_def_use_mgr()->GetDef(pointer->type_id());
|
|
if (type == nullptr || type->opcode() != spv::Op::OpTypePointer) return spv::StorageClass::Max;
|
|
return static_cast<spv::StorageClass>(type->GetSingleWordInOperand(0));
|
|
}
|
|
|
|
// Every way a shader writes memory another invocation - or the application - can
|
|
// see: a store into a buffer or image block, an image store, any atomic.
|
|
Bool IsMemoryWrite(IRContext* context, const Instruction& inst) {
|
|
switch (inst.opcode()) {
|
|
case spv::Op::OpImageWrite:
|
|
case spv::Op::OpAtomicStore:
|
|
case spv::Op::OpAtomicExchange:
|
|
case spv::Op::OpAtomicCompareExchange:
|
|
case spv::Op::OpAtomicCompareExchangeWeak:
|
|
case spv::Op::OpAtomicIIncrement:
|
|
case spv::Op::OpAtomicIDecrement:
|
|
case spv::Op::OpAtomicIAdd:
|
|
case spv::Op::OpAtomicISub:
|
|
case spv::Op::OpAtomicSMin:
|
|
case spv::Op::OpAtomicUMin:
|
|
case spv::Op::OpAtomicSMax:
|
|
case spv::Op::OpAtomicUMax:
|
|
case spv::Op::OpAtomicAnd:
|
|
case spv::Op::OpAtomicOr:
|
|
case spv::Op::OpAtomicXor:
|
|
case spv::Op::OpAtomicFlagTestAndSet:
|
|
case spv::Op::OpAtomicFlagClear:
|
|
case spv::Op::OpAtomicFAddEXT:
|
|
case spv::Op::OpAtomicFMinEXT:
|
|
case spv::Op::OpAtomicFMaxEXT:
|
|
return true;
|
|
case spv::Op::OpStore: {
|
|
const spv::StorageClass storage = PointerStorageClass(context, inst.GetSingleWordInOperand(0));
|
|
return storage == spv::StorageClass::StorageBuffer || storage == spv::StorageClass::Uniform ||
|
|
storage == spv::StorageClass::Image ||
|
|
storage == spv::StorageClass::PhysicalStorageBuffer;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Whether anything stores through this pointer, directly or through an access
|
|
// chain into it. A pointer handed to a function call counts as stored: the callee
|
|
// is not followed, and an output passed by pointer is one the shader means to set.
|
|
Bool IsStoredThrough(IRContext* context, Instruction* pointer) {
|
|
Bool stored = false;
|
|
context->get_def_use_mgr()->WhileEachUser(pointer, [&](Instruction* user) {
|
|
switch (user->opcode()) {
|
|
case spv::Op::OpStore:
|
|
if (user->GetSingleWordInOperand(0) == pointer->result_id()) stored = true;
|
|
break;
|
|
case spv::Op::OpCopyMemory:
|
|
case spv::Op::OpCopyMemorySized:
|
|
if (user->GetSingleWordInOperand(0) == pointer->result_id()) stored = true;
|
|
break;
|
|
case spv::Op::OpFunctionCall:
|
|
stored = true;
|
|
break;
|
|
case spv::Op::OpAccessChain:
|
|
case spv::Op::OpInBoundsAccessChain:
|
|
stored = IsStoredThrough(context, user);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return !stored;
|
|
});
|
|
return stored;
|
|
}
|
|
|
|
// One output value the snapshot carries: a whole output variable, or one member
|
|
// of an output block.
|
|
struct Slot {
|
|
uint32_t variableId = 0;
|
|
Int64 member = -1;
|
|
uint32_t valueTypeId = 0;
|
|
// Filled once the globals exist.
|
|
uint32_t memberIndexId = 0;
|
|
uint32_t outputPointerTypeId = 0;
|
|
uint32_t snapshotVariableId = 0;
|
|
uint32_t snapshotElementPointerTypeId = 0;
|
|
};
|
|
|
|
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(Make(context, spv::Op::OpName, 0, 0,
|
|
{Id(id), {SPV_OPERAND_TYPE_LITERAL_STRING,
|
|
spvtools::utils::MakeVector(name)}}));
|
|
}
|
|
} // namespace
|
|
|
|
spvtools::opt::Pass::Status DeferGeometryEmitsPass::Process() {
|
|
IRContext* irContext = context();
|
|
if (m_outDeferred != nullptr) *m_outDeferred = false;
|
|
|
|
// One geometry entry point, sized by max_vertices.
|
|
Instruction* entryPoint = nullptr;
|
|
for (Instruction& candidate : irContext->module()->entry_points()) {
|
|
if (entryPoint != nullptr) return Status::SuccessWithoutChange;
|
|
entryPoint = &candidate;
|
|
}
|
|
if (entryPoint == nullptr ||
|
|
static_cast<spv::ExecutionModel>(entryPoint->GetSingleWordInOperand(0)) !=
|
|
spv::ExecutionModel::Geometry) {
|
|
return Status::SuccessWithoutChange;
|
|
}
|
|
const uint32_t bodyFunctionId = entryPoint->GetSingleWordInOperand(1);
|
|
uint32_t maxVertices = 0;
|
|
for (const Instruction& mode : irContext->module()->execution_modes()) {
|
|
if (mode.opcode() == spv::Op::OpExecutionMode &&
|
|
mode.GetSingleWordInOperand(0) == bodyFunctionId &&
|
|
static_cast<spv::ExecutionMode>(mode.GetSingleWordInOperand(1)) ==
|
|
spv::ExecutionMode::OutputVertices) {
|
|
maxVertices = mode.GetSingleWordInOperand(2);
|
|
}
|
|
}
|
|
if (maxVertices == 0) return Status::SuccessWithoutChange;
|
|
|
|
std::vector<Instruction*> emits;
|
|
std::vector<Instruction*> cuts;
|
|
Bool writesMemory = false;
|
|
for (Function& function : *irContext->module()) {
|
|
Bool streams = false;
|
|
function.ForEachInst([&](Instruction* inst) {
|
|
switch (inst->opcode()) {
|
|
case spv::Op::OpEmitVertex: emits.push_back(inst); break;
|
|
case spv::Op::OpEndPrimitive: cuts.push_back(inst); break;
|
|
case spv::Op::OpEmitStreamVertex:
|
|
case spv::Op::OpEndStreamPrimitive: streams = true; break;
|
|
default:
|
|
if (!writesMemory && IsMemoryWrite(irContext, *inst)) writesMemory = true;
|
|
break;
|
|
}
|
|
});
|
|
if (streams) return Status::SuccessWithoutChange;
|
|
}
|
|
if (emits.empty() || !writesMemory) return Status::SuccessWithoutChange;
|
|
|
|
// What the snapshot carries.
|
|
auto* defUse = irContext->get_def_use_mgr();
|
|
auto* decorations = irContext->get_decoration_mgr();
|
|
std::vector<Slot> slots;
|
|
uint64_t snapshotScalars = 0;
|
|
for (Instruction& global : irContext->types_values()) {
|
|
if (global.opcode() != spv::Op::OpVariable ||
|
|
static_cast<spv::StorageClass>(global.GetSingleWordInOperand(0)) != spv::StorageClass::Output) {
|
|
continue;
|
|
}
|
|
const Instruction* pointerType = defUse->GetDef(global.type_id());
|
|
const uint32_t valueTypeId = pointerType->GetSingleWordInOperand(1);
|
|
const Instruction* valueType = defUse->GetDef(valueTypeId);
|
|
const Bool block = valueType->opcode() == spv::Op::OpTypeStruct &&
|
|
decorations->HasDecoration(valueTypeId, uint32_t(spv::Decoration::Block));
|
|
if (!block) {
|
|
if (!IsStoredThrough(irContext, &global)) continue;
|
|
const auto scalars = ScalarCount(irContext, valueTypeId);
|
|
if (!scalars) return Status::SuccessWithoutChange;
|
|
snapshotScalars += uint64_t(*scalars) * maxVertices;
|
|
slots.push_back({global.result_id(), -1, valueTypeId});
|
|
continue;
|
|
}
|
|
// A block: per written member, so an unwritten builtin member stays inactive.
|
|
std::vector<Bool> written(valueType->NumInOperands(), false);
|
|
Bool dynamicMember = false;
|
|
defUse->ForEachUser(&global, [&](Instruction* user) {
|
|
if (user->opcode() == spv::Op::OpStore && user->GetSingleWordInOperand(0) == global.result_id()) {
|
|
std::fill(written.begin(), written.end(), true);
|
|
return;
|
|
}
|
|
if (user->opcode() == spv::Op::OpFunctionCall) {
|
|
std::fill(written.begin(), written.end(), true);
|
|
return;
|
|
}
|
|
if (user->opcode() != spv::Op::OpAccessChain && user->opcode() != spv::Op::OpInBoundsAccessChain)
|
|
return;
|
|
if (user->NumInOperands() < 2) return;
|
|
const auto member = ConstantValue(irContext, user->GetSingleWordInOperand(1));
|
|
if (!member || *member >= written.size()) {
|
|
dynamicMember = true;
|
|
return;
|
|
}
|
|
if (IsStoredThrough(irContext, user)) written[*member] = true;
|
|
});
|
|
if (dynamicMember) return Status::SuccessWithoutChange;
|
|
for (uint32_t member = 0; member < written.size(); ++member) {
|
|
if (!written[member]) continue;
|
|
const uint32_t memberTypeId = valueType->GetSingleWordInOperand(member);
|
|
const auto scalars = ScalarCount(irContext, memberTypeId);
|
|
if (!scalars) return Status::SuccessWithoutChange;
|
|
snapshotScalars += uint64_t(*scalars) * maxVertices;
|
|
slots.push_back({global.result_id(), Int64(member), memberTypeId});
|
|
}
|
|
}
|
|
if (snapshotScalars > kMaxSnapshotScalars) return Status::SuccessWithoutChange;
|
|
|
|
// ---- Types, constants and Private globals, all before any code moves ----------
|
|
auto* typeMgr = irContext->get_type_mgr();
|
|
auto* constMgr = irContext->get_constant_mgr();
|
|
const uint32_t uintTypeId = typeMgr->GetUIntTypeId();
|
|
const uint32_t boolTypeId = typeMgr->GetBoolTypeId();
|
|
const uint32_t voidTypeId = typeMgr->GetVoidTypeId();
|
|
const uint32_t zeroId = constMgr->GetUIntConstId(0);
|
|
const uint32_t oneId = constMgr->GetUIntConstId(1);
|
|
const uint32_t lengthId = constMgr->GetUIntConstId(maxVertices);
|
|
const uint32_t lastId = constMgr->GetUIntConstId(maxVertices - 1);
|
|
const uint32_t cutLengthId = constMgr->GetUIntConstId(maxVertices + 1);
|
|
const uint32_t privateUintPointerId = typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Private);
|
|
const uint32_t functionUintPointerId = typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Function);
|
|
for (Slot& slot : slots) {
|
|
slot.snapshotElementPointerTypeId =
|
|
typeMgr->FindPointerToType(slot.valueTypeId, spv::StorageClass::Private);
|
|
if (slot.member >= 0) {
|
|
slot.memberIndexId = constMgr->GetUIntConstId(uint32_t(slot.member));
|
|
slot.outputPointerTypeId = typeMgr->FindPointerToType(slot.valueTypeId, spv::StorageClass::Output);
|
|
}
|
|
}
|
|
const uint32_t bodyFunctionTypeId = irContext->GetFunction(bodyFunctionId)->DefInst().GetSingleWordInOperand(1);
|
|
|
|
// Hand-built from here on; analyses are rebuilt once at the end.
|
|
auto newPrivateArray = [&](uint32_t elementTypeId, uint32_t lengthConstantId) {
|
|
const uint32_t arrayTypeId = irContext->TakeNextId();
|
|
irContext->AddType(Make(irContext, spv::Op::OpTypeArray, 0, arrayTypeId,
|
|
{Id(elementTypeId), Id(lengthConstantId)}));
|
|
const uint32_t pointerTypeId = irContext->TakeNextId();
|
|
irContext->AddType(Make(irContext, spv::Op::OpTypePointer, 0, pointerTypeId,
|
|
{{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Private)}},
|
|
Id(arrayTypeId)}));
|
|
const uint32_t variableId = irContext->TakeNextId();
|
|
irContext->AddGlobalValue(Make(irContext, spv::Op::OpVariable, pointerTypeId, variableId,
|
|
{{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
|
{uint32_t(spv::StorageClass::Private)}}}));
|
|
return variableId;
|
|
};
|
|
for (Slot& slot : slots) slot.snapshotVariableId = newPrivateArray(slot.valueTypeId, lengthId);
|
|
// One slot past max_vertices: where an EndPrimitive with no vertex before it lands.
|
|
const uint32_t cutVariableId = newPrivateArray(uintTypeId, cutLengthId);
|
|
const uint32_t countVariableId = irContext->TakeNextId();
|
|
irContext->AddGlobalValue(Make(irContext, spv::Op::OpVariable, privateUintPointerId, countVariableId,
|
|
{{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
|
{uint32_t(spv::StorageClass::Private)}}}));
|
|
ReplaceName(irContext, countVariableId, "mg_deferredVertexCount");
|
|
ReplaceName(irContext, cutVariableId, "mg_deferredStripEnd");
|
|
|
|
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
|
|
|
// Emits a value-producing instruction before |at| and returns its id.
|
|
auto before = [&](Instruction* at, spv::Op op, uint32_t typeId, OperandList operands) {
|
|
const uint32_t id = typeId != 0 ? irContext->TakeNextId() : 0;
|
|
at->InsertBefore(Make(irContext, op, typeId, id, std::move(operands)));
|
|
return id;
|
|
};
|
|
auto outputPointer = [&](Instruction* at, const Slot& slot) {
|
|
if (slot.member < 0) return slot.variableId;
|
|
return before(at, spv::Op::OpAccessChain, slot.outputPointerTypeId,
|
|
{Id(slot.variableId), Id(slot.memberIndexId)});
|
|
};
|
|
|
|
// ---- Each EmitVertex records; each EndPrimitive marks ---------------------------
|
|
for (Instruction* emit : emits) {
|
|
const uint32_t count = before(emit, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)});
|
|
const uint32_t inRange = before(emit, spv::Op::OpULessThan, boolTypeId, {Id(count), Id(lengthId)});
|
|
const uint32_t index = before(emit, spv::Op::OpSelect, uintTypeId, {Id(inRange), Id(count), Id(lastId)});
|
|
for (const Slot& slot : slots) {
|
|
const uint32_t source = outputPointer(emit, slot);
|
|
const uint32_t value = before(emit, spv::Op::OpLoad, slot.valueTypeId, {Id(source)});
|
|
const uint32_t target = before(emit, spv::Op::OpAccessChain, slot.snapshotElementPointerTypeId,
|
|
{Id(slot.snapshotVariableId), Id(index)});
|
|
before(emit, spv::Op::OpStore, 0, {Id(target), Id(value)});
|
|
}
|
|
const uint32_t cut = before(emit, spv::Op::OpAccessChain, privateUintPointerId,
|
|
{Id(cutVariableId), Id(index)});
|
|
before(emit, spv::Op::OpStore, 0, {Id(cut), Id(zeroId)});
|
|
const uint32_t next = before(emit, spv::Op::OpIAdd, uintTypeId, {Id(count), Id(oneId)});
|
|
before(emit, spv::Op::OpStore, 0, {Id(countVariableId), Id(next)});
|
|
}
|
|
for (Instruction* end : cuts) {
|
|
const uint32_t count = before(end, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)});
|
|
const uint32_t any = before(end, spv::Op::OpINotEqual, boolTypeId, {Id(count), Id(zeroId)});
|
|
const uint32_t previous = before(end, spv::Op::OpISub, uintTypeId, {Id(count), Id(oneId)});
|
|
const uint32_t inRange = before(end, spv::Op::OpULessThan, boolTypeId, {Id(previous), Id(lengthId)});
|
|
const uint32_t clamped =
|
|
before(end, spv::Op::OpSelect, uintTypeId, {Id(inRange), Id(previous), Id(lastId)});
|
|
const uint32_t index = before(end, spv::Op::OpSelect, uintTypeId, {Id(any), Id(clamped), Id(lengthId)});
|
|
const uint32_t cut = before(end, spv::Op::OpAccessChain, privateUintPointerId,
|
|
{Id(cutVariableId), Id(index)});
|
|
before(end, spv::Op::OpStore, 0, {Id(cut), Id(oneId)});
|
|
}
|
|
for (Instruction* inst : emits) irContext->KillInst(inst);
|
|
for (Instruction* inst : cuts) irContext->KillInst(inst);
|
|
|
|
// ---- The new entry point: run the body, then replay -----------------------------
|
|
const uint32_t entryId = irContext->TakeNextId();
|
|
auto function = spvtools::MakeUnique<Function>(
|
|
Make(irContext, spv::Op::OpFunction, voidTypeId, entryId,
|
|
{{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {0}}, Id(bodyFunctionTypeId)}));
|
|
Function* entry = function.get();
|
|
const uint32_t startLabel = irContext->TakeNextId();
|
|
const uint32_t headerLabel = irContext->TakeNextId();
|
|
const uint32_t checkLabel = irContext->TakeNextId();
|
|
const uint32_t replayLabel = irContext->TakeNextId();
|
|
const uint32_t endStripLabel = irContext->TakeNextId();
|
|
const uint32_t afterStripLabel = irContext->TakeNextId();
|
|
const uint32_t continueLabel = irContext->TakeNextId();
|
|
const uint32_t mergeLabel = irContext->TakeNextId();
|
|
auto newBlock = [&](uint32_t label) {
|
|
auto block = spvtools::MakeUnique<BasicBlock>(Make(irContext, spv::Op::OpLabel, 0, label, {}));
|
|
block->SetParent(entry);
|
|
return block;
|
|
};
|
|
auto add = [&](BasicBlock& block, spv::Op op, uint32_t typeId, OperandList operands) {
|
|
const uint32_t id = typeId != 0 ? irContext->TakeNextId() : 0;
|
|
block.AddInstruction(Make(irContext, op, typeId, id, std::move(operands)));
|
|
return id;
|
|
};
|
|
|
|
auto start = newBlock(startLabel);
|
|
const uint32_t iterator = add(*start, spv::Op::OpVariable, functionUintPointerId,
|
|
{{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}});
|
|
add(*start, spv::Op::OpStore, 0, {Id(countVariableId), Id(zeroId)});
|
|
add(*start, spv::Op::OpFunctionCall, voidTypeId, {Id(bodyFunctionId)});
|
|
const uint32_t recorded = add(*start, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)});
|
|
const uint32_t fits = add(*start, spv::Op::OpULessThan, boolTypeId, {Id(recorded), Id(lengthId)});
|
|
const uint32_t limit = add(*start, spv::Op::OpSelect, uintTypeId, {Id(fits), Id(recorded), Id(lengthId)});
|
|
add(*start, spv::Op::OpStore, 0, {Id(iterator), Id(zeroId)});
|
|
add(*start, spv::Op::OpBranch, 0, {Id(headerLabel)});
|
|
|
|
auto header = newBlock(headerLabel);
|
|
add(*header, spv::Op::OpLoopMerge, 0,
|
|
{Id(mergeLabel), Id(continueLabel), {SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}});
|
|
add(*header, spv::Op::OpBranch, 0, {Id(checkLabel)});
|
|
|
|
auto check = newBlock(checkLabel);
|
|
const uint32_t vertex = add(*check, spv::Op::OpLoad, uintTypeId, {Id(iterator)});
|
|
const uint32_t more = add(*check, spv::Op::OpULessThan, boolTypeId, {Id(vertex), Id(limit)});
|
|
add(*check, spv::Op::OpBranchConditional, 0, {Id(more), Id(replayLabel), Id(mergeLabel)});
|
|
|
|
auto replay = newBlock(replayLabel);
|
|
for (const Slot& slot : slots) {
|
|
const uint32_t source = add(*replay, spv::Op::OpAccessChain, slot.snapshotElementPointerTypeId,
|
|
{Id(slot.snapshotVariableId), Id(vertex)});
|
|
const uint32_t value = add(*replay, spv::Op::OpLoad, slot.valueTypeId, {Id(source)});
|
|
uint32_t target = slot.variableId;
|
|
if (slot.member >= 0) {
|
|
target = add(*replay, spv::Op::OpAccessChain, slot.outputPointerTypeId,
|
|
{Id(slot.variableId), Id(slot.memberIndexId)});
|
|
}
|
|
add(*replay, spv::Op::OpStore, 0, {Id(target), Id(value)});
|
|
}
|
|
add(*replay, spv::Op::OpEmitVertex, 0, {});
|
|
const uint32_t cutPointer =
|
|
add(*replay, spv::Op::OpAccessChain, privateUintPointerId, {Id(cutVariableId), Id(vertex)});
|
|
const uint32_t cutValue = add(*replay, spv::Op::OpLoad, uintTypeId, {Id(cutPointer)});
|
|
const uint32_t endsStrip = add(*replay, spv::Op::OpINotEqual, boolTypeId, {Id(cutValue), Id(zeroId)});
|
|
add(*replay, spv::Op::OpSelectionMerge, 0,
|
|
{Id(afterStripLabel), {SPV_OPERAND_TYPE_SELECTION_CONTROL, {0}}});
|
|
add(*replay, spv::Op::OpBranchConditional, 0, {Id(endsStrip), Id(endStripLabel), Id(afterStripLabel)});
|
|
|
|
auto endStrip = newBlock(endStripLabel);
|
|
add(*endStrip, spv::Op::OpEndPrimitive, 0, {});
|
|
add(*endStrip, spv::Op::OpBranch, 0, {Id(afterStripLabel)});
|
|
|
|
auto afterStrip = newBlock(afterStripLabel);
|
|
add(*afterStrip, spv::Op::OpBranch, 0, {Id(continueLabel)});
|
|
|
|
auto continueBlock = newBlock(continueLabel);
|
|
const uint32_t current = add(*continueBlock, spv::Op::OpLoad, uintTypeId, {Id(iterator)});
|
|
const uint32_t next = add(*continueBlock, spv::Op::OpIAdd, uintTypeId, {Id(current), Id(oneId)});
|
|
add(*continueBlock, spv::Op::OpStore, 0, {Id(iterator), Id(next)});
|
|
add(*continueBlock, spv::Op::OpBranch, 0, {Id(headerLabel)});
|
|
|
|
auto merge = newBlock(mergeLabel);
|
|
add(*merge, spv::Op::OpReturn, 0, {});
|
|
|
|
entry->AddBasicBlock(std::move(start));
|
|
entry->AddBasicBlock(std::move(header));
|
|
entry->AddBasicBlock(std::move(check));
|
|
entry->AddBasicBlock(std::move(replay));
|
|
entry->AddBasicBlock(std::move(endStrip));
|
|
entry->AddBasicBlock(std::move(afterStrip));
|
|
entry->AddBasicBlock(std::move(continueBlock));
|
|
entry->AddBasicBlock(std::move(merge));
|
|
entry->SetFunctionEnd(Make(irContext, spv::Op::OpFunctionEnd, 0, 0, {}));
|
|
irContext->AddFunction(std::move(function));
|
|
|
|
// The entry point and its execution modes now name the new function.
|
|
entryPoint->SetInOperand(1, {entryId});
|
|
for (Instruction& mode : irContext->module()->execution_modes()) {
|
|
if (mode.GetSingleWordInOperand(0) == bodyFunctionId) mode.SetInOperand(0, {entryId});
|
|
}
|
|
ReplaceName(irContext, bodyFunctionId, "mg_geometryBody");
|
|
ReplaceName(irContext, entryId, "main");
|
|
|
|
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
|
if (m_outDeferred != nullptr) *m_outDeferred = true;
|
|
return Status::SuccessWithChange;
|
|
}
|
|
|
|
spvtools::Optimizer::PassToken DeferGeometryEmitsPass::CreateDeferGeometryEmitsPass(Bool* outDeferred) {
|
|
return spvtools::Optimizer::PassToken(spvtools::MakeUnique<DeferGeometryEmitsPass>(outDeferred));
|
|
}
|
|
} // namespace ShaderTranspiler
|
|
} // namespace MG_Util
|
|
} // namespace MobileGL
|