197 lines
6.4 KiB
C++
197 lines
6.4 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2023 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_GPU_SPIRV_BUILDER_H_
|
|
#define XENIA_GPU_SPIRV_BUILDER_H_
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "third_party/glslang/SPIRV/SpvBuilder.h"
|
|
#include "xenia/base/assert.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
|
|
// SpvBuilder with extra helpers.
|
|
|
|
class SpirvBuilder : public spv::Builder {
|
|
public:
|
|
SpirvBuilder(unsigned int spv_version, unsigned int user_number,
|
|
spv::SpvBuildLogger* logger)
|
|
: spv::Builder(spv_version, user_number, logger) {}
|
|
|
|
// Make public rather than protected.
|
|
using spv::Builder::createSelectionMerge;
|
|
|
|
// Backward compatibility wrapper for createBranch
|
|
void createBranch(spv::Block* block) {
|
|
spv::Builder::createBranch(true, block);
|
|
}
|
|
|
|
// Backward compatibility wrapper for makeFunctionEntry
|
|
// For shaders, we use LinkageType::Max which means no linkage decoration
|
|
spv::Function* makeFunctionEntry(
|
|
spv::Decoration precision, spv::Id returnType, const char* name,
|
|
const std::vector<spv::Id>& paramTypes,
|
|
const std::vector<std::vector<spv::Decoration>>& precisions,
|
|
spv::Block** entry = nullptr) {
|
|
// LinkageType::Max means no linkage decoration will be added (correct for
|
|
// shader entry points)
|
|
return spv::Builder::makeFunctionEntry(precision, returnType, name,
|
|
spv::LinkageType::Max, paramTypes,
|
|
precisions, entry);
|
|
}
|
|
|
|
// Hide base class createAccessChain to workaround the way
|
|
// glslang 11.6.0+ uses internal accessChain state instead of parameters.
|
|
spv::Id createAccessChain(spv::StorageClass storage_class, spv::Id base,
|
|
const std::vector<spv::Id>& offsets) {
|
|
// glslang 11.6.0+ uses the accessChain member
|
|
// in getResultingAccessChainType() but doesn't populate it from the
|
|
// parameters. We need to set it up correctly before calling the parent.
|
|
clearAccessChain();
|
|
setAccessChainLValue(base);
|
|
for (const auto& offset : offsets) {
|
|
accessChainPush(offset, {}, 0);
|
|
}
|
|
|
|
spv::Id result =
|
|
spv::Builder::createAccessChain(storage_class, base, offsets);
|
|
|
|
// Clear the state again to avoid affecting subsequent operations
|
|
clearAccessChain();
|
|
|
|
return result;
|
|
}
|
|
|
|
spv::Id createQuadOp(spv::Op op_code, spv::Id type_id, spv::Id operand1,
|
|
spv::Id operand2, spv::Id operand3, spv::Id operand4);
|
|
|
|
spv::Id createNoContractionUnaryOp(spv::Op op_code, spv::Id type_id,
|
|
spv::Id operand);
|
|
spv::Id createNoContractionBinOp(spv::Op op_code, spv::Id type_id,
|
|
spv::Id operand1, spv::Id operand2);
|
|
|
|
spv::Id createUnaryBuiltinCall(spv::Id result_type, spv::Id builtins,
|
|
int entry_point, spv::Id operand);
|
|
spv::Id createBinBuiltinCall(spv::Id result_type, spv::Id builtins,
|
|
int entry_point, spv::Id operand1,
|
|
spv::Id operand2);
|
|
spv::Id createTriBuiltinCall(spv::Id result_type, spv::Id builtins,
|
|
int entry_point, spv::Id operand1,
|
|
spv::Id operand2, spv::Id operand3);
|
|
|
|
// Helper to use for building nested control flow with if-then-else with
|
|
// additions over SpvBuilder::If.
|
|
class IfBuilder {
|
|
public:
|
|
IfBuilder(spv::Id condition, spv::SelectionControlMask control,
|
|
SpirvBuilder& builder, unsigned int thenWeight = 0,
|
|
unsigned int elseWeight = 0);
|
|
|
|
~IfBuilder() {
|
|
#ifndef NDEBUG
|
|
assert_true(currentBranch == Branch::kMerge);
|
|
#endif
|
|
}
|
|
|
|
void makeBeginElse(bool branchToMerge = true);
|
|
void makeEndIf(bool branchToMerge = true);
|
|
|
|
// If there's no then/else block that branches to the merge block, the phi
|
|
// parent is the header block - this simplifies then-only usage.
|
|
spv::Id getThenPhiParent() const { return thenPhiParent; }
|
|
spv::Id getElsePhiParent() const { return elsePhiParent; }
|
|
|
|
spv::Id createMergePhi(spv::Id then_variable, spv::Id else_variable) const;
|
|
|
|
private:
|
|
enum class Branch {
|
|
kThen,
|
|
kElse,
|
|
kMerge,
|
|
};
|
|
|
|
IfBuilder(const IfBuilder& ifBuilder) = delete;
|
|
IfBuilder& operator=(const IfBuilder& ifBuilder) = delete;
|
|
|
|
SpirvBuilder& builder;
|
|
spv::Id condition;
|
|
spv::SelectionControlMask control;
|
|
unsigned int thenWeight;
|
|
unsigned int elseWeight;
|
|
|
|
spv::Function& function;
|
|
|
|
spv::Block* headerBlock;
|
|
spv::Block* thenBlock;
|
|
spv::Block* elseBlock;
|
|
spv::Block* mergeBlock;
|
|
|
|
spv::Id thenPhiParent;
|
|
spv::Id elsePhiParent;
|
|
|
|
#ifndef NDEBUG
|
|
Branch currentBranch = Branch::kThen;
|
|
#endif
|
|
};
|
|
|
|
// Simpler and more flexible (such as multiple cases pointing to the same
|
|
// block) compared to makeSwitch.
|
|
class SwitchBuilder {
|
|
public:
|
|
SwitchBuilder(spv::Id selector, spv::SelectionControlMask selection_control,
|
|
SpirvBuilder& builder);
|
|
~SwitchBuilder() { assert_true(current_branch_ == Branch::kMerge); }
|
|
|
|
void makeBeginDefault();
|
|
void makeBeginCase(unsigned int literal);
|
|
void addCurrentCaseLiteral(unsigned int literal);
|
|
void makeEndSwitch();
|
|
|
|
// If there's no default block that branches to the merge block, the phi
|
|
// parent is the header block - this simplifies case-only usage.
|
|
spv::Id getDefaultPhiParent() const { return default_phi_parent_; }
|
|
|
|
private:
|
|
enum class Branch {
|
|
kSelection,
|
|
kDefault,
|
|
kCase,
|
|
kMerge,
|
|
};
|
|
|
|
void endSegment();
|
|
|
|
SpirvBuilder& builder_;
|
|
spv::Id selector_;
|
|
spv::SelectionControlMask selection_control_;
|
|
|
|
spv::Function& function_;
|
|
|
|
spv::Block* header_block_;
|
|
spv::Block* merge_block_;
|
|
spv::Block* default_block_ = nullptr;
|
|
|
|
std::vector<std::pair<unsigned int, spv::Id>> cases_;
|
|
|
|
spv::Id default_phi_parent_;
|
|
|
|
Branch current_branch_ = Branch::kSelection;
|
|
};
|
|
};
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_GPU_SPIRV_BUILDER_H_
|