/** ****************************************************************************** * 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. * ****************************************************************************** */ #include "xenia/gpu/spirv_builder.h" #include "xenia/base/assert.h" namespace xe { namespace gpu { spv::Id SpirvBuilder::createQuadOp(spv::Op op_code, spv::Id type_id, spv::Id operand1, spv::Id operand2, spv::Id operand3, spv::Id operand4) { if (generatingOpCodeForSpecConst) { std::vector operands(4); operands[0] = operand1; operands[1] = operand2; operands[2] = operand3; operands[3] = operand4; return createSpecConstantOp(op_code, type_id, operands, std::vector()); } std::unique_ptr op = std::make_unique(getUniqueId(), type_id, op_code); op->addIdOperand(operand1); op->addIdOperand(operand2); op->addIdOperand(operand3); op->addIdOperand(operand4); spv::Id result = op->getResultId(); buildPoint->addInstruction(std::move(op)); return result; } spv::Id SpirvBuilder::createNoContractionUnaryOp(spv::Op op_code, spv::Id type_id, spv::Id operand) { spv::Id result = createUnaryOp(op_code, type_id, operand); addDecoration(result, spv::DecorationNoContraction); return result; } spv::Id SpirvBuilder::createNoContractionBinOp(spv::Op op_code, spv::Id type_id, spv::Id operand1, spv::Id operand2) { spv::Id result = createBinOp(op_code, type_id, operand1, operand2); addDecoration(result, spv::DecorationNoContraction); return result; } spv::Id SpirvBuilder::createUnaryBuiltinCall(spv::Id result_type, spv::Id builtins, int entry_point, spv::Id operand) { std::unique_ptr instruction = std::make_unique(getUniqueId(), result_type, spv::OpExtInst); instruction->addIdOperand(builtins); instruction->addImmediateOperand(entry_point); instruction->addIdOperand(operand); spv::Id result = instruction->getResultId(); getBuildPoint()->addInstruction(std::move(instruction)); return result; } spv::Id SpirvBuilder::createBinBuiltinCall(spv::Id result_type, spv::Id builtins, int entry_point, spv::Id operand1, spv::Id operand2) { std::unique_ptr instruction = std::make_unique(getUniqueId(), result_type, spv::OpExtInst); instruction->addIdOperand(builtins); instruction->addImmediateOperand(entry_point); instruction->addIdOperand(operand1); instruction->addIdOperand(operand2); spv::Id result = instruction->getResultId(); getBuildPoint()->addInstruction(std::move(instruction)); return result; } spv::Id SpirvBuilder::createTriBuiltinCall(spv::Id result_type, spv::Id builtins, int entry_point, spv::Id operand1, spv::Id operand2, spv::Id operand3) { std::unique_ptr instruction = std::make_unique(getUniqueId(), result_type, spv::OpExtInst); instruction->addIdOperand(builtins); instruction->addImmediateOperand(entry_point); instruction->addIdOperand(operand1); instruction->addIdOperand(operand2); instruction->addIdOperand(operand3); spv::Id result = instruction->getResultId(); getBuildPoint()->addInstruction(std::move(instruction)); return result; } SpirvBuilder::IfBuilder::IfBuilder(spv::Id condition, unsigned int control, SpirvBuilder& builder, unsigned int thenWeight, unsigned int elseWeight) : builder(builder), condition(condition), control(control), thenWeight(thenWeight), elseWeight(elseWeight), function(builder.getBuildPoint()->getParent()) { // Make the blocks, but only put the then-block into the function, the // else-block and merge-block will be added later, in order, after earlier // code is emitted. thenBlock = new spv::Block(builder.getUniqueId(), function); elseBlock = nullptr; mergeBlock = new spv::Block(builder.getUniqueId(), function); // Save the current block, so that we can add in the flow control split when // makeEndIf is called. headerBlock = builder.getBuildPoint(); spv::Id headerBlockId = headerBlock->getId(); thenPhiParent = headerBlockId; elsePhiParent = headerBlockId; function.addBlock(thenBlock); builder.setBuildPoint(thenBlock); } void SpirvBuilder::IfBuilder::makeBeginElse(bool branchToMerge) { #ifndef NDEBUG assert_true(currentBranch == Branch::kThen); #endif if (branchToMerge) { // Close out the "then" by having it jump to the mergeBlock. thenPhiParent = builder.getBuildPoint()->getId(); builder.createBranch(mergeBlock); } // Make the first else block and add it to the function. elseBlock = new spv::Block(builder.getUniqueId(), function); function.addBlock(elseBlock); // Start building the else block. builder.setBuildPoint(elseBlock); #ifndef NDEBUG currentBranch = Branch::kElse; #endif } void SpirvBuilder::IfBuilder::makeEndIf(bool branchToMerge) { #ifndef NDEBUG assert_true(currentBranch == Branch::kThen || currentBranch == Branch::kElse); #endif if (branchToMerge) { // Jump to the merge block. (elseBlock ? elsePhiParent : thenPhiParent) = builder.getBuildPoint()->getId(); builder.createBranch(mergeBlock); } // Go back to the headerBlock and make the flow control split. builder.setBuildPoint(headerBlock); builder.createSelectionMerge(mergeBlock, control); { spv::Block* falseBlock = elseBlock ? elseBlock : mergeBlock; std::unique_ptr branch = std::make_unique(spv::OpBranchConditional); branch->addIdOperand(condition); branch->addIdOperand(thenBlock->getId()); branch->addIdOperand(falseBlock->getId()); if (thenWeight || elseWeight) { branch->addImmediateOperand(thenWeight); branch->addImmediateOperand(elseWeight); } builder.getBuildPoint()->addInstruction(std::move(branch)); thenBlock->addPredecessor(builder.getBuildPoint()); falseBlock->addPredecessor(builder.getBuildPoint()); } // Add the merge block to the function. function.addBlock(mergeBlock); builder.setBuildPoint(mergeBlock); #ifndef NDEBUG currentBranch = Branch::kMerge; #endif } spv::Id SpirvBuilder::IfBuilder::createMergePhi(spv::Id then_variable, spv::Id else_variable) const { assert_true(builder.getBuildPoint() == mergeBlock); return builder.createQuadOp(spv::OpPhi, builder.getTypeId(then_variable), then_variable, getThenPhiParent(), else_variable, getElsePhiParent()); } SpirvBuilder::SwitchBuilder::SwitchBuilder(spv::Id selector, unsigned int selection_control, SpirvBuilder& builder) : builder_(builder), selector_(selector), selection_control_(selection_control), function_(builder.getBuildPoint()->getParent()), header_block_(builder.getBuildPoint()), default_phi_parent_(builder.getBuildPoint()->getId()) { merge_block_ = new spv::Block(builder_.getUniqueId(), function_); } void SpirvBuilder::SwitchBuilder::makeBeginDefault() { assert_null(default_block_); endSegment(); default_block_ = new spv::Block(builder_.getUniqueId(), function_); function_.addBlock(default_block_); default_block_->addPredecessor(header_block_); builder_.setBuildPoint(default_block_); current_branch_ = Branch::kDefault; } void SpirvBuilder::SwitchBuilder::makeBeginCase(unsigned int literal) { endSegment(); auto case_block = new spv::Block(builder_.getUniqueId(), function_); function_.addBlock(case_block); cases_.emplace_back(literal, case_block->getId()); case_block->addPredecessor(header_block_); builder_.setBuildPoint(case_block); current_branch_ = Branch::kCase; } void SpirvBuilder::SwitchBuilder::addCurrentCaseLiteral(unsigned int literal) { assert_true(current_branch_ == Branch::kCase); cases_.emplace_back(literal, cases_.back().second); } void SpirvBuilder::SwitchBuilder::makeEndSwitch() { endSegment(); builder_.setBuildPoint(header_block_); builder_.createSelectionMerge(merge_block_, selection_control_); std::unique_ptr switch_instruction = std::make_unique(spv::OpSwitch); switch_instruction->addIdOperand(selector_); if (default_block_) { switch_instruction->addIdOperand(default_block_->getId()); } else { switch_instruction->addIdOperand(merge_block_->getId()); merge_block_->addPredecessor(header_block_); } for (const std::pair& case_pair : cases_) { switch_instruction->addImmediateOperand(case_pair.first); switch_instruction->addIdOperand(case_pair.second); } builder_.getBuildPoint()->addInstruction(std::move(switch_instruction)); function_.addBlock(merge_block_); builder_.setBuildPoint(merge_block_); current_branch_ = Branch::kMerge; } void SpirvBuilder::SwitchBuilder::endSegment() { assert_true(current_branch_ == Branch::kSelection || current_branch_ == Branch::kDefault || current_branch_ == Branch::kCase); if (current_branch_ == Branch::kSelection) { return; } if (!builder_.getBuildPoint()->isTerminated()) { builder_.createBranch(merge_block_); if (current_branch_ == Branch::kDefault) { default_phi_parent_ = builder_.getBuildPoint()->getId(); } } current_branch_ = Branch::kSelection; } } // namespace gpu } // namespace xe