/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/spirv_shader_translator.h" #include #include #include #include "third_party/glslang/SPIRV/GLSL.std.450.h" #include "xenia/base/assert.h" namespace xe { namespace gpu { SpirvShaderTranslator::SpirvShaderTranslator(bool supports_clip_distance, bool supports_cull_distance) : supports_clip_distance_(supports_clip_distance), supports_cull_distance_(supports_cull_distance) {} void SpirvShaderTranslator::Reset() { ShaderTranslator::Reset(); builder_.reset(); main_switch_op_.reset(); main_switch_next_pc_phi_operands_.clear(); cf_exec_conditional_merge_ = nullptr; cf_instruction_predicate_merge_ = nullptr; } void SpirvShaderTranslator::StartTranslation() { // Tool ID 26 "Xenia Emulator Microcode Translator". // https://github.com/KhronosGroup/SPIRV-Headers/blob/c43a43c7cc3af55910b9bec2a71e3e8a622443cf/include/spirv/spir-v.xml#L79 // TODO(Triang3l): Logger. builder_ = std::make_unique(1 << 16, (26 << 16) | 1, nullptr); builder_->addCapability(IsSpirvTessEvalShader() ? spv::CapabilityTessellation : spv::CapabilityShader); ext_inst_glsl_std_450_ = builder_->import("GLSL.std.450"); builder_->setMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); builder_->setSource(spv::SourceLanguageUnknown, 0); type_void_ = builder_->makeVoidType(); type_bool_ = builder_->makeBoolType(); type_int_ = builder_->makeIntType(32); type_int4_ = builder_->makeVectorType(type_int_, 4); type_uint_ = builder_->makeUintType(32); type_uint3_ = builder_->makeVectorType(type_uint_, 3); type_uint4_ = builder_->makeVectorType(type_uint_, 4); type_float_ = builder_->makeFloatType(32); type_float2_ = builder_->makeVectorType(type_float_, 2); type_float3_ = builder_->makeVectorType(type_float_, 3); type_float4_ = builder_->makeVectorType(type_float_, 4); const_int_0_ = builder_->makeIntConstant(0); id_vector_temp_.clear(); id_vector_temp_.reserve(4); for (uint32_t i = 0; i < 4; ++i) { id_vector_temp_.push_back(const_int_0_); } const_int4_0_ = builder_->makeCompositeConstant(type_int4_, id_vector_temp_); const_uint_0_ = builder_->makeUintConstant(0); id_vector_temp_.clear(); id_vector_temp_.reserve(4); for (uint32_t i = 0; i < 4; ++i) { id_vector_temp_.push_back(const_uint_0_); } const_uint4_0_ = builder_->makeCompositeConstant(type_uint4_, id_vector_temp_); const_float_0_ = builder_->makeFloatConstant(0.0f); id_vector_temp_.clear(); id_vector_temp_.reserve(4); for (uint32_t i = 0; i < 4; ++i) { id_vector_temp_.push_back(const_float_0_); } const_float4_0_ = builder_->makeCompositeConstant(type_float4_, id_vector_temp_); // Common uniform buffer - bool and loop constants. id_vector_temp_.clear(); id_vector_temp_.reserve(2); // 256 bool constants. id_vector_temp_.push_back(builder_->makeArrayType( type_uint4_, builder_->makeUintConstant(2), sizeof(uint32_t) * 4)); // Currently (as of October 24, 2020) makeArrayType only uses the stride to // check if deduplication can be done - the array stride decoration needs to // be applied explicitly. builder_->addDecoration(id_vector_temp_.back(), spv::DecorationArrayStride, sizeof(uint32_t) * 4); // 32 loop constants. id_vector_temp_.push_back(builder_->makeArrayType( type_uint4_, builder_->makeUintConstant(8), sizeof(uint32_t) * 4)); builder_->addDecoration(id_vector_temp_.back(), spv::DecorationArrayStride, sizeof(uint32_t) * 4); spv::Id type_bool_loop_constants = builder_->makeStructType(id_vector_temp_, "XeBoolLoopConstants"); builder_->addMemberName(type_bool_loop_constants, 0, "bool_constants"); builder_->addMemberDecoration(type_bool_loop_constants, 0, spv::DecorationOffset, 0); builder_->addMemberName(type_bool_loop_constants, 1, "loop_constants"); builder_->addMemberDecoration(type_bool_loop_constants, 1, spv::DecorationOffset, sizeof(uint32_t) * 8); builder_->addDecoration(type_bool_loop_constants, spv::DecorationBlock); uniform_bool_loop_constants_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassUniform, type_bool_loop_constants, "xe_uniform_bool_loop_constants"); builder_->addDecoration(uniform_bool_loop_constants_, spv::DecorationDescriptorSet, int(kDescriptorSetBoolLoopConstants)); builder_->addDecoration(uniform_bool_loop_constants_, spv::DecorationBinding, 0); if (IsSpirvVertexOrTessEvalShader()) { StartVertexOrTessEvalShaderBeforeMain(); } // Begin the main function. std::vector main_param_types; std::vector> main_precisions; spv::Block* function_main_entry; function_main_ = builder_->makeFunctionEntry( spv::NoPrecision, type_void_, "main", main_param_types, main_precisions, &function_main_entry); // Begin ucode translation. Initialize everything, even without defined // defaults, for safety. var_main_predicate_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_bool_, "xe_var_predicate", builder_->makeBoolConstant(false)); var_main_loop_count_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_uint4_, "xe_var_loop_count", const_uint4_0_); var_main_address_absolute_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_int_, "xe_var_address_absolute", const_int_0_); var_main_address_relative_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_int4_, "xe_var_address_relative", const_int4_0_); uint32_t register_array_size = register_count(); if (register_array_size) { id_vector_temp_.clear(); id_vector_temp_.reserve(register_array_size); // TODO(Triang3l): In PS, only initialize starting from the interpolators, // probably manually. But not very important. for (uint32_t i = 0; i < register_array_size; ++i) { id_vector_temp_.push_back(const_float4_0_); } spv::Id type_register_array = builder_->makeArrayType( type_float4_, builder_->makeUintConstant(register_array_size), 0); var_main_registers_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_register_array, "xe_var_registers", builder_->makeCompositeConstant(type_register_array, id_vector_temp_)); } // Write the execution model-specific prologue with access to variables in the // main function. if (IsSpirvVertexOrTessEvalShader()) { StartVertexOrTessEvalShaderInMain(); } // Open the main loop. spv::Block& main_loop_pre_header = *builder_->getBuildPoint(); main_loop_header_ = &builder_->makeNewBlock(); spv::Block& main_loop_body = builder_->makeNewBlock(); // Added later because the body has nested control flow, but according to the // specification: // "The order of blocks in a function must satisfy the rule that blocks appear // before all blocks they dominate." main_loop_continue_ = new spv::Block(builder_->getUniqueId(), *function_main_); main_loop_merge_ = new spv::Block(builder_->getUniqueId(), *function_main_); builder_->createBranch(main_loop_header_); // If no jumps, don't create a switch, but still create a loop so exece can // break. bool has_main_switch = !label_addresses().empty(); // Main loop header - based on whether it's the first iteration (entered from // the function or from the continuation), choose the program counter. builder_->setBuildPoint(main_loop_header_); spv::Id main_loop_pc_current = 0; if (has_main_switch) { // OpPhi must be the first in the block. id_vector_temp_.clear(); id_vector_temp_.reserve(4); id_vector_temp_.push_back(const_int_0_); id_vector_temp_.push_back(main_loop_pre_header.getId()); main_loop_pc_next_ = builder_->getUniqueId(); id_vector_temp_.push_back(main_loop_pc_next_); id_vector_temp_.push_back(main_loop_continue_->getId()); main_loop_pc_current = builder_->createOp(spv::OpPhi, type_int_, id_vector_temp_); } uint_vector_temp_.clear(); builder_->createLoopMerge(main_loop_merge_, main_loop_continue_, spv::LoopControlDontUnrollMask, uint_vector_temp_); builder_->createBranch(&main_loop_body); // Main loop body. builder_->setBuildPoint(&main_loop_body); if (has_main_switch) { // Create the program counter switch with cases for every label and for // label 0. main_switch_header_ = builder_->getBuildPoint(); main_switch_merge_ = new spv::Block(builder_->getUniqueId(), *function_main_); { std::unique_ptr main_switch_selection_merge_op = std::make_unique(spv::OpSelectionMerge); main_switch_selection_merge_op->addIdOperand(main_switch_merge_->getId()); main_switch_selection_merge_op->addImmediateOperand( spv::SelectionControlDontFlattenMask); builder_->getBuildPoint()->addInstruction( std::move(main_switch_selection_merge_op)); } main_switch_op_ = std::make_unique(spv::OpSwitch); main_switch_op_->addIdOperand(main_loop_pc_current); main_switch_op_->addIdOperand(main_switch_merge_->getId()); // The default case (the merge here) must have the header as a predecessor. main_switch_merge_->addPredecessor(main_switch_header_); // The instruction will be inserted later, when all cases are filled. // Insert and enter case 0. spv::Block* main_switch_case_0_block = new spv::Block(builder_->getUniqueId(), *function_main_); main_switch_op_->addImmediateOperand(0); main_switch_op_->addIdOperand(main_switch_case_0_block->getId()); // Every switch case must have the OpSelectionMerge/OpSwitch block as a // predecessor. main_switch_case_0_block->addPredecessor(main_switch_header_); function_main_->addBlock(main_switch_case_0_block); builder_->setBuildPoint(main_switch_case_0_block); } } std::vector SpirvShaderTranslator::CompleteTranslation() { // Close flow control within the last switch case. CloseExecConditionals(); bool has_main_switch = !label_addresses().empty(); // After the final exec (if it happened to be not exece, which would already // have a break branch), break from the switch if it exists, or from the // loop it doesn't. if (!builder_->getBuildPoint()->isTerminated()) { builder_->createBranch(has_main_switch ? main_switch_merge_ : main_loop_merge_); } if (has_main_switch) { // Insert the switch instruction with all cases added as operands. builder_->setBuildPoint(main_switch_header_); builder_->getBuildPoint()->addInstruction(std::move(main_switch_op_)); // Build the main switch merge, breaking out of the loop after falling // through the end or breaking from exece (only continuing if a jump - from // a guest loop or from jmp/call - was made). function_main_->addBlock(main_switch_merge_); builder_->setBuildPoint(main_switch_merge_); builder_->createBranch(main_loop_merge_); } // Main loop continuation - choose the program counter based on the path // taken (-1 if not from a jump as a safe fallback, which would result in not // hitting any switch case and reaching the final break in the body). function_main_->addBlock(main_loop_continue_); builder_->setBuildPoint(main_loop_continue_); if (has_main_switch) { // OpPhi, if added, must be the first in the block. // If labels were added, but not jumps (for example, due to the call // instruction not being implemented as of October 18, 2020), send an // impossible program counter value (-1) to the OpPhi at the next iteration. if (main_switch_next_pc_phi_operands_.empty()) { main_switch_next_pc_phi_operands_.push_back( builder_->makeIntConstant(-1)); } std::unique_ptr main_loop_pc_next_op = std::make_unique( main_loop_pc_next_, type_int_, main_switch_next_pc_phi_operands_.size() >= 2 ? spv::OpPhi : spv::OpCopyObject); for (spv::Id operand : main_switch_next_pc_phi_operands_) { main_loop_pc_next_op->addIdOperand(operand); } builder_->getBuildPoint()->addInstruction(std::move(main_loop_pc_next_op)); } builder_->createBranch(main_loop_header_); // Add the main loop merge block and go back to the function. function_main_->addBlock(main_loop_merge_); builder_->setBuildPoint(main_loop_merge_); if (IsSpirvVertexOrTessEvalShader()) { CompleteVertexOrTessEvalShaderInMain(); } // End the main function. builder_->leaveFunction(); // Make the main function the entry point. spv::ExecutionModel execution_model; if (IsSpirvFragmentShader()) { execution_model = spv::ExecutionModelFragment; builder_->addExecutionMode(function_main_, spv::ExecutionModeOriginUpperLeft); } else { assert_true(IsSpirvVertexOrTessEvalShader()); execution_model = IsSpirvTessEvalShader() ? spv::ExecutionModelTessellationEvaluation : spv::ExecutionModelVertex; } spv::Instruction* entry_point = builder_->addEntryPoint(execution_model, function_main_, "main"); if (IsSpirvVertexOrTessEvalShader()) { CompleteVertexOrTessEvalShaderAfterMain(entry_point); } // TODO(Triang3l): Avoid copy? std::vector module_uints; builder_->dump(module_uints); std::vector module_bytes; module_bytes.reserve(sizeof(unsigned int) * module_uints.size()); module_bytes.insert(module_bytes.cend(), reinterpret_cast(module_uints.data()), reinterpret_cast(module_uints.data()) + sizeof(unsigned int) * module_uints.size()); return module_bytes; } void SpirvShaderTranslator::ProcessLabel(uint32_t cf_index) { if (cf_index == 0) { // 0 already added in the beginning. return; } assert_false(label_addresses().empty()); // Close flow control within the previous switch case. CloseExecConditionals(); spv::Function& function = builder_->getBuildPoint()->getParent(); // Create the next switch case and fallthrough to it. spv::Block* new_case = new spv::Block(builder_->getUniqueId(), function); main_switch_op_->addImmediateOperand(cf_index); main_switch_op_->addIdOperand(new_case->getId()); // Every switch case must have the OpSelectionMerge/OpSwitch block as a // predecessor. new_case->addPredecessor(main_switch_header_); // The previous block may have already been terminated if was exece. if (!builder_->getBuildPoint()->isTerminated()) { builder_->createBranch(new_case); } function.addBlock(new_case); builder_->setBuildPoint(new_case); } void SpirvShaderTranslator::ProcessExecInstructionBegin( const ParsedExecInstruction& instr) { UpdateExecConditionals(instr.type, instr.bool_constant_index, instr.condition); } void SpirvShaderTranslator::ProcessExecInstructionEnd( const ParsedExecInstruction& instr) { if (instr.is_end) { // Break out of the main switch (if exists) and the main loop. CloseInstructionPredication(); if (!builder_->getBuildPoint()->isTerminated()) { builder_->createBranch(label_addresses().empty() ? main_loop_merge_ : main_switch_merge_); } } UpdateExecConditionals(instr.type, instr.bool_constant_index, instr.condition); } void SpirvShaderTranslator::ProcessLoopStartInstruction( const ParsedLoopStartInstruction& instr) { // loop il, L - loop with loop data il, end @ L // Loop control is outside execs - actually close the last exec. CloseExecConditionals(); EnsureBuildPointAvailable(); id_vector_temp_.clear(); id_vector_temp_.reserve(3); // Loop constants (member 1). id_vector_temp_.push_back(builder_->makeIntConstant(1)); // 4-component vector. id_vector_temp_.push_back( builder_->makeIntConstant(int(instr.loop_constant_index >> 2))); // Scalar within the vector. id_vector_temp_.push_back( builder_->makeIntConstant(int(instr.loop_constant_index & 3))); // Count (unsigned) in bits 0:7 of the loop constant (struct member 1), // initial aL (unsigned) in 8:15. spv::Id loop_constant = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_bool_loop_constants_, id_vector_temp_), spv::NoPrecision); spv::Id const_int_8 = builder_->makeIntConstant(8); // Push the count to the loop count stack - move XYZ to YZW and set X to the // new iteration count (swizzling the way glslang does it for similar GLSL). spv::Id loop_count_stack_old = builder_->createLoad(var_main_loop_count_, spv::NoPrecision); spv::Id loop_count_new = builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_, loop_constant, const_int_0_, const_int_8); id_vector_temp_.clear(); id_vector_temp_.reserve(4); id_vector_temp_.push_back(loop_count_new); for (unsigned int i = 0; i < 3; ++i) { id_vector_temp_.push_back( builder_->createCompositeExtract(loop_count_stack_old, type_uint_, i)); } builder_->createStore( builder_->createCompositeConstruct(type_uint4_, id_vector_temp_), var_main_loop_count_); // Push aL - keep the same value as in the previous loop if repeating, or the // new one otherwise. spv::Id address_relative_stack_old = builder_->createLoad(var_main_address_relative_, spv::NoPrecision); id_vector_temp_.clear(); id_vector_temp_.reserve(4); if (instr.is_repeat) { id_vector_temp_.emplace_back(); } else { id_vector_temp_.push_back(builder_->createUnaryOp( spv::OpBitcast, type_int_, builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_, loop_constant, const_int_8, const_int_8))); } for (unsigned int i = 0; i < 3; ++i) { id_vector_temp_.push_back(builder_->createCompositeExtract( address_relative_stack_old, type_int_, i)); } if (instr.is_repeat) { id_vector_temp_[0] = id_vector_temp_[1]; } builder_->createStore( builder_->createCompositeConstruct(type_int4_, id_vector_temp_), var_main_address_relative_); // Break (jump to the skip label) if the loop counter is 0 (since the // condition is checked in the end). spv::Block& head_block = *builder_->getBuildPoint(); spv::Id loop_count_zero = builder_->createBinOp( spv::OpIEqual, type_bool_, loop_count_new, const_uint_0_); spv::Block& skip_block = builder_->makeNewBlock(); spv::Block& body_block = builder_->makeNewBlock(); { std::unique_ptr selection_merge_op = std::make_unique(spv::OpSelectionMerge); selection_merge_op->addIdOperand(body_block.getId()); selection_merge_op->addImmediateOperand(spv::SelectionControlMaskNone); head_block.addInstruction(std::move(selection_merge_op)); } { std::unique_ptr branch_conditional_op = std::make_unique(spv::OpBranchConditional); branch_conditional_op->addIdOperand(loop_count_zero); branch_conditional_op->addIdOperand(skip_block.getId()); branch_conditional_op->addIdOperand(body_block.getId()); // More likely to enter than to skip. branch_conditional_op->addImmediateOperand(1); branch_conditional_op->addImmediateOperand(2); head_block.addInstruction(std::move(branch_conditional_op)); } skip_block.addPredecessor(&head_block); body_block.addPredecessor(&head_block); builder_->setBuildPoint(&skip_block); main_switch_next_pc_phi_operands_.push_back( builder_->makeIntConstant(int(instr.loop_skip_address))); main_switch_next_pc_phi_operands_.push_back( builder_->getBuildPoint()->getId()); builder_->createBranch(main_loop_continue_); builder_->setBuildPoint(&body_block); } void SpirvShaderTranslator::ProcessLoopEndInstruction( const ParsedLoopEndInstruction& instr) { // endloop il, L - end loop w/ data il, head @ L // Loop control is outside execs - actually close the last exec. CloseExecConditionals(); EnsureBuildPointAvailable(); // Subtract 1 from the loop counter (will store later). spv::Id loop_count_stack_old = builder_->createLoad(var_main_loop_count_, spv::NoPrecision); spv::Id loop_count = builder_->createBinOp( spv::OpISub, type_uint_, builder_->createCompositeExtract(loop_count_stack_old, type_uint_, 0), builder_->makeUintConstant(1)); spv::Id address_relative_stack_old = builder_->createLoad(var_main_address_relative_, spv::NoPrecision); // Predicated break works like break if (loop_count == 0 || [!]p0). // Three options, due to logical operations usage (so OpLogicalNot is not // required): // - Continue if (loop_count != 0). // - Continue if (loop_count != 0 && p0), if breaking if !p0. // - Break if (loop_count == 0 || p0), if breaking if p0. bool break_is_true = instr.is_predicated_break && instr.predicate_condition; spv::Id condition = builder_->createBinOp(break_is_true ? spv::OpIEqual : spv::OpINotEqual, type_bool_, loop_count, const_uint_0_); if (instr.is_predicated_break) { condition = builder_->createBinOp( instr.predicate_condition ? spv::OpLogicalOr : spv::OpLogicalAnd, type_bool_, condition, builder_->createLoad(var_main_predicate_, spv::NoPrecision)); } spv::Block& body_block = *builder_->getBuildPoint(); spv::Block& continue_block = builder_->makeNewBlock(); spv::Block& break_block = builder_->makeNewBlock(); { std::unique_ptr selection_merge_op = std::make_unique(spv::OpSelectionMerge); selection_merge_op->addIdOperand(break_block.getId()); selection_merge_op->addImmediateOperand(spv::SelectionControlMaskNone); body_block.addInstruction(std::move(selection_merge_op)); } { std::unique_ptr branch_conditional_op = std::make_unique(spv::OpBranchConditional); branch_conditional_op->addIdOperand(condition); // More likely to continue than to break. if (break_is_true) { branch_conditional_op->addIdOperand(break_block.getId()); branch_conditional_op->addIdOperand(continue_block.getId()); branch_conditional_op->addImmediateOperand(1); branch_conditional_op->addImmediateOperand(2); } else { branch_conditional_op->addIdOperand(continue_block.getId()); branch_conditional_op->addIdOperand(break_block.getId()); branch_conditional_op->addImmediateOperand(2); branch_conditional_op->addImmediateOperand(1); } body_block.addInstruction(std::move(branch_conditional_op)); } continue_block.addPredecessor(&body_block); break_block.addPredecessor(&body_block); // Continue case. builder_->setBuildPoint(&continue_block); // Store the loop count with 1 subtracted. builder_->createStore(builder_->createCompositeInsert( loop_count, loop_count_stack_old, type_uint4_, 0), var_main_loop_count_); // Extract the value to add to aL (signed, in bits 16:23 of the loop // constant). id_vector_temp_.clear(); id_vector_temp_.reserve(3); // Loop constants (member 1). id_vector_temp_.push_back(builder_->makeIntConstant(1)); // 4-component vector. id_vector_temp_.push_back( builder_->makeIntConstant(int(instr.loop_constant_index >> 2))); // Scalar within the vector. id_vector_temp_.push_back( builder_->makeIntConstant(int(instr.loop_constant_index & 3))); spv::Id loop_constant = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_bool_loop_constants_, id_vector_temp_), spv::NoPrecision); spv::Id address_relative_old = builder_->createCompositeExtract( address_relative_stack_old, type_int_, 0); builder_->createStore( builder_->createCompositeInsert( builder_->createBinOp( spv::OpIAdd, type_int_, address_relative_old, builder_->createTriOp( spv::OpBitFieldSExtract, type_int_, builder_->createUnaryOp(spv::OpBitcast, type_int_, loop_constant), builder_->makeIntConstant(16), builder_->makeIntConstant(8))), address_relative_stack_old, type_int4_, 0), var_main_address_relative_); // Jump back to the beginning of the loop body. main_switch_next_pc_phi_operands_.push_back( builder_->makeIntConstant(int(instr.loop_body_address))); main_switch_next_pc_phi_operands_.push_back( builder_->getBuildPoint()->getId()); builder_->createBranch(main_loop_continue_); // Break case. builder_->setBuildPoint(&break_block); // Pop the current loop off the loop counter and the relative address stacks - // move YZW to XYZ and set W to 0. id_vector_temp_.clear(); id_vector_temp_.reserve(4); for (unsigned int i = 1; i < 4; ++i) { id_vector_temp_.push_back( builder_->createCompositeExtract(loop_count_stack_old, type_uint_, i)); } id_vector_temp_.push_back(const_uint_0_); builder_->createStore( builder_->createCompositeConstruct(type_uint4_, id_vector_temp_), var_main_loop_count_); id_vector_temp_.clear(); id_vector_temp_.reserve(4); for (unsigned int i = 1; i < 4; ++i) { id_vector_temp_.push_back(builder_->createCompositeExtract( address_relative_stack_old, type_int_, i)); } id_vector_temp_.push_back(const_int_0_); builder_->createStore( builder_->createCompositeConstruct(type_int4_, id_vector_temp_), var_main_address_relative_); id_vector_temp_.clear(); id_vector_temp_.reserve(4); // Now going to fall through to the next control flow instruction. } void SpirvShaderTranslator::ProcessJumpInstruction( const ParsedJumpInstruction& instr) { // Treat like exec, merge with execs if possible, since it's an if too. ParsedExecInstruction::Type type; if (instr.type == ParsedJumpInstruction::Type::kConditional) { type = ParsedExecInstruction::Type::kConditional; } else if (instr.type == ParsedJumpInstruction::Type::kPredicated) { type = ParsedExecInstruction::Type::kPredicated; } else { type = ParsedExecInstruction::Type::kUnconditional; } UpdateExecConditionals(type, instr.bool_constant_index, instr.condition); // UpdateExecConditionals may not necessarily close the instruction-level // predicate check (it's not necessary if the execs are merged), but here the // instruction itself is on the control flow level, so the predicate check is // on the control flow level too. CloseInstructionPredication(); if (builder_->getBuildPoint()->isTerminated()) { // Unreachable for some reason. return; } main_switch_next_pc_phi_operands_.push_back( builder_->makeIntConstant(int(instr.target_address))); main_switch_next_pc_phi_operands_.push_back( builder_->getBuildPoint()->getId()); builder_->createBranch(main_loop_continue_); } void SpirvShaderTranslator::EnsureBuildPointAvailable() { if (!builder_->getBuildPoint()->isTerminated()) { return; } spv::Block& new_block = builder_->makeNewBlock(); new_block.setUnreachable(); builder_->setBuildPoint(&new_block); } void SpirvShaderTranslator::StartVertexOrTessEvalShaderBeforeMain() { // Create the inputs. if (IsSpirvTessEvalShader()) { input_primitive_id_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassInput, type_int_, "gl_PrimitiveID"); builder_->addDecoration(input_primitive_id_, spv::DecorationBuiltIn, spv::BuiltInPrimitiveId); } else { input_vertex_index_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassInput, type_int_, "gl_VertexIndex"); builder_->addDecoration(input_vertex_index_, spv::DecorationBuiltIn, spv::BuiltInVertexIndex); } // Create the entire GLSL 4.50 gl_PerVertex output similar to what glslang // does. Members (like gl_PointSize) don't need to be used, and also // ClipDistance and CullDistance may exist even if the device doesn't support // them, as long as the capabilities aren't enabled, and nothing is stored to // them. if (supports_clip_distance_) { builder_->addCapability(spv::CapabilityClipDistance); } if (supports_cull_distance_) { builder_->addCapability(spv::CapabilityCullDistance); } std::vector struct_per_vertex_members; struct_per_vertex_members.reserve(kOutputPerVertexMemberCount); struct_per_vertex_members.push_back(type_float4_); struct_per_vertex_members.push_back(type_float_); // TODO(Triang3l): Specialization constant for ucp_cull_only_ena, for 6 + 1 // or 1 + 7 array sizes. struct_per_vertex_members.push_back(builder_->makeArrayType( type_float_, builder_->makeUintConstant(supports_clip_distance_ ? 6 : 1), 0)); struct_per_vertex_members.push_back( builder_->makeArrayType(type_float_, builder_->makeUintConstant(1), 0)); spv::Id type_struct_per_vertex = builder_->makeStructType(struct_per_vertex_members, "gl_PerVertex"); builder_->addMemberDecoration(type_struct_per_vertex, kOutputPerVertexMemberPosition, spv::DecorationBuiltIn, spv::BuiltInPosition); builder_->addMemberDecoration(type_struct_per_vertex, kOutputPerVertexMemberPointSize, spv::DecorationBuiltIn, spv::BuiltInPointSize); builder_->addMemberDecoration( type_struct_per_vertex, kOutputPerVertexMemberClipDistance, spv::DecorationBuiltIn, spv::BuiltInClipDistance); builder_->addMemberDecoration( type_struct_per_vertex, kOutputPerVertexMemberCullDistance, spv::DecorationBuiltIn, spv::BuiltInCullDistance); builder_->addDecoration(type_struct_per_vertex, spv::DecorationBlock); output_per_vertex_ = builder_->createVariable(spv::NoPrecision, spv::StorageClassOutput, type_struct_per_vertex, "xe_out_gl_PerVertex"); } void SpirvShaderTranslator::StartVertexOrTessEvalShaderInMain() { var_main_point_size_edge_flag_kill_vertex_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassFunction, type_float3_, "xe_var_point_size_edge_flag_kill_vertex"); } void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() {} void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderAfterMain( spv::Instruction* entry_point) { if (IsSpirvTessEvalShader()) { entry_point->addIdOperand(input_primitive_id_); } else { entry_point->addIdOperand(input_vertex_index_); } entry_point->addIdOperand(output_per_vertex_); } void SpirvShaderTranslator::UpdateExecConditionals( ParsedExecInstruction::Type type, uint32_t bool_constant_index, bool condition) { // Check if we can merge the new exec with the previous one, or the jump with // the previous exec. The instruction-level predicate check is also merged in // this case. if (type == ParsedExecInstruction::Type::kConditional) { // Can merge conditional with conditional, as long as the bool constant and // the expected values are the same. if (cf_exec_conditional_merge_ && cf_exec_bool_constant_or_predicate_ == bool_constant_index && cf_exec_condition_ == condition) { return; } } else if (type == ParsedExecInstruction::Type::kPredicated) { // Can merge predicated with predicated if the conditions are the same and // the previous exec hasn't modified the predicate register. if (!cf_exec_predicate_written_ && cf_exec_conditional_merge_ && cf_exec_bool_constant_or_predicate_ == kCfExecBoolConstantPredicate && cf_exec_condition_ == condition) { return; } } else { // Can merge unconditional with unconditional. assert_true(type == ParsedExecInstruction::Type::kUnconditional); if (!cf_exec_conditional_merge_) { return; } } CloseExecConditionals(); if (type == ParsedExecInstruction::Type::kUnconditional) { return; } EnsureBuildPointAvailable(); spv::Id condition_id; if (type == ParsedExecInstruction::Type::kConditional) { id_vector_temp_.clear(); id_vector_temp_.reserve(3); // Bool constants (member 0). id_vector_temp_.push_back(const_int_0_); // 128-bit vector. id_vector_temp_.push_back( builder_->makeIntConstant(int(bool_constant_index >> 7))); // 32-bit scalar of a 128-bit vector. id_vector_temp_.push_back( builder_->makeIntConstant(int((bool_constant_index >> 5) & 3))); spv::Id bool_constant_scalar = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_bool_loop_constants_, id_vector_temp_), spv::NoPrecision); condition_id = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, bool_constant_scalar, builder_->makeUintConstant(uint32_t(1) << (bool_constant_index & 31))), const_uint_0_); cf_exec_bool_constant_or_predicate_ = bool_constant_index; } else if (type == ParsedExecInstruction::Type::kPredicated) { condition_id = builder_->createLoad(var_main_predicate_, spv::NoPrecision); cf_exec_bool_constant_or_predicate_ = kCfExecBoolConstantPredicate; } else { assert_unhandled_case(type); return; } cf_exec_condition_ = condition; spv::Function& function = builder_->getBuildPoint()->getParent(); cf_exec_conditional_merge_ = new spv::Block(builder_->getUniqueId(), function); { std::unique_ptr selection_merge_op = std::make_unique(spv::OpSelectionMerge); selection_merge_op->addIdOperand(cf_exec_conditional_merge_->getId()); selection_merge_op->addImmediateOperand(spv::SelectionControlMaskNone); builder_->getBuildPoint()->addInstruction(std::move(selection_merge_op)); } spv::Block& inner_block = builder_->makeNewBlock(); builder_->createConditionalBranch( condition_id, condition ? &inner_block : cf_exec_conditional_merge_, condition ? cf_exec_conditional_merge_ : &inner_block); builder_->setBuildPoint(&inner_block); } void SpirvShaderTranslator::CloseInstructionPredication() { if (!cf_instruction_predicate_merge_) { return; } spv::Block& inner_block = *builder_->getBuildPoint(); if (!inner_block.isTerminated()) { builder_->createBranch(cf_instruction_predicate_merge_); } inner_block.getParent().addBlock(cf_instruction_predicate_merge_); builder_->setBuildPoint(cf_instruction_predicate_merge_); cf_instruction_predicate_merge_ = nullptr; } void SpirvShaderTranslator::CloseExecConditionals() { // Within the exec - instruction-level predicate check. CloseInstructionPredication(); // Exec level. if (cf_exec_conditional_merge_) { spv::Block& inner_block = *builder_->getBuildPoint(); if (!inner_block.isTerminated()) { builder_->createBranch(cf_exec_conditional_merge_); } inner_block.getParent().addBlock(cf_exec_conditional_merge_); builder_->setBuildPoint(cf_exec_conditional_merge_); cf_exec_conditional_merge_ = nullptr; } // Nothing relies on the predicate value being unchanged now. cf_exec_predicate_written_ = false; } } // namespace gpu } // namespace xe