/** ****************************************************************************** * 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 #include #include #include #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& paramTypes, const std::vector>& 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& 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> cases_; spv::Id default_phi_parent_; Branch current_branch_ = Branch::kSelection; }; }; } // namespace gpu } // namespace xe #endif // XENIA_GPU_SPIRV_BUILDER_H_